propagate from branch 'im.pidgin.pidgin' (head 545c69f0b96bacaa2f93ecfc4200bb05dc103ea0) vv

Thu, 05 Feb 2009 00:31:35 +0000

author
Michael Ruprecht <maiku@pidgin.im>
date
Thu, 05 Feb 2009 00:31:35 +0000
branch
vv
changeset 26292
14fa95350f0c
parent 25563
545c69f0b96b (current diff)
parent 26291
53ac539c8318 (diff)
child 26293
477ccf9db427

propagate from branch 'im.pidgin.pidgin' (head 545c69f0b96bacaa2f93ecfc4200bb05dc103ea0)
to branch 'im.pidgin.pidgin.vv' (head 53ac539c8318e16dcdcd4c89e51b9c6a995a5726)

ChangeLog.API file | annotate | diff | comparison | revisions
libpurple/network.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/jabber.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/msn.c file | annotate | diff | comparison | revisions
libpurple/protocols/novell/novell.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/qq.c 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/xmlnode.c file | annotate | diff | comparison | revisions
libpurple/xmlnode.h file | annotate | diff | comparison | revisions
pidgin/gtkdialogs.c file | annotate | diff | comparison | revisions
--- a/ChangeLog.API	Wed Feb 04 05:15:49 2009 +0000
+++ b/ChangeLog.API	Thu Feb 05 00:31:35 2009 +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.5 (??/??/2009):
 	libpurple:
 		Changed:
--- a/Doxyfile.in	Wed Feb 04 05:15:49 2009 +0000
+++ b/Doxyfile.in	Thu Feb 05 00:31:35 2009 +0000
@@ -1070,7 +1070,7 @@
 # undefined via #undef or recursively expanded use the := operator 
 # instead of the = operator.
 
-PREDEFINED             = 
+PREDEFINED             = USE_VV
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
 # this tag can be used to specify a list of macro names that should be expanded. 
--- a/configure.ac	Wed Feb 04 05:15:49 2009 +0000
+++ b/configure.ac	Thu Feb 05 00:31:35 2009 +0000
@@ -47,7 +47,7 @@
 m4_define([purple_major_version], [2])
 m4_define([purple_minor_version], [5])
 m4_define([purple_micro_version], [5])
-m4_define([purple_version_suffix], [devel])
+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], [5])
-m4_define([gnt_version_suffix], [devel])
+m4_define([gnt_version_suffix], [vv-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]))
@@ -324,6 +324,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)]),
@@ -745,6 +748,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 >= 0.0.3 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,
@@ -2480,6 +2533,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 Feb 04 05:15:49 2009 +0000
+++ b/finch/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/finch/gntaccount.c	Thu Feb 05 00:31:35 2009 +0000
@@ -1099,3 +1099,4 @@
 	return &ui_ops;
 }
 
+
--- a/finch/gntdebug.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/finch/gntdebug.c	Thu Feb 05 00:31:35 2009 +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>
@@ -386,6 +387,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
 	REGISTER_G_LOG_HANDLER("stderr");
 
 	g_set_print_handler(print_stderr);   /* Redirect the debug messages to stderr */
--- a/finch/gntft.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/finch/gntft.c	Thu Feb 05 00:31:35 2009 +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	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,382 @@
+/**
+ * @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;
+
+	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,
+};
+
+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));
+
+	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)
+{
+	FinchMedia *gntmedia = FINCH_MEDIA(media);
+	purple_debug_info("gntmedia", "finch_media_finalize\n");
+	if (gntmedia->priv->media)
+		g_object_unref(gntmedia->priv->media);
+}
+
+static void
+finch_media_emit_message(FinchMedia *gntmedia, const char *msg)
+{
+	g_signal_emit(gntmedia, finch_media_signals[MESSAGE], 0, msg);
+}
+
+static void
+finch_media_ready_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	GstElement *sendbin, *sendlevel;
+
+	GList *sessions = purple_media_get_session_names(media);
+
+	purple_media_audio_init_src(&sendbin, &sendlevel);
+
+	for (; sessions; sessions = sessions->next) {
+		purple_media_set_src(media, sessions->data, sendbin);
+	}
+	g_list_free(sessions);
+}
+
+static void
+finch_media_accept_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	GntWidget *parent;
+	GstElement *sendbin = NULL;
+
+	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);
+
+	purple_media_get_elements(media, &sendbin, NULL, NULL, NULL);
+	gst_element_set_state(GST_ELEMENT(sendbin), GST_STATE_PLAYING);
+}
+
+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);
+
+	parent = GNT_WIDGET(gntmedia);
+	while (parent->parent)
+		parent = parent->parent;
+	gnt_box_readjust(GNT_BOX(parent));
+	gnt_widget_draw(parent);
+}
+
+static void
+finch_media_state_changed_cb(PurpleMedia *media,
+		PurpleMediaStateChangedType type,
+		gchar *sid, gchar *name, FinchMedia *gntmedia)
+{
+	purple_debug_info("gntmedia", "type: %d sid: %s name: %s\n",
+			type, sid, name);
+	if (sid == NULL && name == NULL) {
+		if (type == PURPLE_MEDIA_STATE_CHANGED_END) {
+			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));
+			/*
+			 * XXX: This shouldn't have to be here
+			 * to free the FinchMedia widget.
+			 */
+			g_object_unref(gntmedia);
+		} else if (type == PURPLE_MEDIA_STATE_CHANGED_REJECTED) {
+			finch_media_emit_message(gntmedia,
+					_("You have rejected the call."));
+		}
+	} else if (type == PURPLE_MEDIA_STATE_CHANGED_NEW
+			&& sid != NULL && name != NULL) {
+		finch_media_ready_cb(media, gntmedia);
+	} else if (type == PURPLE_MEDIA_STATE_CHANGED_CONNECTED) {
+		finch_media_accept_cb(media, 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:
+		{
+			gboolean is_initiator;
+			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_object_get(G_OBJECT(media->priv->media), "initiator",
+					&is_initiator, NULL);
+			if (is_initiator == TRUE) {
+				finch_media_wait_cb(media->priv->media, media);
+			}
+			g_signal_connect(G_OBJECT(media->priv->media), "state-changed",
+				G_CALLBACK(finch_media_state_changed_cb), media);
+			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;
+		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 gboolean
+finch_new_media(PurpleMediaManager *manager, PurpleMedia *media,
+		PurpleConnection *gc, gchar *name, gpointer null)
+{
+	GntWidget *gntmedia;
+	PurpleConversation *conv;
+
+	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
+			purple_connection_get_account(gc), name);
+
+	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);
+	return TRUE;
+}
+
+static PurpleCmdRet
+call_cmd_cb(PurpleConversation *conv, const char *cmd, char **args,
+		char **eror, gpointer data)
+{
+	PurpleAccount *account = purple_conversation_get_account(conv);
+
+	PurpleMedia *media = purple_prpl_initiate_media(account,
+			purple_conversation_get_name(conv),
+			PURPLE_MEDIA_AUDIO);
+
+	if (!media)
+		return PURPLE_CMD_STATUS_FAILED;
+
+	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	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/finch/gntnotify.c	Thu Feb 05 00:31:35 2009 +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"
 #include "debug.h"
--- a/finch/gntplugin.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/finch/gntplugin.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/finch/gntpounce.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/finch/gntrequest.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/finch/gntstatus.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/finch/gntui.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/finch/libgnt/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -91,6 +91,7 @@
 
 AM_CPPFLAGS = \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GNT_CFLAGS) \
 	$(DEBUG_CFLAGS) \
 	$(LIBXML_CFLAGS) \
--- a/finch/libgnt/gntkeys.h	Wed Feb 04 05:15:49 2009 +0000
+++ b/finch/libgnt/gntkeys.h	Thu Feb 05 00:31:35 2009 +0000
@@ -165,5 +165,6 @@
 #undef lines
 #undef buttons
 #undef newline
+#undef set_clock
 
 #endif
--- a/finch/libgnt/wms/Makefile.am	Wed Feb 04 05:15:49 2009 +0000
+++ b/finch/libgnt/wms/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -36,5 +36,6 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(GNT_CFLAGS) \
-	$(PLUGIN_CFLAGS)
+	$(PLUGIN_CFLAGS) \
+	$(FARSIGHT_CFLAGS) 
 
--- a/finch/libgnt/wms/s.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/finch/libgnt/wms/s.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/finch/plugins/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/Makefile.am	Thu Feb 05 00:31:35 2009 +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 marshallers.h
+	@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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/example/Makefile.am	Thu Feb 05 00:31:35 2009 +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	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,5 @@
+VOID:BOXED,BOXED
+VOID:POINTER,POINTER,OBJECT
+BOOLEAN:OBJECT,POINTER,STRING
+VOID:STRING,STRING
+VOID:ENUM,STRING,STRING
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,2485 @@
+/**
+ * @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 "mediamanager.h"
+#include "network.h"
+
+#include "debug.h"
+
+#ifdef USE_VV
+
+#include <gst/interfaces/propertyprobe.h>
+#include <gst/interfaces/xoverlay.h>
+#include <gst/farsight/fs-conference-iface.h>
+
+/** @copydoc _PurpleMediaSession */
+typedef struct _PurpleMediaSession PurpleMediaSession;
+/** @copydoc _PurpleMediaStream */
+typedef struct _PurpleMediaStream PurpleMediaStream;
+
+struct _PurpleMediaSession
+{
+	gchar *id;
+	PurpleMedia *media;
+	GstElement *src;
+	FsSession *session;
+
+	PurpleMediaSessionType type;
+
+	gboolean codecs_ready;
+	gboolean accepted;
+
+	GstElement *sink;
+	gulong window_id;
+};
+
+struct _PurpleMediaStream
+{
+	PurpleMediaSession *session;
+	gchar *participant;
+	FsStream *stream;
+	GstElement *sink;
+
+	GList *local_candidates;
+	GList *remote_candidates;
+
+	gboolean candidates_prepared;
+
+	FsCandidate *local_candidate;
+	FsCandidate *remote_candidate;
+
+	gulong window_id;
+};
+
+struct _PurpleMediaPrivate
+{
+	FsConference *conference;
+	gboolean initiator;
+
+	GHashTable *sessions;	/* PurpleMediaSession table */
+	GHashTable *participants; /* FsParticipant table */
+
+	GList *streams;		/* PurpleMediaStream table */
+
+	GstElement *pipeline;
+	GstElement *confbin;
+};
+
+#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_dispose (GObject *object);
+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 void purple_media_new_local_candidate_cb(FsStream *stream,
+		FsCandidate *local_candidate, PurpleMediaSession *session);
+static void purple_media_candidates_prepared_cb(FsStream *stream,
+		PurpleMediaSession *session);
+static void purple_media_candidate_pair_established_cb(FsStream *stream,
+		FsCandidate *native_candidate, FsCandidate *remote_candidate,
+		PurpleMediaSession *session);
+
+static GObjectClass *parent_class = NULL;
+
+
+
+enum {
+	ERROR,
+	ACCEPTED,
+	CODECS_CHANGED,
+	NEW_CANDIDATE,
+	READY_NEW,
+	STATE_CHANGED,
+	LAST_SIGNAL
+};
+static guint purple_media_signals[LAST_SIGNAL] = {0};
+
+enum {
+	PROP_0,
+	PROP_CONFERENCE,
+	PROP_INITIATOR,
+};
+
+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;
+}
+
+GType
+purple_media_state_changed_get_type()
+{
+	static GType type = 0;
+	if (type == 0) {
+		static const GEnumValue values[] = {
+			{ PURPLE_MEDIA_STATE_CHANGED_NEW, "PURPLE_MEDIA_STATE_CHANGED_NEW", "new" },
+			{ PURPLE_MEDIA_STATE_CHANGED_CONNECTED, "PURPLE_MEDIA_STATE_CHANGED_CONNECTED", "connected" },
+			{ PURPLE_MEDIA_STATE_CHANGED_END, "PURPLE_MEDIA_STATE_CHANGED_END", "end" },
+			{ 0, NULL, NULL }
+		};
+		type = g_enum_register_static("PurpleMediaStateChangedType", values);
+	}
+	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->dispose = purple_media_dispose;
+	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_CONFERENCE,
+			g_param_spec_object("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_INITIATOR,
+			g_param_spec_boolean("initiator",
+			"initiator",
+			"If the local user initiated the conference.",
+			FALSE,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	purple_media_signals[ERROR] = g_signal_new("error", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__STRING,
+					 G_TYPE_NONE, 1, G_TYPE_STRING);
+	purple_media_signals[ACCEPTED] = g_signal_new("accepted", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__STRING_STRING,
+					 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+	purple_media_signals[CODECS_CHANGED] = g_signal_new("codecs-changed", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__STRING,
+					 G_TYPE_NONE, 1, G_TYPE_STRING);
+	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, PURPLE_TYPE_MEDIA_CANDIDATE);
+	purple_media_signals[READY_NEW] = g_signal_new("ready-new", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__STRING_STRING,
+					 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+	purple_media_signals[STATE_CHANGED] = g_signal_new("state-changed", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__ENUM_STRING_STRING,
+					 G_TYPE_NONE, 3, PURPLE_MEDIA_TYPE_STATE_CHANGED,
+					 G_TYPE_STRING, G_TYPE_STRING);
+	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_stream_free(PurpleMediaStream *stream)
+{
+	if (stream == NULL)
+		return;
+
+	/* Remove the connected_cb timeout */
+	g_source_remove_by_user_data(stream);
+
+	g_free(stream->participant);
+
+	if (stream->local_candidates)
+		fs_candidate_list_destroy(stream->local_candidates);
+	if (stream->remote_candidates)
+		fs_candidate_list_destroy(stream->remote_candidates);
+
+	if (stream->local_candidate)
+		fs_candidate_destroy(stream->local_candidate);
+	if (stream->remote_candidate)
+		fs_candidate_destroy(stream->remote_candidate);
+
+	g_free(stream);
+}
+
+static void
+purple_media_session_free(PurpleMediaSession *session)
+{
+	if (session == NULL)
+		return;
+
+	g_free(session->id);
+	g_free(session);
+}
+
+static void
+purple_media_dispose(GObject *media)
+{
+	PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media);
+	GList *iter = NULL;
+
+	purple_debug_info("media","purple_media_dispose\n");
+
+	purple_media_manager_remove_media(purple_media_manager_get(),
+			PURPLE_MEDIA(media));
+
+	if (priv->confbin) {
+		gst_element_set_state(GST_ELEMENT(priv->confbin),
+				GST_STATE_NULL);
+		gst_bin_remove(GST_BIN(priv->pipeline), priv->confbin);
+		priv->confbin = NULL;
+		priv->conference = NULL;
+	}
+
+	for (iter = priv->streams; iter; iter = g_list_next(iter)) {
+		PurpleMediaStream *stream = iter->data;
+		if (stream->stream) {
+			g_object_unref(stream->stream);
+			stream->stream = NULL;
+		}
+	}
+
+	if (priv->sessions) {
+		GList *sessions = g_hash_table_get_values(priv->sessions);
+		for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+			PurpleMediaSession *session = sessions->data;
+			if (session->session) {
+				g_object_unref(session->session);
+				session->session = NULL;
+			}
+		}
+	}
+
+	if (priv->participants) {
+		GList *participants = g_hash_table_get_values(priv->participants);
+		for (; participants; participants = g_list_delete_link(participants, participants))
+			g_object_unref(participants->data);
+	}
+
+	G_OBJECT_CLASS(parent_class)->finalize(media);
+}
+
+static void
+purple_media_finalize(GObject *media)
+{
+	PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media);
+	purple_debug_info("media","purple_media_finalize\n");
+
+	for (; priv->streams; priv->streams = g_list_delete_link(priv->streams, priv->streams))
+		purple_media_stream_free(priv->streams->data);
+
+	if (priv->sessions) {
+		GList *sessions = g_hash_table_get_values(priv->sessions);
+		for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+			purple_media_session_free(sessions->data);
+		}
+		g_hash_table_destroy(priv->sessions);
+	}
+
+	G_OBJECT_CLASS(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_CONFERENCE: {
+			gchar *name;
+
+			if (media->priv->conference)
+				gst_object_unref(media->priv->conference);
+			media->priv->conference = g_value_get_object(value);
+			gst_object_ref(media->priv->conference);
+
+			name = g_strdup_printf("conf_%p",
+					media->priv->conference);
+			media->priv->confbin = gst_bin_new(name);
+			g_free(name);
+			gst_bin_add(GST_BIN(purple_media_get_pipeline(media)),
+					media->priv->confbin);
+			gst_bin_add(GST_BIN(media->priv->confbin),
+					GST_ELEMENT(media->priv->conference));
+
+			gst_element_set_state(GST_ELEMENT(
+					media->priv->confbin),
+					GST_STATE_PLAYING);
+			break;
+		}
+		case PROP_INITIATOR:
+			media->priv->initiator = g_value_get_boolean(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_CONFERENCE:
+			g_value_set_object(value, media->priv->conference);
+			break;
+		case PROP_INITIATOR:
+			g_value_set_boolean(value, media->priv->initiator);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+
+}
+
+PurpleMediaCandidate *
+purple_media_candidate_new(const gchar *foundation, guint component_id,
+		PurpleMediaCandidateType type,
+		PurpleMediaNetworkProtocol proto,
+		const gchar *ip, guint port)
+{
+	PurpleMediaCandidate *candidate = g_new0(PurpleMediaCandidate, 1);
+	candidate->foundation = g_strdup(foundation);
+	candidate->component_id = component_id;
+	candidate->type = type;
+	candidate->proto = proto;
+	candidate->ip = g_strdup(ip);
+	candidate->port = port;
+	return candidate;
+}
+
+static PurpleMediaCandidate *
+purple_media_candidate_copy(PurpleMediaCandidate *candidate)
+{
+	PurpleMediaCandidate *new_candidate;
+
+	if (candidate == NULL)
+		return NULL;
+
+	new_candidate = g_new0(PurpleMediaCandidate, 1);
+	new_candidate->foundation = g_strdup(candidate->foundation);
+	new_candidate->component_id = candidate->component_id;
+	new_candidate->ip = g_strdup(candidate->ip);
+	new_candidate->port = candidate->port;
+	new_candidate->base_ip = g_strdup(candidate->base_ip);
+	new_candidate->base_port = candidate->base_port;
+	new_candidate->proto = candidate->proto;
+	new_candidate->priority = candidate->priority;
+	new_candidate->type = candidate->type;
+	new_candidate->username = g_strdup(candidate->username);
+	new_candidate->password = g_strdup(candidate->password);
+	new_candidate->ttl = candidate->ttl;
+	return new_candidate;
+}
+
+static void
+purple_media_candidate_free(PurpleMediaCandidate *candidate)
+{
+	if (candidate == NULL)
+		return;
+
+	g_free((gchar*)candidate->foundation);
+	g_free((gchar*)candidate->ip);
+	g_free((gchar*)candidate->base_ip);
+	g_free((gchar*)candidate->username);
+	g_free((gchar*)candidate->password);
+	g_free(candidate);
+}
+
+static FsCandidate *
+purple_media_candidate_to_fs(PurpleMediaCandidate *candidate)
+{
+	FsCandidate *fscandidate;
+
+	if (candidate == NULL)
+		return NULL;
+
+	fscandidate = fs_candidate_new(candidate->foundation,
+		candidate->component_id, candidate->type,
+		candidate->proto, candidate->ip, candidate->port);
+
+	fscandidate->base_ip = g_strdup(candidate->base_ip);
+	fscandidate->base_port = candidate->base_port;
+	fscandidate->priority = candidate->priority;
+	fscandidate->username = g_strdup(candidate->username);
+	fscandidate->password = g_strdup(candidate->password);
+	fscandidate->ttl = candidate->ttl;
+	return fscandidate;
+}
+
+static PurpleMediaCandidate *
+purple_media_candidate_from_fs(FsCandidate *fscandidate)
+{
+	PurpleMediaCandidate *candidate;
+
+	if (fscandidate == NULL)
+		return NULL;
+
+	candidate = purple_media_candidate_new(fscandidate->foundation,
+		fscandidate->component_id, fscandidate->type,
+		fscandidate->proto, fscandidate->ip, fscandidate->port);
+	candidate->base_ip = g_strdup(fscandidate->base_ip);
+	candidate->base_port = fscandidate->base_port;
+	candidate->priority = fscandidate->priority;
+	candidate->username = g_strdup(fscandidate->username);
+	candidate->password = g_strdup(fscandidate->password);
+	candidate->ttl = fscandidate->ttl;
+	return candidate;
+}
+
+static GList *
+purple_media_candidate_list_from_fs(GList *candidates)
+{
+	GList *new_list = NULL;
+
+	for (; candidates; candidates = g_list_next(candidates)) {
+		new_list = g_list_prepend(new_list,
+				purple_media_candidate_from_fs(
+				candidates->data));
+	}
+
+	new_list = g_list_reverse(new_list);
+	return new_list;
+}
+
+static GList *
+purple_media_candidate_list_to_fs(GList *candidates)
+{
+	GList *new_list = NULL;
+
+	for (; candidates; candidates = g_list_next(candidates)) {
+		new_list = g_list_prepend(new_list,
+				purple_media_candidate_to_fs(
+				candidates->data));
+	}
+
+	new_list = g_list_reverse(new_list);
+	return new_list;
+}
+
+GList *
+purple_media_candidate_list_copy(GList *candidates)
+{
+	GList *new_list = NULL;
+
+	for (; candidates; candidates = g_list_next(candidates)) {
+		new_list = g_list_prepend(new_list, g_boxed_copy(
+				PURPLE_TYPE_MEDIA_CANDIDATE,
+				candidates->data));
+	}
+
+	new_list = g_list_reverse(new_list);
+	return new_list;
+}
+
+void
+purple_media_candidate_list_free(GList *candidates)
+{
+	for (; candidates; candidates =
+			g_list_delete_link(candidates, candidates)) {
+		g_boxed_free(PURPLE_TYPE_MEDIA_CANDIDATE,
+				candidates->data);
+	}
+}
+
+GType
+purple_media_candidate_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		type = g_boxed_type_register_static("PurpleMediaCandidate",
+				(GBoxedCopyFunc)purple_media_candidate_copy,
+				(GBoxedFreeFunc)purple_media_candidate_free);
+	}
+	return type;
+}
+
+static FsMediaType
+purple_media_to_fs_media_type(PurpleMediaSessionType type)
+{
+	if (type & PURPLE_MEDIA_AUDIO)
+		return FS_MEDIA_TYPE_AUDIO;
+	else if (type & PURPLE_MEDIA_VIDEO)
+		return FS_MEDIA_TYPE_VIDEO;
+	else
+		return 0;
+}
+
+static FsStreamDirection
+purple_media_to_fs_stream_direction(PurpleMediaSessionType 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;
+}
+
+static PurpleMediaSessionType
+purple_media_from_fs(FsMediaType type, FsStreamDirection direction)
+{
+	PurpleMediaSessionType 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;
+}
+
+void
+purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec,
+		const gchar *name, const gchar *value)
+{
+	PurpleMediaCodecParameter *new_param;
+
+	g_return_if_fail(codec != NULL);
+	g_return_if_fail(name != NULL && value != NULL);
+
+	new_param = g_new0(PurpleMediaCodecParameter, 1);
+	new_param->name = g_strdup(name);
+	new_param->value = g_strdup(value);
+	codec->optional_params = g_list_append(
+			codec->optional_params, new_param);
+}
+
+void
+purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec,
+		PurpleMediaCodecParameter *param)
+{
+	g_return_if_fail(codec != NULL && param != NULL);
+
+	g_free(param->name);
+	g_free(param->value);
+	g_free(param);
+
+	codec->optional_params =
+			g_list_remove(codec->optional_params, param);
+}
+
+PurpleMediaCodecParameter *
+purple_media_codec_get_optional_parameter(PurpleMediaCodec *codec,
+		const gchar *name, const gchar *value)
+{
+	GList *iter;
+
+	g_return_val_if_fail(codec != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
+
+	for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
+		PurpleMediaCodecParameter *param = iter->data;
+		if (!g_ascii_strcasecmp(param->name, name) &&
+				(value == NULL ||
+				!g_ascii_strcasecmp(param->value, value)))
+			return param;
+	}
+
+	return NULL;
+}
+
+PurpleMediaCodec *
+purple_media_codec_new(int id, const char *encoding_name,
+		PurpleMediaSessionType media_type, guint clock_rate)
+{
+	PurpleMediaCodec *codec = g_new0(PurpleMediaCodec, 1);
+
+	codec->id = id;
+	codec->encoding_name = g_strdup(encoding_name);
+	codec->media_type = media_type;
+	codec->clock_rate = clock_rate;
+	return codec;
+}
+
+static PurpleMediaCodec *
+purple_media_codec_copy(PurpleMediaCodec *codec)
+{
+	PurpleMediaCodec *new_codec;
+	GList *iter;
+
+	if (codec == NULL)
+		return NULL;
+
+	new_codec = purple_media_codec_new(codec->id, codec->encoding_name,
+			codec->media_type, codec->clock_rate);
+	new_codec->channels = codec->channels;
+
+	for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
+		PurpleMediaCodecParameter *param =
+				(PurpleMediaCodecParameter*)iter->data;
+		purple_media_codec_add_optional_parameter(new_codec,
+				param->name, param->value);
+	}
+
+	return new_codec;
+}
+
+static void
+purple_media_codec_free(PurpleMediaCodec *codec)
+{
+	if (codec == NULL)
+		return;
+
+	g_free(codec->encoding_name);
+
+	for (; codec->optional_params; codec->optional_params =
+			g_list_delete_link(codec->optional_params,
+			codec->optional_params)) {
+		purple_media_codec_remove_optional_parameter(codec,
+				codec->optional_params->data);
+	}
+
+	g_free(codec);
+}
+
+static FsCodec *
+purple_media_codec_to_fs(const PurpleMediaCodec *codec)
+{
+	FsCodec *new_codec;
+	GList *iter;
+
+	if (codec == NULL)
+		return NULL;
+
+	new_codec = fs_codec_new(codec->id, codec->encoding_name,
+			purple_media_to_fs_media_type(codec->media_type),
+			codec->clock_rate);
+	new_codec->channels = codec->channels;
+
+	for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
+		PurpleMediaCodecParameter *param =
+				(PurpleMediaCodecParameter*)iter->data;
+		fs_codec_add_optional_parameter(new_codec,
+				param->name, param->value);
+	}
+
+	return new_codec;
+}
+
+static PurpleMediaCodec *
+purple_media_codec_from_fs(const FsCodec *codec)
+{
+	PurpleMediaCodec *new_codec;
+	GList *iter;
+
+	if (codec == NULL)
+		return NULL;
+
+	new_codec = purple_media_codec_new(codec->id, codec->encoding_name,
+			purple_media_from_fs(codec->media_type,
+			FS_DIRECTION_BOTH), codec->clock_rate);
+	new_codec->channels = codec->channels;
+
+	for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
+		FsCodecParameter *param = (FsCodecParameter*)iter->data;
+		purple_media_codec_add_optional_parameter(new_codec,
+				param->name, param->value);
+	}
+
+	return new_codec;
+}
+
+gchar *
+purple_media_codec_to_string(const PurpleMediaCodec *codec)
+{
+	FsCodec *fscodec = purple_media_codec_to_fs(codec);
+	gchar *str = fs_codec_to_string(fscodec);
+	fs_codec_destroy(fscodec);
+	return str;
+}
+
+static GList *
+purple_media_codec_list_from_fs(GList *codecs)
+{
+	GList *new_list = NULL;
+
+	for (; codecs; codecs = g_list_next(codecs)) {
+		new_list = g_list_prepend(new_list,
+				purple_media_codec_from_fs(
+				codecs->data));
+	}
+
+	new_list = g_list_reverse(new_list);
+	return new_list;
+}
+
+static GList *
+purple_media_codec_list_to_fs(GList *codecs)
+{
+	GList *new_list = NULL;
+
+	for (; codecs; codecs = g_list_next(codecs)) {
+		new_list = g_list_prepend(new_list,
+				purple_media_codec_to_fs(
+				codecs->data));
+	}
+
+	new_list = g_list_reverse(new_list);
+	return new_list;
+}
+
+GList *
+purple_media_codec_list_copy(GList *codecs)
+{
+	GList *new_list = NULL;
+
+	for (; codecs; codecs = g_list_next(codecs)) {
+		new_list = g_list_prepend(new_list, g_boxed_copy(
+				PURPLE_TYPE_MEDIA_CODEC,
+				codecs->data));
+	}
+
+	new_list = g_list_reverse(new_list);
+	return new_list;
+}
+
+void
+purple_media_codec_list_free(GList *codecs)
+{
+	for (; codecs; codecs =
+			g_list_delete_link(codecs, codecs)) {
+		g_boxed_free(PURPLE_TYPE_MEDIA_CODEC,
+				codecs->data);
+	}
+}
+
+GType
+purple_media_codec_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		type = g_boxed_type_register_static("PurpleMediaCodec",
+				(GBoxedCopyFunc)purple_media_codec_copy,
+				(GBoxedFreeFunc)purple_media_codec_free);
+	}
+	return type;
+}
+
+
+
+PurpleMediaSessionType
+purple_media_get_overall_type(PurpleMedia *media)
+{
+	GList *values;
+	PurpleMediaSessionType type = PURPLE_MEDIA_NONE;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), type);
+
+	values = g_hash_table_get_values(media->priv->sessions);
+
+	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)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	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)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	return (FsParticipant*) (media->priv->participants) ?
+			g_hash_table_lookup(media->priv->participants, name) : NULL;
+}
+
+static PurpleMediaStream*
+purple_media_get_stream(PurpleMedia *media, const gchar *session, const gchar *participant)
+{
+	GList *streams;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	streams = media->priv->streams;
+
+	for (; streams; streams = g_list_next(streams)) {
+		PurpleMediaStream *stream = streams->data;
+		if (!strcmp(stream->session->id, session) &&
+				!strcmp(stream->participant, participant))
+			return stream;
+	}
+
+	return NULL;
+}
+
+static GList *
+purple_media_get_streams(PurpleMedia *media, const gchar *session,
+		const gchar *participant)
+{
+	GList *streams;
+	GList *ret = NULL;
+	
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	streams = media->priv->streams;
+
+	for (; streams; streams = g_list_next(streams)) {
+		PurpleMediaStream *stream = streams->data;
+		if ((session == NULL ||
+				!strcmp(stream->session->id, session)) &&
+				(participant == NULL ||
+				!strcmp(stream->participant, participant)))
+			ret = g_list_append(ret, stream);
+	}
+
+	return ret;
+}
+
+static void
+purple_media_add_session(PurpleMedia *media, PurpleMediaSession *session)
+{
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	g_return_if_fail(session != NULL);
+
+	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)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+	return g_hash_table_remove(media->priv->sessions, session->id);
+}
+
+static FsParticipant *
+purple_media_add_participant(PurpleMedia *media, const gchar *name)
+{
+	FsParticipant *participant;
+	GError *err = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	participant = purple_media_get_participant(media, name);
+
+	if (participant)
+		return participant;
+
+	participant = fs_conference_new_participant(media->priv->conference,
+						    (gchar*)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_full(g_str_hash,
+				g_str_equal, g_free, NULL);
+	}
+
+	g_hash_table_insert(media->priv->participants, g_strdup(name), participant);
+
+	return participant;
+}
+
+static PurpleMediaStream *
+purple_media_insert_stream(PurpleMediaSession *session, const gchar *name, FsStream *stream)
+{
+	PurpleMediaStream *media_stream;
+	
+	g_return_val_if_fail(session != NULL, NULL);
+
+	media_stream = g_new0(PurpleMediaStream, 1);
+	media_stream->stream = stream;
+	media_stream->participant = g_strdup(name);
+	media_stream->session = session;
+
+	session->media->priv->streams =
+			g_list_append(session->media->priv->streams, media_stream);
+
+	return media_stream;
+}
+
+static void
+purple_media_insert_local_candidate(PurpleMediaSession *session, const gchar *name,
+				     FsCandidate *candidate)
+{
+	PurpleMediaStream *stream;
+
+	g_return_if_fail(session != NULL);
+
+	stream = purple_media_get_stream(session->media, session->id, name);
+	stream->local_candidates = g_list_append(stream->local_candidates, candidate);
+}
+
+GList *
+purple_media_get_session_names(PurpleMedia *media)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	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_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	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_SEND_VIDEO && video_src)
+			*video_src = session->src;
+	}
+
+	values = media->priv->streams;
+	for (; values; values = g_list_next(values)) {
+		PurpleMediaStream *stream = (PurpleMediaStream*)values->data;
+
+		if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO && audio_sink)
+			*audio_sink = stream->sink;
+		if (stream->session->type & PURPLE_MEDIA_RECV_VIDEO && video_sink)
+			*video_sink = stream->sink;
+	}
+}
+
+void 
+purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src)
+{
+	PurpleMediaSession *session;
+	GstPad *sinkpad;
+	GstPad *srcpad;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	session = purple_media_get_session(media, sess_id);
+
+	if (session == NULL) {
+		purple_debug_warning("media", "purple_media_set_src: trying"
+				" to set src on non-existent session\n");
+		return;
+	}
+
+	if (session->src)
+		gst_object_unref(session->src);
+	session->src = src;
+	gst_bin_add(GST_BIN(session->media->priv->confbin),
+		    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,
+		const gchar *participant, GstElement *sink)
+{
+	PurpleMediaStream *stream;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	stream = purple_media_get_stream(media, sess_id, participant);
+
+	if (stream == NULL) {
+		purple_debug_warning("media", "purple_media_set_sink: trying"
+				" to set sink on non-existent stream\n");
+		return;
+	}
+
+	if (stream->sink)
+		gst_object_unref(stream->sink);
+	stream->sink = sink;
+	gst_bin_add(GST_BIN(stream->session->media->priv->confbin),
+		    stream->sink);
+}
+
+GstElement *
+purple_media_get_src(PurpleMedia *media, const gchar *sess_id)
+{
+	PurpleMediaSession *session;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	session = purple_media_get_session(media, sess_id);
+	return (session != NULL) ? session->src : NULL;
+}
+
+GstElement *
+purple_media_get_sink(PurpleMedia *media, const gchar *sess_id, const gchar *participant)
+{
+	PurpleMediaStream *stream;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	stream = purple_media_get_stream(media, sess_id, participant);
+	return (stream != NULL) ? stream->sink : NULL;
+}
+
+static PurpleMediaSession *
+purple_media_session_from_fs_stream(PurpleMedia *media, FsStream *stream)
+{
+	FsSession *fssession;
+	GList *values;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	g_return_val_if_fail(FS_IS_STREAM(stream), NULL);
+
+	g_object_get(stream, "session", &fssession, NULL);
+
+	values = g_hash_table_get_values(media->priv->sessions);
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		PurpleMediaSession *session = values->data;
+
+		if (session->session == fssession) {
+			g_list_free(values);
+			g_object_unref(fssession);
+			return session;
+		}
+	}
+
+	g_object_unref(fssession);
+	return NULL;
+}
+
+/* This could also emit when participants are ready */
+static void
+purple_media_emit_ready(PurpleMedia *media, PurpleMediaSession *session, const gchar *participant)
+{
+	GList *sessions;
+	gboolean conf_ready = TRUE;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	if ((session != NULL) && ((media->priv->initiator == FALSE &&
+			session->accepted == FALSE) ||
+			(purple_media_codecs_ready(media, session->id) == FALSE)))
+		return;
+
+	sessions = g_hash_table_get_values(media->priv->sessions);
+
+	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+		PurpleMediaSession *session_data = sessions->data;
+		GList *streams = purple_media_get_streams(media,
+				session_data->id, NULL);
+		gboolean session_ready = TRUE;
+
+		if ((media->priv->initiator == FALSE &&
+				session_data->accepted == FALSE) ||
+				(purple_media_codecs_ready(
+				media, session_data->id) == FALSE))
+			conf_ready = FALSE;
+
+		for (; streams; streams = g_list_delete_link(streams, streams)) {
+			PurpleMediaStream *stream = streams->data;
+			if (stream->candidates_prepared == FALSE) {
+				session_ready = FALSE;
+				conf_ready = FALSE;
+			} else if (session_data == session)
+				g_signal_emit(media, purple_media_signals[READY_NEW],
+						0, session_data->id, stream->participant);
+		}
+
+		if (session_ready == TRUE &&
+				(session == session_data || session == NULL))
+			g_signal_emit(media, purple_media_signals[READY_NEW],
+					0, session_data->id, NULL);
+	}
+
+	if (conf_ready == TRUE) {
+		g_signal_emit(media, purple_media_signals[READY_NEW],
+				0, NULL, NULL);
+	}
+}
+
+static gboolean
+media_bus_call(GstBus *bus, GstMessage *msg, gpointer dummy)
+{
+	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: {
+			PurpleMedia *media = NULL;
+			if (FS_IS_CONFERENCE(GST_MESSAGE_SRC(msg))) {
+				GList *iter = purple_media_manager_get_media(
+						purple_media_manager_get());
+				for (; iter; iter = g_list_next(iter)) {
+					if (PURPLE_MEDIA(iter->data)->priv->conference
+							== FS_CONFERENCE(GST_MESSAGE_SRC(msg))) {
+						media = iter->data;
+						break;
+					}
+				}
+			}
+
+			if (gst_structure_has_name(msg->structure, "farsight-error")) {
+				FsError error_no;
+				gst_structure_get_enum(msg->structure, "error-no",
+						FS_TYPE_ERROR, (gint*)&error_no);
+				/*
+				 * Unknown CName is only a problem for the
+				 * multicast transmitter which isn't used.
+				 */
+				if (error_no != FS_ERROR_UNKNOWN_CNAME)
+					purple_debug_error("media", "farsight-error: %i: %s\n", error_no,
+						  	gst_structure_get_string(msg->structure, "error-msg"));
+			} else if (gst_structure_has_name(msg->structure,
+					"farsight-new-local-candidate")) {
+				FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream"));
+				FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "candidate"));
+				PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream);
+				purple_media_new_local_candidate_cb(stream, local_candidate, session);
+			} else if (gst_structure_has_name(msg->structure,
+					"farsight-local-candidates-prepared")) {
+				FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream"));
+				PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream);
+				purple_media_candidates_prepared_cb(stream, session);
+			} else if (gst_structure_has_name(msg->structure,
+					"farsight-new-active-candidate-pair")) {
+				FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream"));
+				FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "local-candidate"));
+				FsCandidate *remote_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "remote-candidate"));
+				PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream);
+				purple_media_candidate_pair_established_cb(stream, local_candidate, remote_candidate, session);
+			} else if (gst_structure_has_name(msg->structure,
+					"farsight-recv-codecs-changed")) {
+				GList *codecs = g_value_get_boxed(gst_structure_get_value(msg->structure, "codecs"));
+				FsCodec *codec = codecs->data;
+				purple_debug_info("media", "farsight-recv-codecs-changed: %s\n", codec->encoding_name);
+				
+			} else if (gst_structure_has_name(msg->structure,
+					"farsight-component-state-changed")) {
+				
+			} else if (gst_structure_has_name(msg->structure,
+					"farsight-send-codec-changed")) {
+				
+			} else if (gst_structure_has_name(msg->structure,
+					"farsight-codecs-changed")) {
+				GList *sessions = g_hash_table_get_values(PURPLE_MEDIA(media)->priv->sessions);
+				FsSession *fssession = g_value_get_object(gst_structure_get_value(msg->structure, "session"));
+				for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+					PurpleMediaSession *session = sessions->data;
+					if (session->session == fssession) {
+						gboolean ready;
+						gchar *session_id;
+
+						g_object_get(session->session, "codecs-ready", &ready, NULL);
+						if (session->codecs_ready == FALSE && ready == TRUE) {
+							session->codecs_ready = ready;
+							purple_media_emit_ready(media, session, NULL);
+						}
+
+						session_id = g_strdup(session->id);
+						g_signal_emit(media, purple_media_signals[CODECS_CHANGED], 0, session_id);
+						g_free(session_id);
+						g_list_free(sessions);
+						break;
+					}
+				}
+			}
+			break;
+		}
+		default:
+			break;
+	}
+
+	return TRUE;
+}
+
+GstElement *
+purple_media_get_pipeline(PurpleMedia *media)
+{
+	static GstElement *pipeline = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	if (!pipeline) {
+		GstBus *bus;
+		media->priv->pipeline = pipeline = gst_pipeline_new(NULL);
+		bus = gst_pipeline_get_bus(GST_PIPELINE(media->priv->pipeline));
+		gst_bus_add_signal_watch(GST_BUS(bus));
+		g_signal_connect(G_OBJECT(bus), "message",
+				G_CALLBACK(media_bus_call), NULL);
+		gst_bus_set_sync_handler(bus, gst_bus_sync_signal_handler, NULL);
+		gst_object_unref(bus);
+		gst_element_set_state(pipeline, GST_STATE_PLAYING);
+	}
+
+	media->priv->pipeline = pipeline;
+	return media->priv->pipeline;
+}
+
+void
+purple_media_error(PurpleMedia *media, const gchar *error, ...)
+{
+	va_list args;
+	gchar *message;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	va_start(args, error);
+	message = g_strdup_vprintf(error, args);
+	va_end(args);
+
+	purple_debug_error("media", "%s\n", message);
+	g_signal_emit(media, purple_media_signals[ERROR], 0, message);
+
+	g_free(message);
+}
+
+void
+purple_media_accept(PurpleMedia *media)
+{
+	GList *sessions;
+	GList *streams;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	sessions = g_hash_table_get_values(media->priv->sessions);
+
+	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+		PurpleMediaSession *session = sessions->data;
+		session->accepted = TRUE;
+
+		if (media->priv->initiator == FALSE)
+			purple_media_emit_ready(media, session, NULL);
+	}
+
+	g_signal_emit(media, purple_media_signals[ACCEPTED],
+			0, NULL, NULL);
+	streams = media->priv->streams;
+
+	for (; streams; streams = g_list_next(streams)) {
+		PurpleMediaStream *stream = streams->data;
+		g_object_set(G_OBJECT(stream->stream), "direction",
+				purple_media_to_fs_stream_direction(
+				stream->session->type), NULL);
+	}
+}
+
+void
+purple_media_hangup(PurpleMedia *media)
+{
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+			0, PURPLE_MEDIA_STATE_CHANGED_HANGUP,
+			NULL, NULL);
+	purple_media_end(media, NULL, NULL);
+}
+
+void
+purple_media_reject(PurpleMedia *media)
+{
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+			0, PURPLE_MEDIA_STATE_CHANGED_REJECTED,
+			NULL, NULL);
+	purple_media_end(media, NULL, NULL);
+}
+
+void
+purple_media_end(PurpleMedia *media,
+		const gchar *session_id, const gchar *participant)
+{
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	if (session_id == NULL && participant == NULL) {
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_CHANGED_END,
+				NULL, NULL);
+		g_object_unref(media);
+	}
+}
+
+GList*
+purple_media_get_devices(const gchar *plugin)
+{
+	GObjectClass *klass;
+	GstPropertyProbe *probe;
+	const GParamSpec *pspec;
+	GstElement *element = gst_element_factory_make(plugin, NULL);
+	GstElementFactory *factory;
+	const gchar *longname = NULL;
+	GList *ret = NULL;
+
+	if (element == NULL)
+		return NULL;
+
+	factory = gst_element_get_factory(element);
+
+	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);
+				
+				ret = g_list_append(ret, g_value_dup_string(device));
+
+				g_object_set(G_OBJECT(element), "device",
+						g_value_get_string(device), NULL);
+				g_object_get(G_OBJECT(element), "device-name", &name, NULL);
+				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);
+			}
+			g_value_array_free(array);
+		}
+
+		/* Restore autoprobe[-fps] to TRUE. */
+		if (g_object_class_find_property (klass, "autoprobe")) {
+			g_object_set (G_OBJECT (element), "autoprobe", TRUE, NULL);
+			if (g_object_class_find_property (klass, "autoprobe-fps")) {
+				g_object_set (G_OBJECT (element), "autoprobe-fps", TRUE, NULL);
+			}
+		}
+	}
+
+	gst_object_unref(element);
+	return ret;
+}
+
+gchar *
+purple_media_element_get_device(GstElement *element)
+{
+	gchar *device;
+	g_object_get(G_OBJECT(element), "device", &device, NULL);
+	return device;
+}
+
+void
+purple_media_audio_init_src(GstElement **sendbin, GstElement **sendlevel)
+{
+	GstElement *src;
+	GstElement *volume;
+	GstPad *pad;
+	GstPad *ghost;
+	const gchar *audio_device = purple_prefs_get_string("/purple/media/audio/device");
+	double input_volume = purple_prefs_get_int("/purple/media/audio/volume/input")/10.0;
+
+	g_return_if_fail(sendbin != NULL && sendlevel != NULL);
+
+	*sendbin = gst_bin_new("purplesendaudiobin");
+	src = gst_element_factory_make("alsasrc", "asrc");
+	volume = gst_element_factory_make("volume", "purpleaudioinputvolume");
+	g_object_set(volume, "volume", input_volume, NULL);
+	*sendlevel = gst_element_factory_make("level", "sendlevel");
+	gst_bin_add_many(GST_BIN(*sendbin), src, volume, *sendlevel, NULL);
+	gst_element_link(src, volume);
+	gst_element_link(volume, *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);
+
+	if (audio_device != NULL && strcmp(audio_device, ""))
+		g_object_set(G_OBJECT(src), "device", audio_device, NULL);
+}
+
+void
+purple_media_video_init_src(GstElement **sendbin)
+{
+	GstElement *src, *tee, *queue;
+	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");
+
+	g_return_if_fail(sendbin != NULL);
+
+	*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", "purplevideosrctee");
+	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_static_pad(queue, "src");
+	ghost = gst_ghost_pad_new("ghostsrc", pad);
+	gst_object_unref(pad);
+	gst_element_add_pad(*sendbin, ghost);
+
+	if (video_device != NULL && strcmp(video_device, ""))
+		g_object_set(G_OBJECT(src), "device", video_device, NULL);
+}
+
+void
+purple_media_audio_init_recv(GstElement **recvbin, GstElement **recvlevel)
+{
+	GstElement *sink, *volume;
+	GstPad *pad, *ghost;
+	double output_volume = purple_prefs_get_int(
+			"/purple/media/audio/volume/output")/10.0;
+
+	g_return_if_fail(recvbin != NULL && recvlevel != NULL);
+
+	*recvbin = gst_bin_new("pidginrecvaudiobin");
+	sink = gst_element_factory_make("alsasink", "asink");
+	g_object_set(G_OBJECT(sink), "sync", FALSE, NULL);
+	volume = gst_element_factory_make("volume", "purpleaudiooutputvolume");
+	g_object_set(volume, "volume", output_volume, NULL);
+	*recvlevel = gst_element_factory_make("level", "recvlevel");
+	gst_bin_add_many(GST_BIN(*recvbin), sink, volume, *recvlevel, NULL);
+	gst_element_link(*recvlevel, sink);
+	gst_element_link(volume, *recvlevel);
+	pad = gst_element_get_pad(volume, "sink");
+	ghost = gst_ghost_pad_new("ghostsink", pad);
+	gst_element_add_pad(*recvbin, ghost);
+	g_object_set(G_OBJECT(*recvlevel), "message", TRUE, NULL);
+}
+
+void
+purple_media_video_init_recv(GstElement **recvbin)
+{
+	GstElement *sink;
+	GstPad *pad, *ghost;
+
+	g_return_if_fail(recvbin != NULL);
+
+	*recvbin = gst_bin_new("fakebin");
+	sink = gst_element_factory_make("fakesink", NULL);
+	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);
+}
+
+static void
+purple_media_new_local_candidate_cb(FsStream *stream,
+				    FsCandidate *local_candidate,
+				    PurpleMediaSession *session)
+{
+	gchar *name;
+	FsParticipant *participant;
+	PurpleMediaCandidate *candidate;
+
+	g_return_if_fail(FS_IS_STREAM(stream));
+	g_return_if_fail(session != NULL);
+
+	purple_debug_info("media", "got new local candidate: %s\n", local_candidate->foundation);
+	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));
+
+	candidate = purple_media_candidate_from_fs(local_candidate);
+	g_signal_emit(session->media, purple_media_signals[NEW_CANDIDATE],
+		      0, session->id, name, candidate);
+	purple_media_candidate_free(candidate);
+
+	g_free(name);
+}
+
+static void
+purple_media_candidates_prepared_cb(FsStream *stream, PurpleMediaSession *session)
+{
+	gchar *name;
+	FsParticipant *participant;
+	PurpleMediaStream *stream_data;
+
+	g_return_if_fail(FS_IS_STREAM(stream));
+	g_return_if_fail(session != NULL);
+
+	g_object_get(stream, "participant", &participant, NULL);
+	g_object_get(participant, "cname", &name, NULL);
+	g_object_unref(participant);
+
+	stream_data = purple_media_get_stream(session->media, session->id, name);
+	stream_data->candidates_prepared = TRUE;
+
+	purple_media_emit_ready(session->media, session, name);
+	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 *fsstream,
+					   FsCandidate *native_candidate,
+					   FsCandidate *remote_candidate,
+					   PurpleMediaSession *session)
+{
+	gchar *name;
+	FsParticipant *participant;
+	PurpleMediaStream *stream;
+
+	g_return_if_fail(FS_IS_STREAM(fsstream));
+	g_return_if_fail(session != NULL);
+
+	g_object_get(fsstream, "participant", &participant, NULL);
+	g_object_get(participant, "cname", &name, NULL);
+	g_object_unref(participant);
+
+	stream = purple_media_get_stream(session->media, session->id, name);
+
+	stream->local_candidate = fs_candidate_copy(native_candidate);
+	stream->remote_candidate = fs_candidate_copy(remote_candidate);
+
+	purple_debug_info("media", "candidate pair established\n");
+}
+
+static gboolean
+purple_media_connected_cb(PurpleMediaStream *stream)
+{
+	g_return_val_if_fail(stream != NULL, FALSE);
+	g_signal_emit(stream->session->media,
+			purple_media_signals[STATE_CHANGED],
+			0, PURPLE_MEDIA_STATE_CHANGED_CONNECTED,
+			stream->session->id, stream->participant);
+	return FALSE;
+}
+
+static void
+purple_media_src_pad_added_cb(FsStream *fsstream, GstPad *srcpad,
+			      FsCodec *codec, PurpleMediaStream *stream)
+{
+	PurpleMediaSessionType type = purple_media_from_fs(codec->media_type, FS_DIRECTION_RECV);
+	GstPad *sinkpad = NULL;
+
+	g_return_if_fail(FS_IS_STREAM(fsstream));
+	g_return_if_fail(stream != NULL);
+
+	if (stream->sink == NULL)
+		stream->sink = purple_media_manager_get_element(
+			purple_media_manager_get(), type);
+
+	gst_bin_add(GST_BIN(stream->session->media->priv->confbin),
+		    stream->sink);
+	sinkpad = gst_element_get_static_pad(stream->sink, "ghostsink");
+	gst_pad_link(srcpad, sinkpad);
+	gst_element_set_state(stream->sink, GST_STATE_PLAYING);
+
+	g_timeout_add_full(G_PRIORITY_HIGH, 0,
+			(GSourceFunc)purple_media_connected_cb, stream, NULL);
+}
+
+static gboolean
+purple_media_add_stream_internal(PurpleMedia *media, const gchar *sess_id,
+				 const gchar *who, FsMediaType type,
+				 FsStreamDirection type_direction,
+				 const gchar *transmitter,
+				 guint num_params, GParameter *params)
+{
+	PurpleMediaSession *session;
+	FsParticipant *participant = NULL;
+	PurpleMediaStream *stream = NULL;
+	FsStreamDirection *direction = NULL;
+	PurpleMediaSessionType session_type;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	session = purple_media_get_session(media, sess_id);
+
+	if (!session) {
+		GError *err = NULL;
+		GList *codec_conf = NULL;
+		gchar *filename = NULL;
+
+		session = g_new0(PurpleMediaSession, 1);
+
+		session->session = fs_conference_new_session(media->priv->conference, type, &err);
+
+		if (err != NULL) {
+			purple_media_error(media, "Error creating session: %s\n", err->message);
+			g_error_free(err);
+			g_free(session);
+			return FALSE;
+		}
+
+	/* 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
+
+		filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
+		codec_conf = fs_codec_list_from_keyfile(filename, &err);
+		g_free(filename);
+
+		if (err != NULL) {
+			purple_debug_error("media", "Error reading codec configuration file: %s\n", err->message);
+			g_error_free(err);
+		}
+
+		fs_session_set_codec_preferences(session->session, codec_conf, NULL);
+
+		/*
+		 * Removes a 5-7 second delay before
+		 * receiving the src-pad-added signal.
+		 * Only works for non-multicast FsRtpSessions.
+		 */
+		if (!strcmp(transmitter, "nice") || !strcmp(transmitter, "rawudp"))
+			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);
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_CHANGED_NEW,
+				session->id, NULL);
+
+		session_type = purple_media_from_fs(type, FS_DIRECTION_SEND);
+		purple_media_set_src(media, session->id,
+				purple_media_manager_get_element(
+				purple_media_manager_get(), session_type));
+		gst_element_set_state(session->src, GST_STATE_PLAYING);
+	}
+
+	if (!(participant = purple_media_add_participant(media, who))) {
+		purple_media_remove_session(media, session);
+		g_free(session);
+		return FALSE;
+	} else {
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_CHANGED_NEW,
+				NULL, who);
+	}
+
+	stream = purple_media_get_stream(media, sess_id, who);
+
+	if (!stream) {
+		GError *err = NULL;
+		FsStream *fsstream = NULL;
+		const gchar *stun_ip = purple_network_get_stun_ip();
+		const gchar *turn_ip = purple_network_get_turn_ip();
+
+		if (stun_ip || turn_ip) {
+			guint new_num_params = 
+				stun_ip && turn_ip ? num_params + 2 : num_params + 1;
+			guint next_param_index = num_params;
+			GParameter *param = g_new0(GParameter, new_num_params);
+			memcpy(param, params, sizeof(GParameter) * num_params);
+
+			if (stun_ip) {
+				purple_debug_info("media", 
+					"setting property stun-ip on new stream: %s\n", stun_ip);
+
+				param[next_param_index].name = "stun-ip";
+				g_value_init(&param[next_param_index].value, G_TYPE_STRING);
+				g_value_set_string(&param[next_param_index].value, stun_ip);
+				next_param_index++;
+			}
+
+			if (turn_ip) {
+				GValueArray *relay_info = g_value_array_new(0);
+				GValue value;
+				gint turn_port = 
+					purple_prefs_get_int("/purple/network/turn_port");
+				const gchar *username =
+					purple_prefs_get_string("/purple/network/turn_username");
+				const gchar *password =
+					purple_prefs_get_string("/purple/network/turn_password");
+				GstStructure *turn_setup = gst_structure_new("relay-info",
+					"ip", G_TYPE_STRING, turn_ip, 
+					"port", G_TYPE_UINT, turn_port,
+					"username", G_TYPE_STRING, username,
+					"password", G_TYPE_STRING, password,
+					NULL);
+
+				if (turn_setup) {
+					memset(&value, 0, sizeof(GValue));
+					g_value_init(&value, GST_TYPE_STRUCTURE);
+					gst_value_set_structure(&value, turn_setup);
+					relay_info = g_value_array_append(relay_info, &value);
+					gst_structure_free(turn_setup);
+
+					purple_debug_info("media",
+						"setting property relay-info on new stream\n");
+					param[next_param_index].name = "relay-info";
+					g_value_init(&param[next_param_index].value, 
+						G_TYPE_VALUE_ARRAY);
+					g_value_set_boxed(&param[next_param_index].value,
+						relay_info);
+					g_value_array_free(relay_info);
+				} else {
+					purple_debug_error("media", "Error relay info");
+					g_object_unref(participant);
+					g_hash_table_remove(media->priv->participants, who);
+					purple_media_remove_session(media, session);
+					g_free(session);
+					return FALSE;
+				}
+			}
+
+			fsstream = fs_session_new_stream(session->session,
+					participant, type_direction &
+					FS_DIRECTION_RECV, transmitter,
+					new_num_params, param, &err);
+			g_free(param);
+		} else {
+			fsstream = fs_session_new_stream(session->session,
+					participant, type_direction &
+					FS_DIRECTION_RECV, transmitter,
+					num_params, params, &err);
+		}
+
+		if (err) {
+			purple_debug_error("media", "Error creating stream: %s\n",
+					   err->message);
+			g_error_free(err);
+			g_object_unref(participant);
+			g_hash_table_remove(media->priv->participants, who);
+			purple_media_remove_session(media, session);
+			g_free(session);
+			return FALSE;
+		}
+
+		stream = purple_media_insert_stream(session, who, fsstream);
+
+		/* callback for source pad added (new stream source ready) */
+		g_signal_connect(G_OBJECT(fsstream),
+				 "src-pad-added", G_CALLBACK(purple_media_src_pad_added_cb), stream);
+
+		g_signal_emit(media, purple_media_signals[STATE_CHANGED],
+				0, PURPLE_MEDIA_STATE_CHANGED_NEW,
+				session->id, who);
+	} else if (*direction != type_direction) {	
+		/* change direction */
+		g_object_set(stream->stream, "direction", type_direction, NULL);
+	}
+
+	return TRUE;
+}
+
+gboolean
+purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who,
+			PurpleMediaSessionType type,
+			const gchar *transmitter,
+			guint num_params, GParameter *params)
+{
+	FsStreamDirection type_direction;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	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, num_params, params)) {
+			return FALSE;
+		}
+	}
+	else 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, num_params, params)) {
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+void
+purple_media_remove_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who)
+{
+	/* Add state-changed end emits in here when this is implemented */
+}
+
+PurpleMediaSessionType
+purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id)
+{
+	PurpleMediaSession *session;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), PURPLE_MEDIA_NONE);
+	session = purple_media_get_session(media, sess_id);
+	return session->type;
+}
+/* XXX: Should wait until codecs-ready is TRUE before using this function */
+GList *
+purple_media_get_codecs(PurpleMedia *media, const gchar *sess_id)
+{
+	GList *fscodecs;
+	GList *codecs;
+	PurpleMediaSession *session;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+
+	session = purple_media_get_session(media, sess_id);
+
+	if (session == NULL)
+		return NULL;
+
+	g_object_get(G_OBJECT(session->session),
+		     "codecs", &fscodecs, NULL);
+	codecs = purple_media_codec_list_from_fs(fscodecs);
+	fs_codec_list_destroy(fscodecs);
+	return codecs;
+}
+
+GList *
+purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+{
+	PurpleMediaStream *stream;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	stream = purple_media_get_stream(media, sess_id, name);
+	return purple_media_candidate_list_from_fs(stream->local_candidates);
+}
+
+void
+purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id,
+				   const gchar *name, GList *remote_candidates)
+{
+	PurpleMediaStream *stream;
+	GError *err = NULL;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+	stream = purple_media_get_stream(media, sess_id, name);
+
+	stream->remote_candidates = g_list_concat(stream->remote_candidates,
+			purple_media_candidate_list_to_fs(remote_candidates));
+
+	fs_stream_set_remote_candidates(stream->stream,
+			stream->remote_candidates, &err);
+
+	if (err) {
+		purple_debug_error("media", "Error adding remote"
+				" candidates: %s\n", err->message);
+		g_error_free(err);
+	}
+}
+
+PurpleMediaCandidate *
+purple_media_get_local_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+{
+	PurpleMediaStream *stream;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	stream = purple_media_get_stream(media, sess_id, name);
+	return purple_media_candidate_from_fs(stream->local_candidate);
+}
+
+PurpleMediaCandidate *
+purple_media_get_remote_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+{
+	PurpleMediaStream *stream;
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
+	stream = purple_media_get_stream(media, sess_id, name);
+	return purple_media_candidate_from_fs(stream->remote_candidate);
+}
+
+gboolean
+purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id, const gchar *name, GList *codecs)
+{
+	PurpleMediaStream *stream;
+	FsStream *fsstream;
+	GList *fscodecs;
+	GError *err = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+	stream = purple_media_get_stream(media, sess_id, name);
+
+	if (stream == NULL)
+		return FALSE;
+
+	fsstream = stream->stream;
+	fscodecs = purple_media_codec_list_to_fs(codecs);
+	fs_stream_set_remote_codecs(fsstream, fscodecs, &err);
+	fs_codec_list_destroy(fscodecs);
+
+	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;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	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;
+}
+
+gboolean
+purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, PurpleMediaCodec *codec)
+{
+	PurpleMediaSession *session;
+	FsCodec *fscodec;
+	GError *err = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	session = purple_media_get_session(media, sess_id);
+
+	if (session != NULL)
+		return FALSE;
+
+	fscodec = purple_media_codec_to_fs(codec);
+	fs_session_set_send_codec(session->session, fscodec, &err);
+	fs_codec_destroy(fscodec);
+
+	if (err) {
+		purple_debug_error("media", "Error setting send codec\n");
+		g_error_free(err);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+gboolean
+purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id)
+{
+	PurpleMediaSession *session;
+	gboolean ret;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	session = purple_media_get_session(media, sess_id);
+
+	if (session == NULL)
+		return FALSE;
+
+	g_object_get(session->session, "codecs-ready", &ret, NULL);
+	return ret;
+}
+
+gboolean
+purple_media_accepted(PurpleMedia *media, const gchar *sess_id,
+		const gchar *participant)
+{
+	PurpleMediaSession *session;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	session = purple_media_get_session(media, sess_id);
+
+	if (session == NULL)
+		return FALSE;
+
+	return session->accepted;
+}
+
+void purple_media_mute(PurpleMedia *media, gboolean active)
+{
+	GList *sessions;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	sessions = g_hash_table_get_values(media->priv->sessions);
+	purple_debug_info("media", "Turning mute %s\n", active ? "on" : "off");
+
+	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+		PurpleMediaSession *session = sessions->data;
+		if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
+			GstElement *volume = gst_bin_get_by_name(
+					GST_BIN(session->src),
+					"purpleaudioinputvolume");
+			g_object_set(volume, "mute", active, NULL);
+		}
+	}
+}
+
+void purple_media_set_input_volume(PurpleMedia *media,
+		const gchar *session_id, double level)
+{
+	GList *sessions;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	if (session_id == NULL)
+		sessions = g_hash_table_get_values(media->priv->sessions);
+	else
+		sessions = g_list_append(NULL,
+				purple_media_get_session(media, session_id));
+
+	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+		PurpleMediaSession *session = sessions->data;
+
+		if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
+			GstElement *volume = gst_bin_get_by_name(
+					GST_BIN(session->src),
+					"purpleaudioinputvolume");
+			g_object_set(volume, "volume", level, NULL);
+		}
+	}
+}
+
+void purple_media_set_output_volume(PurpleMedia *media,
+		const gchar *session_id, const gchar *participant,
+		double level)
+{
+	GList *streams;
+
+	g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+	streams = purple_media_get_streams(media,
+			session_id, participant);
+
+	for (; streams; streams = g_list_delete_link(streams, streams)) {
+		PurpleMediaStream *stream = streams->data;
+
+		if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO) {
+			GstElement *volume = gst_bin_get_by_name(
+					GST_BIN(stream->sink),
+					"purpleaudiooutputvolume");
+			g_object_set(volume, "volume", level, NULL);
+		}
+	}
+}
+
+typedef struct
+{
+	gchar *name;
+	gulong window_id;
+	gulong handler_id;
+} PurpleMediaXOverlayData;
+
+static void
+window_id_cb(GstBus *bus, GstMessage *msg, PurpleMediaXOverlayData *data)
+{
+	gchar *name;
+
+	if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT ||
+			!gst_structure_has_name(msg->structure,
+			"prepare-xwindow-id"))
+		return;
+
+	name = gst_object_get_name(GST_MESSAGE_SRC(msg));
+
+	if (!strncmp(name, data->name, strlen(data->name))) {
+		g_signal_handler_disconnect(bus, data->handler_id);
+
+		gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(
+				GST_MESSAGE_SRC(msg)), data->window_id);
+
+		g_free(data->name);
+		g_free(data);
+	}
+
+	g_free(name);
+		
+	return;
+}
+
+gboolean
+purple_media_set_output_window(PurpleMedia *media, const gchar *session_id,
+		const gchar *participant, gulong window_id)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	if (session_id != NULL && participant == NULL) {
+
+		PurpleMediaSession *session;
+		session = purple_media_get_session(media, session_id);
+
+		if (session == NULL)
+			return FALSE;
+
+		session->window_id = window_id;
+
+		if (session->sink == NULL) {
+			PurpleMediaXOverlayData *data;
+			GstBus *bus;
+			GstElement *tee, *bin, *queue, *sink;
+			GstPad *request_pad, *sinkpad, *ghostpad;
+			gchar *name;
+
+			/* Create sink */
+			tee = gst_bin_get_by_name(GST_BIN(session->src),
+					"purplevideosrctee");
+			bin = gst_bin_new(NULL);
+			gst_bin_add(GST_BIN(GST_ELEMENT_PARENT(tee)), bin);
+
+			queue = gst_element_factory_make("queue", NULL);
+			name = g_strdup_printf(
+					"session-sink_%s", session_id);
+			sink = gst_element_factory_make(
+					"autovideosink", name);
+
+			gst_bin_add_many(GST_BIN(bin), queue, sink, NULL);
+			gst_element_link(queue, sink);
+
+			sinkpad = gst_element_get_static_pad(queue, "sink");
+			ghostpad = gst_ghost_pad_new("ghostsink", sinkpad);
+			gst_object_unref(sinkpad);
+			gst_element_add_pad(bin, ghostpad);
+
+			/* Connect callback for prepared-xwindow-id signal */
+			data = g_new0(PurpleMediaXOverlayData, 1);
+			data->name = name;
+			data->window_id = window_id;
+
+			bus = gst_pipeline_get_bus(GST_PIPELINE(
+					purple_media_get_pipeline(media)));
+			data->handler_id = g_signal_connect(bus,
+					"sync-message::element",
+					G_CALLBACK(window_id_cb), data);
+			gst_object_unref(bus);
+
+			gst_element_set_state(bin, GST_STATE_PLAYING);
+
+			request_pad = gst_element_get_request_pad(
+					tee, "src%d");
+			gst_pad_link(request_pad, ghostpad);
+			gst_object_unref(request_pad);
+
+			session->sink = bin;
+			return TRUE;
+		} else {
+			/* Changing the XOverlay output window */
+			GstElement *xoverlay = gst_bin_get_by_interface(
+					GST_BIN(session->sink),
+					GST_TYPE_X_OVERLAY);
+			if (xoverlay != NULL) {
+				gst_x_overlay_set_xwindow_id(
+					GST_X_OVERLAY(xoverlay),
+					window_id);
+			}
+			return FALSE;
+		}
+	} else if (session_id != NULL && participant != NULL) {
+		PurpleMediaStream *stream = purple_media_get_stream(
+				media, session_id, participant);
+		GstBus *bus;
+		GstElement *bin, *sink;
+		GstPad *pad, *peer = NULL, *ghostpad;
+		PurpleMediaXOverlayData *data;
+		gchar *name;
+
+		if (stream == NULL)
+			return FALSE;
+
+		stream->window_id = window_id;
+
+		if (stream->sink != NULL) {
+			gboolean is_fakebin;
+			name = gst_element_get_name(stream->sink);
+			is_fakebin = !strcmp(name, "fakebin");
+			g_free(name);
+
+			if (is_fakebin) {
+				pad = gst_element_get_static_pad(
+						stream->sink, "ghostsink");
+				peer = gst_pad_get_peer(pad);
+
+				gst_pad_unlink(peer, pad);
+				gst_object_unref(pad);
+				gst_element_set_state(stream->sink,
+						GST_STATE_NULL);
+				gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(
+						stream->sink)), stream->sink);
+			} else {
+				/* Changing the XOverlay output window */
+				GstElement *xoverlay =
+						gst_bin_get_by_interface(
+						GST_BIN(stream->sink),
+						GST_TYPE_X_OVERLAY);
+				if (xoverlay != NULL) {
+					gst_x_overlay_set_xwindow_id(
+						GST_X_OVERLAY(xoverlay),
+						window_id);
+					return TRUE;
+				}
+				return FALSE;
+			}
+		}
+
+		bin = gst_bin_new(NULL);
+
+		if (stream->sink != NULL)
+			gst_bin_add(GST_BIN(GST_ELEMENT_PARENT(
+					stream->sink)), bin);
+
+		name = g_strdup_printf("stream-sink_%s_%s",
+				session_id, participant);
+		sink = gst_element_factory_make("autovideosink", name);
+
+		gst_bin_add(GST_BIN(bin), sink);
+		pad = gst_element_get_static_pad(sink, "sink");
+		ghostpad = gst_ghost_pad_new("ghostsink", pad);
+		gst_element_add_pad(bin, ghostpad);
+
+		/* Connect callback for prepared-xwindow-id signal */
+		data = g_new0(PurpleMediaXOverlayData, 1);
+		data->name = name;
+		data->window_id = window_id;
+
+		bus = gst_pipeline_get_bus(GST_PIPELINE(
+				purple_media_get_pipeline(media)));
+		data->handler_id = g_signal_connect(bus,
+				"sync-message::element",
+				G_CALLBACK(window_id_cb), data);
+		gst_object_unref(bus);
+
+		if (stream->sink != NULL) {
+			gst_element_set_state(bin, GST_STATE_PLAYING);
+			gst_pad_link(peer, ghostpad);
+		}
+
+		stream->sink = bin;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+dummy_block_cb(GstPad *pad, gboolean blocked, gpointer user_data)
+{
+}
+
+gboolean
+purple_media_remove_output_window(PurpleMedia *media, const gchar *session_id,
+		const gchar *participant)
+{	
+	GstElement *parent, *fakesink, *sink;
+	GstPad *pad, *peer;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE);
+
+	if (session_id != NULL && participant == NULL) {
+		PurpleMediaSession *session;
+		GstPad *pad, *peer;
+
+		session = purple_media_get_session(media, session_id);
+
+		if (session == NULL)
+			return FALSE;
+
+		sink = session->sink;
+
+		if (!GST_IS_ELEMENT(sink))
+			return FALSE;
+
+		pad = gst_element_get_static_pad(sink, "ghostsink");
+		peer = gst_pad_get_peer(pad);
+		gst_object_unref(pad);
+
+		gst_element_release_request_pad(GST_ELEMENT_PARENT(peer), peer);
+		gst_object_unref(peer);
+
+		gst_element_set_state(sink, GST_STATE_NULL);
+
+		gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(sink)), sink);
+		session->sink = NULL;
+		return TRUE;	
+	} else if (session_id != NULL && participant != NULL) {
+		PurpleMediaStream *stream;
+		stream = purple_media_get_stream(media,
+				session_id, participant);
+
+		if (stream == NULL)
+			return FALSE;
+
+		sink = stream->sink;
+	} else
+		return FALSE;
+
+	if (!GST_IS_ELEMENT(sink))
+		return FALSE;
+
+	/* Remove sink */
+	parent = GST_ELEMENT(gst_element_get_parent(sink));
+
+	if (parent == NULL) {
+		/* It's not added and therefore not linked */
+		gst_object_unref(sink);
+		return FALSE;
+	}
+
+	pad = gst_element_get_static_pad(sink, "ghostsink");
+
+	if (pad == NULL) {
+		/* It's already a fakesink */
+		gst_object_unref(parent);
+		return FALSE;
+	}
+
+	peer = gst_pad_get_peer(pad);
+	gst_object_unref(pad);
+	gst_pad_set_blocked_async(peer, TRUE, dummy_block_cb, NULL);
+	gst_element_set_state(sink, GST_STATE_NULL);
+	gst_bin_remove(GST_BIN(parent), sink);
+
+	/* Add fakesink */
+	fakesink = gst_element_factory_make("fakesink", NULL);
+	gst_bin_add(GST_BIN(parent), fakesink);
+	gst_element_sync_state_with_parent(fakesink);
+	gst_object_unref(parent);
+	pad = gst_element_get_static_pad(fakesink, "sink");
+	gst_pad_link(peer, pad);
+	gst_object_unref(pad);
+	gst_pad_set_blocked_async(peer, FALSE, dummy_block_cb, NULL);
+	gst_object_unref(peer);
+	return TRUE;
+}
+
+void
+purple_media_remove_output_windows(PurpleMedia *media)
+{
+	GList *iter = media->priv->streams;
+	for (; iter; iter = g_list_next(iter)) {
+		PurpleMediaStream *stream = iter->data;
+		purple_media_remove_output_window(media,
+				stream->session->id, stream->participant);
+	}
+
+	iter = purple_media_get_session_names(media);
+	for (; iter; iter = g_list_delete_link(iter, iter)) {
+		gchar *session_name = iter->data;
+		purple_media_remove_output_window(media, session_name, NULL);
+	}
+}
+
+#endif  /* USE_VV */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,658 @@
+/**
+ * @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 <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_MEDIA            (purple_media_get_type())
+#define PURPLE_TYPE_MEDIA_CANDIDATE  (purple_media_candidate_get_type())
+#define PURPLE_TYPE_MEDIA_CODEC      (purple_media_codec_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))
+
+#define PURPLE_MEDIA_TYPE_STATE_CHANGED	(purple_media_state_changed_get_type())
+
+/** @copydoc _PurpleMedia */
+typedef struct _PurpleMedia PurpleMedia;
+/** @copydoc _PurpleMediaClass */
+typedef struct _PurpleMediaClass PurpleMediaClass;
+/** @copydoc _PurpleMediaPrivate */
+typedef struct _PurpleMediaPrivate PurpleMediaPrivate;
+/** @copydoc _PurpleMediaCandidate */
+typedef struct _PurpleMediaCandidate PurpleMediaCandidate;
+/** @copydoc _PurpleMediaCodec */
+typedef struct _PurpleMediaCodec PurpleMediaCodec;
+/** @copydoc _PurpleMediaCodecParameter */
+typedef struct _PurpleMediaCodecParameter PurpleMediaCodecParameter;
+
+#else
+
+typedef void PurpleMedia;
+
+#endif /* USE_VV */
+
+/** Media session types */
+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
+} PurpleMediaSessionType;
+
+/** Media state-changed types */
+typedef enum {
+	PURPLE_MEDIA_STATE_CHANGED_NEW = 0,
+	PURPLE_MEDIA_STATE_CHANGED_CONNECTED,
+	PURPLE_MEDIA_STATE_CHANGED_REJECTED,	/** Local user rejected the stream. */
+	PURPLE_MEDIA_STATE_CHANGED_HANGUP,	/** Local user hung up the stream */
+	PURPLE_MEDIA_STATE_CHANGED_END,
+} PurpleMediaStateChangedType;
+
+typedef enum {
+	PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
+	PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX,
+	PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX,
+	PURPLE_MEDIA_CANDIDATE_TYPE_RELAY,
+	PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST,
+} PurpleMediaCandidateType;
+
+typedef enum {
+	PURPLE_MEDIA_COMPONENT_NONE = 0,
+	PURPLE_MEDIA_COMPONENT_RTP = 1,
+	PURPLE_MEDIA_COMPONENT_RTCP = 2,
+} PurpleMediaComponentType;
+
+typedef enum {
+	PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
+	PURPLE_MEDIA_NETWORK_PROTOCOL_TCP,
+} PurpleMediaNetworkProtocol;
+
+#ifdef USE_VV
+
+/** The media class */
+struct _PurpleMediaClass
+{
+	GObjectClass parent_class;     /**< The parent class. */
+};
+
+/** The media class's private data */
+struct _PurpleMedia
+{
+	GObject parent;                /**< The parent of this object. */
+	PurpleMediaPrivate *priv;      /**< The private data of this object. */
+};
+
+struct _PurpleMediaCandidate
+{
+	const gchar *foundation;
+	guint component_id;
+	const gchar *ip;
+	guint16 port;
+	const gchar *base_ip;
+	guint16 base_port;
+	PurpleMediaNetworkProtocol proto;
+	guint32 priority;
+	PurpleMediaCandidateType type;
+	const gchar *username;
+	const gchar *password;
+	guint ttl;
+};
+
+struct _PurpleMediaCodecParameter
+{
+	gchar *name;
+	gchar *value;
+};
+
+struct _PurpleMediaCodec
+{
+	gint id;
+	char *encoding_name;
+	PurpleMediaSessionType media_type;
+	guint clock_rate;
+	guint channels;
+	GList *optional_params;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the media class's GType
+ *
+ * @return The media class's GType.
+ */
+GType purple_media_get_type(void);
+
+/**
+ * Gets the type of the state-changed enum
+ *
+ * @return The state-changed enum's GType
+ */
+GType purple_media_state_changed_get_type(void);
+
+/**
+ * Gets the type of the media candidate structure.
+ *
+ * @return The media canditate's GType
+ */
+GType purple_media_candidate_get_type(void);
+
+/**
+ * Creates a PurpleMediaCandidate instance.
+ *
+ * @param foundation The foundation of the candidate.
+ * @param component_id The component this candidate is for.
+ * @param type The type of candidate.
+ * @param proto The protocol this component is for.
+ * @param ip The IP address of this component.
+ * @param port The network port.
+ *
+ * @return The newly created PurpleMediaCandidate instance.
+ */
+PurpleMediaCandidate *purple_media_candidate_new(
+		const gchar *foundation, guint component_id,
+		PurpleMediaCandidateType type,
+		PurpleMediaNetworkProtocol proto,
+		const gchar *ip, guint port);
+
+/**
+ * Copies a GList of PurpleMediaCandidate and its contents.
+ *
+ * @param candidates The list of candidates to be copied.
+ *
+ * @return The copy of the GList.
+ */
+GList *purple_media_candidate_list_copy(GList *candidates);
+
+/**
+ * Frees a GList of PurpleMediaCandidate and its contents.
+ *
+ * @param candidates The list of candidates to be freed.
+ */
+void purple_media_candidate_list_free(GList *candidates);
+
+/**
+ * Gets the type of the media codec structure.
+ *
+ * @return The media codec's GType
+ */
+GType purple_media_codec_get_type(void);
+
+/**
+ * Creates a new PurpleMediaCodec instance.
+ *
+ * @param id Codec identifier.
+ * @param encoding_name Name of the media type this encodes.
+ * @param media_type PurpleMediaSessionType of this codec.
+ * @param clock_rate The clock rate this codec encodes at, if applicable.
+ *
+ * @return The newly created PurpleMediaCodec.
+ */
+PurpleMediaCodec *purple_media_codec_new(int id, const char *encoding_name,
+		PurpleMediaSessionType media_type, guint clock_rate);
+
+/**
+ * Creates a string representation of the codec.
+ *
+ * @param codec The codec to create the string of.
+ *
+ * @return The new string representation.
+ */
+gchar *purple_media_codec_to_string(const PurpleMediaCodec *codec);
+
+/**
+ * Adds an optional parameter to the codec.
+ *
+ * @param codec The codec to add the parameter to.
+ * @param name The name of the parameter to add.
+ * @param value The value of the parameter to add.
+ */
+void purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec,
+		const gchar *name, const gchar *value);
+
+/**
+ * Removes an optional parameter from the codec.
+ *
+ * @param codec The codec to remove the parameter from.
+ * @param param A pointer to the parameter to remove.
+ */
+void purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec,
+		PurpleMediaCodecParameter *param);
+
+/**
+ * Gets an optional parameter based on the values given.
+ *
+ * @param codec The codec to find the parameter in.
+ * @param name The name of the parameter to search for.
+ * @param value The value to search for or NULL.
+ *
+ * @return The value found or NULL.
+ */
+PurpleMediaCodecParameter *purple_media_codec_get_optional_parameter(
+		PurpleMediaCodec *codec, const gchar *name,
+		const gchar *value);
+
+/**
+ * Copies a GList of PurpleMediaCodec and its contents.
+ *
+ * @param codecs The list of codecs to be copied.
+ *
+ * @return The copy of the GList.
+ */
+GList *purple_media_codec_list_copy(GList *codecs);
+
+/**
+ * Frees a GList of PurpleMediaCodec and its contents.
+ *
+ * @param codecs The list of codecs to be freed.
+ */
+void purple_media_codec_list_free(GList *codecs);
+
+/**
+ * Combines all the separate session types into a single PurpleMediaSessionType.
+ *
+ * @param media The media session to retrieve session types from.
+ *
+ * @return Combined type.
+ */
+PurpleMediaSessionType purple_media_get_overall_type(PurpleMedia *media);
+
+/**
+ * Gets a list of session names.
+ *
+ * @param media The media session to retrieve session names from.
+ *
+ * @return GList of session names.
+ */
+GList *purple_media_get_session_names(PurpleMedia *media);
+
+/**
+ * Gets an audio and video source and sink from the media session.
+ *
+ * Retrieves the first of each element in the media session.
+ *
+ * @param media The media session to retreive the sources and sinks from.
+ * @param audio_src Set to the audio source.
+ * @param audio_sink Set to the audio sink.
+ * @param video_src Set to the video source.
+ * @param video_sink Set to the video sink.
+ */
+void purple_media_get_elements(PurpleMedia *media,
+			       GstElement **audio_src, GstElement **audio_sink,
+			       GstElement **video_src, GstElement **video_sink);
+
+/**
+ * Sets the source on a session.
+ *
+ * @param media The media object the session is in.
+ * @param sess_id The session id of the session to set the source on.
+ * @param src The source to set the session source to.
+ */
+void purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src);
+
+/**
+ * Sets the sink on a stream.
+ *
+ * @param media The media object the session is in.
+ * @param sess_id The session id the stream belongs to.
+ * @param sess_id The participant the stream is associated with.
+ * @param sink The source to set the session sink to.
+ */
+void purple_media_set_sink(PurpleMedia *media, const gchar *sess_id,
+		const gchar *participant, GstElement *sink);
+
+/**
+ * Gets the source from a session
+ *
+ * @param media The media object the session is in.
+ * @param sess_id The session id of the session to get the source from.
+ *
+ * @return The source retrieved.
+ */
+GstElement *purple_media_get_src(PurpleMedia *media, const gchar *sess_id);
+
+/**
+ * Gets the sink from a stream
+ *
+ * @param media The media object the session is in.
+ * @param sess_id The session id the stream belongs to.
+ * @param participant The participant the stream is associated with.
+ *
+ * @return The sink retrieved.
+ */
+GstElement *purple_media_get_sink(PurpleMedia *media, const gchar *sess_id, const gchar *participant);
+
+/**
+ * Gets the pipeline from the media session.
+ *
+ * @param media The media session to retrieve the pipeline from.
+ *
+ * @return The pipeline retrieved.
+ */
+GstElement *purple_media_get_pipeline(PurpleMedia *media);
+
+/**
+ * Signals an error in the media session.
+ *
+ * @param media The media object to set the state on.
+ * @param error The format of the error message to send in the signal.
+ * @param ... The arguments to plug into the format.
+ */
+void purple_media_error(PurpleMedia *media, const gchar *error, ...);
+
+/**
+ * Set the media session to the accepted state.
+ *
+ * @param media The media object to set the state on.
+ */
+void purple_media_accept(PurpleMedia *media);
+
+/**
+ * Set the media session to the rejected state.
+ *
+ * @param media The media object to set the state on.
+ */
+void purple_media_reject(PurpleMedia *media);
+
+/**
+ * Set the media session to the hangup state.
+ *
+ * @param media The media object to set the state on.
+ */
+void purple_media_hangup(PurpleMedia *media);
+
+/**
+ * Ends all streams that match the given parameters
+ *
+ * @param media The media object with which to end streams.
+ * @param session_id The session to end streams on.
+ * @param participant The participant to end streams with.
+ */
+void purple_media_end(PurpleMedia *media, const gchar *session_id,
+		const gchar *participant);
+
+/**
+ * Enumerates a list of devices.
+ *
+ * @param plugin The name of the GStreamer plugin from which to enumerate devices.
+ *
+ * @return The list of enumerated devices.
+ */
+GList *purple_media_get_devices(const gchar *plugin);
+
+/**
+ * Gets the device the plugin is currently set to.
+ *
+ * @param element The plugin to retrieve the device from.
+ *
+ * @return The device retrieved.
+ */
+gchar *purple_media_element_get_device(GstElement *element);
+
+/**
+ * Creates a default audio source.
+ *
+ * @param sendbin Set to the newly created audio source.
+ * @param sendlevel Set to the newly created level within the audio source.
+ */
+void purple_media_audio_init_src(GstElement **sendbin,
+                                 GstElement **sendlevel);
+
+/**
+ * Creates a default video source.
+ *
+ * @param sendbin Set to the newly created video source.
+ */
+void purple_media_video_init_src(GstElement **sendbin);
+
+/**
+ * Creates a default audio sink.
+ *
+ * @param recvbin Set to the newly created audio sink.
+ * @param recvlevel Set to the newly created level within the audio sink.
+ */
+void purple_media_audio_init_recv(GstElement **recvbin, GstElement **recvlevel);
+
+/**
+ * Creates a default video sink.
+ *
+ * @param sendbin Set to the newly created video sink.
+ */
+void purple_media_video_init_recv(GstElement **sendbin);
+
+/**
+ * Adds a stream to a session.
+ *
+ * It only adds a stream to one audio session or video session as
+ * the @c sess_id must be unique between sessions.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to add the stream to.
+ * @param who The name of the remote user to add the stream for.
+ * @param type The type of stream to create.
+ * @param transmitter The transmitter to use for the stream.
+ * @param num_params The number of parameters to pass to Farsight.
+ * @param params The parameters to pass to Farsight.
+ *
+ * @return @c TRUE The stream was added successfully, @c FALSE otherwise.
+ */
+gboolean purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who,
+		PurpleMediaSessionType type, const gchar *transmitter,
+		guint num_params, GParameter *params);
+
+/**
+ * Removes a stream from a session.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to remove the stream from.
+ * @param who The name of the remote user to remove the stream from.
+ */
+void purple_media_remove_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who);
+
+/**
+ * Gets the session type from a session
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to get the type from.
+ *
+ * @return The retreived session type.
+ */
+PurpleMediaSessionType purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id);
+
+/**
+ * Gets the codecs from a session.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to get the codecs from.
+ *
+ * @return The retreieved codecs.
+ */
+GList *purple_media_get_codecs(PurpleMedia *media, const gchar *sess_id);
+
+/**
+ * Adds remote candidates to the stream.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session find the stream in.
+ * @param name The name of the remote user to add the candidates for.
+ * @param remote_candidates The remote candidates to add.
+ */
+void purple_media_add_remote_candidates(PurpleMedia *media,
+					const gchar *sess_id,
+					const gchar *name,
+					GList *remote_candidates);
+
+/**
+ * Gets the local candidates from a stream.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to find the stream in.
+ * @param name The name of the remote user to get the candidates from.
+ */
+GList *purple_media_get_local_candidates(PurpleMedia *media,
+					 const gchar *sess_id,
+					 const gchar *name);
+
+/**
+ * Gets the active local candidate for the stream.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to find the stream in.
+ * @param name The name of the remote user to get the active candidate from.
+ *
+ * @return The active candidate retrieved.
+ */
+PurpleMediaCandidate *purple_media_get_local_candidate(PurpleMedia *media,
+		const gchar *sess_id, const gchar *name);
+
+/**
+ * Gets the active remote candidate for the stream.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to find the stream in.
+ * @param name The name of the remote user to get the remote candidate from.
+ *
+ * @return The remote candidate retrieved.
+ */
+PurpleMediaCandidate *purple_media_get_remote_candidate(PurpleMedia *media,
+		const gchar *sess_id, const gchar *name);
+
+/**
+ * Gets remote candidates from the stream.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session find the stream in.
+ * @param name The name of the remote user to get the candidates from.
+ *
+ * @return @c TRUE The codecs were set successfully, or @c FALSE otherwise.
+ */
+gboolean purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id,
+					const gchar *name, GList *codecs);
+
+/**
+ * Returns whether or not the candidates for a remote user are prepared
+ *
+ * @param media The media object to find the remote user in.
+ * @param name The remote user to check for.
+ *
+ * @return @c TRUE All streams for the remote user have candidates prepared, @c FALSE otherwise.
+ */
+gboolean purple_media_candidates_prepared(PurpleMedia *media, const gchar *name);
+
+/**
+ * Sets the send codec for the a session.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to set the codec for.
+ * @param codec The codec to set the session to stream.
+ *
+ * @return @c TRUE The codec was successfully changed, or @c FALSE otherwise.
+ */
+gboolean purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, PurpleMediaCodec *codec);
+
+/**
+ * Gets whether a session's codecs are ready to be used.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to check.
+ *
+ * @return @c TRUE The codecs are ready, or @c FALSE otherwise.
+ */
+gboolean purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id);
+
+/**
+ * Gets whether a streams selected have been accepted.
+ *
+ * @param media The media object to find the session in.
+ * @param sess_id The session id of the session to check.
+ * @param participant The participant to check.
+ *
+ * @return @c TRUE The selected streams have been accepted, or @c FALSE otherwise.
+ */
+gboolean purple_media_accepted(PurpleMedia *media, const gchar *sess_id,
+		const gchar *participant);
+
+/**
+ * Mutes or unmutes all the audio local audio sources.
+ *
+ * @param media The media object to mute or unmute
+ * @param active @c TRUE to mutes all of the local audio sources, or @c FALSE to unmute.
+ */
+void purple_media_mute(PurpleMedia *media, gboolean active);
+
+/**
+ * Sets the input volume of all the selected sessions.
+ *
+ * @param media The media object the sessions are in.
+ * @param session_id The session to select (if any).
+ * @param level The level to set the volume to.
+ */
+void purple_media_set_input_volume(PurpleMedia *media, const gchar *session_id, double level);
+
+/**
+ * Sets the output volume of all the selected streams.
+ *
+ * @param media The media object the streams are in.
+ * @param session_id The session to limit the streams to (if any).
+ * @param participant The participant to limit the streams to (if any).
+ * @param level The level to set the volume to.
+ */
+void purple_media_set_output_volume(PurpleMedia *media, const gchar *session_id,
+		const gchar *participant, double level);
+
+gboolean purple_media_set_output_window(PurpleMedia *media,
+		const gchar *session_id, const gchar *participant,
+		gulong window_id);
+gboolean purple_media_remove_output_window(PurpleMedia *media,
+		const gchar *session_id, const gchar *participant);
+void purple_media_remove_output_windows(PurpleMedia *media);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif  /* USE_VV */
+
+
+#endif  /* __MEDIA_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/mediamanager.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,357 @@
+/**
+ * @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 "debug.h"
+#include "marshallers.h"
+#include "mediamanager.h"
+#include "media.h"
+
+#ifdef USE_VV
+
+#include <gst/farsight/fs-conference-iface.h>
+
+struct _PurpleMediaManagerPrivate
+{
+	GList *medias;
+	GList *elements;
+
+	PurpleMediaElementInfo *video_src;
+	PurpleMediaElementInfo *video_sink;
+	PurpleMediaElementInfo *audio_src;
+	PurpleMediaElementInfo *audio_sink;
+};
+
+#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,
+		purple_smarshal_BOOLEAN__OBJECT_POINTER_STRING,
+		G_TYPE_BOOLEAN, 3, PURPLE_TYPE_MEDIA,
+		G_TYPE_POINTER, G_TYPE_STRING);
+	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)
+{
+	PurpleMediaManagerPrivate *priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media);
+	for (; priv->medias; priv->medias =
+			g_list_delete_link(priv->medias, priv->medias)) {
+		g_object_unref(priv->medias->data);
+	}
+	for (; priv->elements; priv->elements =
+			g_list_delete_link(priv->elements, priv->elements));
+	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,
+				  gboolean initiator)
+{
+	PurpleMedia *media;
+	FsConference *conference = FS_CONFERENCE(gst_element_factory_make(conference_type, NULL));
+	GstStateChangeReturn ret;
+	gboolean signal_ret;
+
+	if (conference == NULL) {
+		purple_conv_present_error(remote_user,
+					  purple_connection_get_account(gc),
+					  _("Error creating conference."));
+		purple_debug_error("media", "Conference == NULL\n");
+		return NULL;
+	}
+
+	media = PURPLE_MEDIA(g_object_new(purple_media_get_type(),
+			     "conference", conference,
+			     "initiator", initiator,
+			     NULL));
+
+	ret = gst_element_set_state(GST_ELEMENT(conference), GST_STATE_PLAYING);
+
+	if (ret == GST_STATE_CHANGE_FAILURE) {
+		purple_conv_present_error(remote_user,
+					  purple_connection_get_account(gc),
+					  _("Error creating conference."));
+		purple_debug_error("media", "Failed to start conference.\n");
+		g_object_unref(media);
+		return NULL;
+	}
+
+	g_signal_emit(manager, purple_media_manager_signals[INIT_MEDIA], 0,
+			media, gc, remote_user, &signal_ret);
+
+	if (signal_ret == FALSE) {
+		g_object_unref(media);
+		return NULL;
+	}
+
+	manager->priv->medias = g_list_append(manager->priv->medias, media);
+	return media;
+}
+
+GList *
+purple_media_manager_get_media(PurpleMediaManager *manager)
+{
+	return manager->priv->medias;
+}
+
+void
+purple_media_manager_remove_media(PurpleMediaManager *manager,
+				  PurpleMedia *media)
+{
+	GList *list = g_list_find(manager->priv->medias, media);
+	if (list)
+		manager->priv->medias =
+			g_list_delete_link(manager->priv->medias, list);
+}
+
+GstElement *
+purple_media_manager_get_element(PurpleMediaManager *manager,
+		PurpleMediaSessionType type)
+{
+	GstElement *ret = NULL;
+
+	/* TODO: If src, retrieve current src */
+	/* TODO: Send a signal here to allow for overriding the source/sink */
+
+	if (type & PURPLE_MEDIA_SEND_AUDIO
+			&& manager->priv->audio_src != NULL)
+		ret = manager->priv->audio_src->create();
+	else if (type & PURPLE_MEDIA_RECV_AUDIO
+			&& manager->priv->audio_sink != NULL)
+		ret = manager->priv->audio_sink->create();
+	else if (type & PURPLE_MEDIA_SEND_VIDEO
+			&& manager->priv->video_src != NULL)
+		ret = manager->priv->video_src->create();
+	else if (type & PURPLE_MEDIA_RECV_VIDEO
+			&& manager->priv->video_sink != NULL)
+		ret = manager->priv->video_sink->create();
+
+	if (ret == NULL)
+		purple_debug_error("media", "Error creating source or sink\n");
+
+	return ret;
+}
+
+PurpleMediaElementInfo *
+purple_media_manager_get_element_info(PurpleMediaManager *manager,
+		const gchar *id)
+{
+	GList *iter;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
+
+	iter = manager->priv->elements;
+
+	for (; iter; iter = g_list_next(iter)) {
+		PurpleMediaElementInfo *info = iter->data;
+		if (!strcmp(info->id, id))
+			return info;
+	}
+
+	return NULL;
+}
+
+gboolean
+purple_media_manager_register_element(PurpleMediaManager *manager,
+		PurpleMediaElementInfo *info)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
+	g_return_val_if_fail(info != NULL, FALSE);
+
+	if (purple_media_manager_get_element_info(manager, info->id) != NULL)
+		return FALSE;
+
+	manager->priv->elements =
+			g_list_prepend(manager->priv->elements, info);
+	return TRUE;
+}
+
+gboolean
+purple_media_manager_unregister_element(PurpleMediaManager *manager,
+		const gchar *id)
+{
+	PurpleMediaElementInfo *info;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
+
+	info = purple_media_manager_get_element_info(manager, id);
+
+	if (info == NULL)
+		return FALSE;
+
+	if (manager->priv->audio_src == info)
+		manager->priv->audio_src = NULL;
+	if (manager->priv->audio_sink == info)
+		manager->priv->audio_sink = NULL;
+	if (manager->priv->video_src == info)
+		manager->priv->video_src = NULL;
+	if (manager->priv->video_sink == info)
+		manager->priv->video_sink = NULL;
+
+	manager->priv->elements = g_list_remove(
+			manager->priv->elements, info);
+	return TRUE;
+}
+
+gboolean
+purple_media_manager_set_active_element(PurpleMediaManager *manager,
+		PurpleMediaElementInfo *info)
+{
+	gboolean ret = FALSE;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
+	g_return_val_if_fail(info != NULL, FALSE);
+
+	if (purple_media_manager_get_element_info(manager, info->id) == NULL)
+		purple_media_manager_register_element(manager, info);
+
+	if (info->type & PURPLE_MEDIA_ELEMENT_SRC) {
+		if (info->type & PURPLE_MEDIA_ELEMENT_AUDIO) {
+			manager->priv->audio_src = info;
+			ret = TRUE;
+		}
+		if (info->type & PURPLE_MEDIA_ELEMENT_VIDEO) {
+			manager->priv->video_src = info;
+			ret = TRUE;
+		}
+	}
+	if (info->type & PURPLE_MEDIA_ELEMENT_SINK) {
+		if (info->type & PURPLE_MEDIA_ELEMENT_AUDIO) {
+			manager->priv->audio_sink = info;
+			ret = TRUE;
+		}
+		if (info->type & PURPLE_MEDIA_ELEMENT_VIDEO) {
+			manager->priv->video_sink = info;
+			ret = TRUE;
+		}
+	}
+
+	return ret;
+}
+
+PurpleMediaElementInfo *
+purple_media_manager_get_active_element(PurpleMediaManager *manager,
+		PurpleMediaElementType type)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
+
+	if (type & PURPLE_MEDIA_ELEMENT_SRC) {
+		if (type & PURPLE_MEDIA_ELEMENT_AUDIO)
+			return manager->priv->audio_src;
+		else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
+			return manager->priv->video_src;
+	} else if (type & PURPLE_MEDIA_ELEMENT_SINK) {
+		if (type & PURPLE_MEDIA_ELEMENT_AUDIO)
+			return manager->priv->audio_sink;
+		else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
+			return manager->priv->video_sink;
+	}
+
+	return NULL;
+}
+
+#endif  /* USE_VV */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/mediamanager.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,188 @@
+/**
+ * @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 <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))
+
+/** @copydoc _PurpleMediaManager */
+typedef struct _PurpleMediaManager PurpleMediaManager;
+/** @copydoc _PurpleMediaManagerClass */
+typedef struct _PurpleMediaManagerClass PurpleMediaManagerClass;
+/** @copydoc _PurpleMediaManagerPrivate */
+typedef struct _PurpleMediaManagerPrivate PurpleMediaManagerPrivate;
+/** @copydoc _PurpleMediaElementInfo */
+typedef struct _PurpleMediaElementInfo PurpleMediaElementInfo;
+
+/** The media manager class. */
+struct _PurpleMediaManagerClass
+{
+	GObjectClass parent_class;       /**< The parent class. */
+};
+
+/** The media manager's data. */
+struct _PurpleMediaManager
+{
+	GObject parent;                  /**< The parent of this manager. */
+	PurpleMediaManagerPrivate *priv; /**< Private data for the manager. */
+};
+
+typedef enum {
+	PURPLE_MEDIA_ELEMENT_AUDIO = 1,			/** supports audio */
+	PURPLE_MEDIA_ELEMENT_VIDEO = 1 << 1,		/** supports video */
+	PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO = PURPLE_MEDIA_ELEMENT_AUDIO
+			| PURPLE_MEDIA_ELEMENT_VIDEO, 	/** supports audio and video */
+
+	PURPLE_MEDIA_ELEMENT_NO_SRCS = 0,		/** has no src pads */
+	PURPLE_MEDIA_ELEMENT_ONE_SRC = 1 << 2,		/** has one src pad */
+	PURPLE_MEDIA_ELEMENT_MULTI_SRC = 1 << 3, 	/** has multiple src pads */
+	PURPLE_MEDIA_ELEMENT_REQUEST_SRC = 1 << 4, 	/** src pads must be requested */
+
+	PURPLE_MEDIA_ELEMENT_NO_SINKS = 0,		/** has no sink pads */
+	PURPLE_MEDIA_ELEMENT_ONE_SINK = 1 << 5, 	/** has one sink pad */
+	PURPLE_MEDIA_ELEMENT_MULTI_SINK = 1 << 6, 	/** has multiple sink pads */
+	PURPLE_MEDIA_ELEMENT_REQUEST_SINK = 1 << 7, 	/** sink pads must be requested */
+
+	PURPLE_MEDIA_ELEMENT_UNIQUE = 1 << 8,		/** This element is unique and
+							 only one instance of it should
+							 be created at a time */
+
+	PURPLE_MEDIA_ELEMENT_SRC = 1 << 9,		/** can be set as an active src */
+	PURPLE_MEDIA_ELEMENT_SINK = 1 << 10,		/** can be set as an active sink */
+} PurpleMediaElementType;
+
+struct _PurpleMediaElementInfo
+{
+	const gchar *id;
+	PurpleMediaElementType type;
+	GstElement *(*create)(void);
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**************************************************************************/
+/** @cname Media Manager API                                              */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Gets the media manager's GType.
+ *
+ * @return The media manager's GType.
+ */
+GType purple_media_manager_get_type(void);
+
+/**
+ * Gets the "global" media manager object. It's created if it doesn't already exist.
+ *
+ * @return The "global" instance of the media manager object.
+ */
+PurpleMediaManager *purple_media_manager_get(void);
+
+/**
+ * Creates a media session.
+ *
+ * @param manager The media manager to create the session under.
+ * @param gc The connection to create the session on.
+ * @param conference_type The conference type to feed into Farsight2.
+ * @param remote_user The remote user to initiate the session with.
+ *
+ * @return A newly created media session.
+ */
+PurpleMedia *purple_media_manager_create_media(PurpleMediaManager *manager,
+						PurpleConnection *gc,
+						const char *conference_type,
+						const char *remote_user,
+						gboolean initiator);
+
+/**
+ * Gets all of the media sessions.
+ *
+ * @param manager The media manager to get all of the sessions from.
+ *
+ * @return A list of all the media sessions.
+ */
+GList *purple_media_manager_get_media(PurpleMediaManager *manager);
+
+/**
+ * Removes a media session from the media manager.
+ *
+ * @param manager The media manager to remove the media session from.
+ * @param media The media session to remove.
+ */
+void
+purple_media_manager_remove_media(PurpleMediaManager *manager,
+				  PurpleMedia *media);
+
+/**
+ * Returns a GStreamer source or sink for audio or video.
+ *
+ * @param manager The media manager to use to obtain the source/sink.
+ * @param type The type of source/sink to get.
+ */
+GstElement *purple_media_manager_get_element(PurpleMediaManager *manager,
+		PurpleMediaSessionType type);
+
+PurpleMediaElementInfo *purple_media_manager_get_element_info(
+		PurpleMediaManager *manager, const gchar *name);
+gboolean purple_media_manager_register_element(PurpleMediaManager *manager,
+		PurpleMediaElementInfo *info);
+gboolean purple_media_manager_unregister_element(PurpleMediaManager *manager,
+		const gchar *name);
+gboolean purple_media_manager_set_active_element(PurpleMediaManager *manager,
+		PurpleMediaElementInfo *info);
+PurpleMediaElementInfo *purple_media_manager_get_active_element(
+		PurpleMediaManager *manager, PurpleMediaElementType type);
+/*}@*/
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif  /* USE_VV */
+
+
+#endif  /* __MEDIA_MANAGER_H_ */
--- a/libpurple/network.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/network.c	Thu Feb 05 00:31:35 2009 +0000
@@ -48,6 +48,7 @@
 #include "prefs.h"
 #include "stun.h"
 #include "upnp.h"
+#include "dnsquery.h"
 
 /*
  * Calling sizeof(struct ifreq) isn't always correct on
@@ -96,6 +97,10 @@
 static NMState nm_get_network_state(void);
 #endif
 
+/* Cached IP addresses for STUN and TURN servers (set globally in prefs) */
+static gchar *stun_ip = NULL;
+static gchar *turn_ip = NULL;
+
 const unsigned char *
 purple_network_ip_atoi(const char *ip)
 {
@@ -708,6 +713,14 @@
 		case NM_STATE_CONNECTED:
 			/* Call res_init in case DNS servers have changed */
 			res_init();
+			/* update STUN IP in case we it changed (theoretically we could
+			   have gone from IPv4 to IPv6, f.ex. or we were previously
+			   offline */
+			purple_network_set_stun_server(
+				purple_prefs_get_string("/purple/network/stun_server"));
+			purple_network_set_turn_server(
+				purple_prefs_get_string("/purple/network/turn_server"));
+			
 			if (ui_ops != NULL && ui_ops->network_connected != NULL)
 				ui_ops->network_connected();
 			break;
@@ -769,6 +782,88 @@
 
 #endif
 
+static void
+purple_network_ip_lookup_cb(GSList *hosts, gpointer data, 
+	const char *error_message)
+{
+	const gchar **ip = (const gchar **) data; 
+
+	if (error_message) {
+		purple_debug_error("network", "lookup of IP address failed: %s\n",
+			error_message);
+		g_slist_free(hosts);
+		return;
+	}
+
+	if (hosts && g_slist_next(hosts)) {
+		struct sockaddr *addr = g_slist_next(hosts)->data; 
+		char dst[INET6_ADDRSTRLEN];
+		
+		if (addr->sa_family == AF_INET6) {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, 
+				dst, sizeof(dst));
+		} else {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, 
+				dst, sizeof(dst));
+		}
+
+		*ip = g_strdup(dst);
+		purple_debug_info("network", "set IP address: %s\n", *ip);
+	}
+	
+	g_slist_free(hosts);
+}
+
+void
+purple_network_set_stun_server(const gchar *stun_server)
+{
+	if (stun_server && stun_server[0] != '\0') {
+		if (purple_network_is_available()) {
+			purple_debug_info("network", "running DNS query for STUN server\n");
+			purple_dnsquery_a(stun_server, 3478, purple_network_ip_lookup_cb,
+				&stun_ip);
+		} else {
+			purple_debug_info("network", 
+				"network is unavailable, don't try to update STUN IP");
+		}
+	} else if (stun_ip) {
+		g_free(stun_ip);
+		stun_ip = NULL;
+	}
+}
+
+void
+purple_network_set_turn_server(const gchar *turn_server)
+{
+	if (turn_server && turn_server[0] != '\0') {
+		if (purple_network_is_available()) {
+			purple_debug_info("network", "running DNS query for TURN server\n");
+			purple_dnsquery_a(turn_server, 
+				purple_prefs_get_int("/purple/network/turn_port"), 
+				purple_network_ip_lookup_cb, &turn_ip);
+		} else {
+			purple_debug_info("network", 
+				"network is unavailable, don't try to update TURN IP");
+		}
+	} else if (turn_ip) {
+		g_free(turn_ip);
+		turn_ip = NULL;
+	}
+}
+
+
+const gchar *
+purple_network_get_stun_ip(void)
+{
+	return stun_ip;
+}
+
+const gchar *
+purple_network_get_turn_ip(void)
+{
+	return turn_ip;
+}
+
 void *
 purple_network_get_handle(void)
 {
@@ -801,6 +896,11 @@
 #endif
 
 	purple_prefs_add_none  ("/purple/network");
+	purple_prefs_add_string("/purple/network/stun_server", "");
+	purple_prefs_add_string("/purple/network/turn_server", "");
+	purple_prefs_add_int   ("/purple/network/turn_port", 3478);
+	purple_prefs_add_string("/purple/network/turn_username", "");
+	purple_prefs_add_string("/purple/network/turn_password", "");
 	purple_prefs_add_bool  ("/purple/network/auto_ip", TRUE);
 	purple_prefs_add_string("/purple/network/public_ip", "");
 	purple_prefs_add_bool  ("/purple/network/map_ports", TRUE);
@@ -839,6 +939,11 @@
 
 	purple_pmp_init();
 	purple_upnp_init();
+	
+	purple_network_set_stun_server(
+		purple_prefs_get_string("/purple/network/stun_server"));
+	purple_network_set_turn_server(
+		purple_prefs_get_string("/purple/network/turn_server"));
 }
 
 void
@@ -880,4 +985,7 @@
 #endif
 	purple_signal_unregister(purple_network_get_handle(),
 							 "network-configuration-changed");
+	
+	if (stun_ip)
+		g_free(stun_ip);
 }
--- a/libpurple/network.h	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/network.h	Thu Feb 05 00:31:35 2009 +0000
@@ -214,6 +214,37 @@
  */
 void *purple_network_get_handle(void);
 
+/**	
+ * Update the STUN server IP given the host name
+ * Will result in a DNS query being executed asynchronous
+ * 
+ * @param stun_server The host name of the STUN server to set
+ */
+void purple_network_set_stun_server(const gchar *stun_server);
+	
+/**
+ * Get the IP address of the STUN server as a string representation
+ *
+ * @return the IP address
+ */
+const gchar *purple_network_get_stun_ip(void);
+	
+/**	
+ * Update the TURN server IP given the host name
+ * Will result in a DNS query being executed asynchronous
+ * 
+ * @param stun_server The host name of the STUN server to set
+ */
+void purple_network_set_turn_server(const gchar *stun_server);
+	
+/**
+ * Get the IP address of the STUN server as a string representation
+ *
+ * @return the IP address
+ */
+const gchar *purple_network_get_turn_ip(void);
+		
+	
 /**
  * Initializes the network subsystem.
  */
--- a/libpurple/plugins/Makefile.am	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/plugins/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/plugins/perl/Makefile.am	Thu Feb 05 00:31:35 2009 +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 \
@@ -167,4 +167,5 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(PLUGIN_CFLAGS) \
-	$(PERL_CFLAGS)
+	$(PERL_CFLAGS) \
+	$(FARSIGHT_CFLAGS)
--- a/libpurple/plugins/ssl/Makefile.am	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/plugins/ssl/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -52,6 +52,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/plugins/tcl/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -50,4 +50,11 @@
 	$(GLIB_CFLAGS) \
 	$(DEBUG_CFLAGS) \
 	$(LIBXML_CFLAGS) \
-	$(AVAHI_CFLAGS)
\ No newline at end of file
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
+#if MDNS_AVAHI
+#  AM_CPPFLAGS += $(AVAHI_CFLAGS)
+#else
+#endif
--- a/libpurple/protocols/bonjour/bonjour.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/gg/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -71,5 +71,8 @@
 	-I$(top_builddir)/libpurple \
 	$(INTGG_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
 	$(DEBUG_CFLAGS)
 
--- a/libpurple/protocols/gg/gg.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/gg/gg.c	Thu Feb 05 00:31:35 2009 +0000
@@ -2292,13 +2292,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 = {
@@ -2376,3 +2376,4 @@
 PURPLE_INIT_PLUGIN(gg, init_plugin, info);
 
 /* vim: set ts=8 sts=0 sw=8 noet: */
+
--- a/libpurple/protocols/irc/Makefile.am	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/irc/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -35,4 +35,6 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
 	$(DEBUG_CFLAGS)
--- a/libpurple/protocols/irc/irc.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/irc/irc.c	Thu Feb 05 00:31:35 2009 +0000
@@ -912,13 +912,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -21,6 +21,20 @@
 			  iq.h \
 			  jabber.c \
 			  jabber.h \
+			  jingle/jingle.c \
+			  jingle/jingle.h \
+			  jingle/content.c \
+			  jingle/content.h \
+			  jingle/iceudp.c \
+			  jingle/iceudp.h \
+			  jingle/rawudp.c \
+			  jingle/rawudp.h \
+			  jingle/rtp.c \
+			  jingle/rtp.h \
+			  jingle/session.c \
+			  jingle/session.h \
+			  jingle/transport.c \
+			  jingle/transport.h \
 			  jutil.c \
 			  jutil.h \
 			  message.c \
@@ -80,4 +94,6 @@
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
 	$(LIBXML_CFLAGS)
--- a/libpurple/protocols/jabber/Makefile.mingw	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/Makefile.mingw	Thu Feb 05 00:31:35 2009 +0000
@@ -53,6 +53,13 @@
 			google.c \
 			iq.c \
 			jabber.c \
+			jingle/jingle.c \
+			jingle/content.c \
+			jingle/iceudp.c \
+			jingle/rawudp.c \
+			jingle/rtp.c \
+			jingle/session.c \
+			jingle/transport.c \
 			jutil.c \
 			message.c \
 			oob.c \
@@ -78,6 +85,7 @@
 ##
 LIBS = \
 			-lglib-2.0 \
+			-lgobject-2.0 \
 			-lxml2 \
 			-lws2_32 \
 			-lintl \
--- a/libpurple/protocols/jabber/caps.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/caps.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Thu Feb 05 00:31:35 2009 +0000
@@ -28,6 +28,7 @@
 #include "iq.h"
 #include "disco.h"
 #include "jabber.h"
+#include "jingle/jingle.h"
 #include "presence.h"
 #include "roster.h"
 #include "pep.h"
@@ -88,7 +89,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;
 
@@ -114,7 +115,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");
@@ -151,6 +152,16 @@
 						SUPPORT_FEATURE(feat->namespace);
 				}
 			}
+#ifdef USE_VV
+		} else if (node && !strcmp(node, CAPS0115_NODE "#voice-v1")) {
+			SUPPORT_FEATURE("http://www.google.com/xmpp/protocol/session");
+			SUPPORT_FEATURE("http://www.google.com/xmpp/protocol/voice/v1");
+			SUPPORT_FEATURE(JINGLE);
+			SUPPORT_FEATURE(JINGLE_APP_RTP_SUPPORT_AUDIO);
+			SUPPORT_FEATURE(JINGLE_APP_RTP_SUPPORT_VIDEO);
+			SUPPORT_FEATURE(JINGLE_TRANSPORT_RAWUDP);
+			SUPPORT_FEATURE(JINGLE_TRANSPORT_ICEUDP);
+#endif
 		} else {
 			const char *ext = NULL;
 			unsigned pos;
@@ -441,7 +452,12 @@
 		if (!strcmp(name, "Google Talk")) {
 			purple_debug_info("jabber", "Google Talk!\n");
 			js->googletalk = TRUE;
-		}
+
+			/* autodiscover stun and relays */
+			jabber_google_send_jingle_info(js);
+		} else {
+			/* TODO: add external service discovery here... */
+		} 
 	}
 
 	for (child = xmlnode_get_child(query, "feature"); child;
--- a/libpurple/protocols/jabber/google.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Thu Feb 05 00:31:35 2009 +0000
@@ -20,8 +20,11 @@
 
 #include "internal.h"
 #include "debug.h"
+#include "mediamanager.h"
 #include "util.h"
 #include "privacy.h"
+#include "dnsquery.h"
+#include "network.h"
 
 #include "buddy.h"
 #include "google.h"
@@ -29,6 +32,525 @@
 #include "presence.h"
 #include "iq.h"
 
+#include "jingle/jingle.h"
+
+#ifdef USE_VV
+
+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)
+{
+	if (sessions != NULL)
+		g_hash_table_remove(sessions, &(session->id));
+	g_free(session->id.id);
+	g_free(session->id.initiator);
+	g_free(session->remote_jid);
+	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_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_candidates(PurpleMedia *media, gchar *session_id,
+		gchar *participant, 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);
+	PurpleMediaCandidate *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 = (PurpleMediaCandidate*)(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);
+		/*
+		 * As of this writing, Farsight 2 in Google compatibility
+		 * mode doesn't provide a password. The Gmail client
+		 * requires this to be set.
+		 */
+		xmlnode_set_attrib(candidate, "password",
+				transport->password != NULL ?
+				transport->password : "");
+		xmlnode_set_attrib(candidate, "preference", pref);
+		xmlnode_set_attrib(candidate, "protocol", transport->proto ==
+				PURPLE_MEDIA_NETWORK_PROTOCOL_UDP ? "udp" : "tcp");
+		xmlnode_set_attrib(candidate, "type", transport->type ==
+				PURPLE_MEDIA_CANDIDATE_TYPE_HOST ? "local" :
+						      transport->type ==
+				PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX ? "stun" :
+					       	      transport->type ==
+				PURPLE_MEDIA_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_ready(PurpleMedia *media, gchar *id,
+		gchar *participant, GoogleSession *session)
+{
+	if (id == NULL && participant == NULL) {
+		gchar *me = g_strdup_printf("%s@%s/%s",
+				session->js->user->node,
+				session->js->user->domain,
+				session->js->user->resource);
+		JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+		xmlnode *sess, *desc, *payload;
+		GList *codecs, *iter;
+
+		if (!strcmp(session->id.initiator, me)) {
+			xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+			xmlnode_set_attrib(iq->node, "from", session->id.initiator);
+			sess = google_session_create_xmlnode(session, "initiate");
+		} else {
+			xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+			xmlnode_set_attrib(iq->node, "from", me);
+			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");
+
+		codecs = purple_media_get_codecs(media, "google-voice");
+
+		for (iter = codecs; iter; iter = g_list_next(iter)) {
+			PurpleMediaCodec *codec = (PurpleMediaCodec*)iter->data;
+			gchar *id = g_strdup_printf("%d", codec->id);
+			gchar *clock_rate = g_strdup_printf("%d", codec->clock_rate);
+			payload = xmlnode_new_child(desc, "payload-type");
+			xmlnode_set_attrib(payload, "id", id);
+			xmlnode_set_attrib(payload, "name", codec->encoding_name);
+			xmlnode_set_attrib(payload, "clockrate", clock_rate);
+			g_free(clock_rate);
+			g_free(id);
+		}
+		purple_media_codec_list_free(codecs);
+
+		jabber_iq_send(iq);
+
+		google_session_send_candidates(session->media,
+				"google-voice", session->remote_jid, session);
+	}
+}
+
+static void
+google_session_state_changed_cb(PurpleMedia *media,
+		PurpleMediaStateChangedType type,
+		gchar *sid, gchar *name, GoogleSession *session)
+{
+	if (sid == NULL && name == NULL) {
+		if (type == PURPLE_MEDIA_STATE_CHANGED_END) {
+			google_session_destroy(session);
+		} else if (type == PURPLE_MEDIA_STATE_CHANGED_HANGUP) {
+			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);
+		} else if (type == PURPLE_MEDIA_STATE_CHANGED_REJECTED) {
+			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);
+		}
+		
+	}
+}
+
+static GParameter *
+jabber_google_session_get_params(JabberStream *js, guint *num)
+{
+	guint num_params;
+	GParameter *params = jingle_get_params(js, &num_params);
+	GParameter *new_params = g_new0(GParameter, num_params + 1);
+
+	memcpy(new_params, params, sizeof(GParameter) * num_params);
+
+	purple_debug_info("jabber", "setting Google jingle compatibility param\n");
+	new_params[num_params].name = "compatibility-mode";
+	g_value_init(&new_params[num_params].value, G_TYPE_UINT);
+	g_value_set_uint(&new_params[num_params].value, 1); /* NICE_COMPATIBILITY_GOOGLE */
+
+	g_free(params);
+	*num = num_params + 1;
+	return new_params;
+}
+
+
+PurpleMedia*
+jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type)
+{
+	GoogleSession *session;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	gchar *jid;
+	GParameter *params;
+	guint num_params;
+
+	/* construct JID to send to */
+	jb = jabber_buddy_find(js, who, FALSE);
+	if (!jb) {
+		purple_debug_error("jingle-rtp",
+				"Could not find Jabber buddy\n");
+		return NULL;
+	}
+	jbr = jabber_buddy_find_resource(jb, NULL);
+	if (!jbr) {
+		purple_debug_error("jingle-rtp",
+				"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 = g_new0(GoogleSession, 1);
+	session->id.id = jabber_get_next_id(js);
+	session->id.initiator = g_strdup_printf("%s@%s/%s", js->user->node,
+			js->user->domain, js->user->resource);
+	session->state = SENT_INITIATE;
+	session->js = js;
+	session->remote_jid = jid;
+
+	session->media = purple_media_manager_create_media(
+			purple_media_manager_get(), js->gc,
+			"fsrtpconference", session->remote_jid, TRUE);
+
+	params = jabber_google_session_get_params(js, &num_params);
+
+	if (purple_media_add_stream(session->media, "google-voice",
+				session->remote_jid, PURPLE_MEDIA_AUDIO,
+				"nice", num_params, params) == FALSE) {
+		purple_media_error(session->media, "Error adding stream.");
+		purple_media_hangup(session->media);
+		google_session_destroy(session);
+		g_free(params);
+		return NULL;
+	}
+
+	g_signal_connect(G_OBJECT(session->media), "ready-new",
+			G_CALLBACK(google_session_ready), session);
+	g_signal_connect(G_OBJECT(session->media), "state-changed",
+			G_CALLBACK(google_session_state_changed_cb), session);
+
+	if (sessions == NULL)
+		sessions = g_hash_table_new(google_session_id_hash,
+				google_session_id_equal);
+	g_hash_table_insert(sessions, &(session->id), session);
+	g_free(params);
+
+	return session->media;
+}
+
+static void
+google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	JabberIq *result;
+	GList *codecs = NULL;
+	xmlnode *desc_element, *codec_element;
+	PurpleMediaCodec *codec;
+	const char *id, *encoding_name,  *clock_rate;
+	GParameter *params;
+	guint num_params;	
+
+	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, FALSE);
+
+	params = jabber_google_session_get_params(js, &num_params);
+
+	if (purple_media_add_stream(session->media, "google-voice", session->remote_jid, 
+				PURPLE_MEDIA_AUDIO, "nice", num_params, params) == FALSE) {
+		purple_media_error(session->media, "Error adding stream.");
+		purple_media_hangup(session->media);
+		google_session_send_terminate(session);
+		g_free(params);
+		return;
+	}
+
+	g_free(params);
+
+	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 = purple_media_codec_new(atoi(id), encoding_name, PURPLE_MEDIA_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(G_OBJECT(session->media), "ready-new",
+			G_CALLBACK(google_session_ready), session);
+	g_signal_connect(G_OBJECT(session->media), "state-changed",
+			G_CALLBACK(google_session_state_changed_cb), session);
+
+	purple_media_codec_list_free(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)) {
+		PurpleMediaCandidate *info;
+		g_snprintf(n, sizeof(n), "S%d", name++);
+		info = purple_media_candidate_new(n, PURPLE_MEDIA_COMPONENT_RTP,
+				!strcmp(xmlnode_get_attrib(cand, "type"), "local") ?
+					PURPLE_MEDIA_CANDIDATE_TYPE_HOST :
+			     		!strcmp(xmlnode_get_attrib(cand, "type"), "stun") ?
+						PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX :
+			     			!strcmp(xmlnode_get_attrib(cand, "type"), "relay") ?
+							PURPLE_MEDIA_CANDIDATE_TYPE_RELAY :
+							PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
+						!strcmp(xmlnode_get_attrib(cand, "protocol"),"udp") ?
+							PURPLE_MEDIA_NETWORK_PROTOCOL_UDP :
+							PURPLE_MEDIA_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);
+	purple_media_candidate_list_free(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_accept(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	xmlnode *desc_element = xmlnode_get_child(sess, "description");
+	xmlnode *codec_element = xmlnode_get_child(desc_element, "payload-type");
+	GList *codecs = NULL;
+
+	for (; codec_element; codec_element =
+			xmlnode_get_next_twin(codec_element)) {
+		const gchar *encoding_name =
+				xmlnode_get_attrib(codec_element, "name");
+		const gchar *id = xmlnode_get_attrib(codec_element, "id");
+		const gchar *clock_rate =
+				xmlnode_get_attrib(codec_element, "clockrate");
+
+		PurpleMediaCodec *codec = purple_media_codec_new(atoi(id),
+				encoding_name, PURPLE_MEDIA_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);
+
+	purple_media_accept(session->media);
+}
+
+static void
+google_session_handle_reject(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	purple_media_end(session->media, NULL, NULL);
+}
+
+static void
+google_session_handle_terminate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	purple_media_end(session->media, NULL, NULL);
+}
+
+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")) {
+		google_session_handle_accept(js, session, packet, sess);
+	} 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)
 {
@@ -525,3 +1047,105 @@
 	const char *attr = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
 	return attr ? g_strdup_printf("♫ %s", attr) : g_strdup("");
 }
+
+static void
+jabber_google_stun_lookup_cb(GSList *hosts, gpointer data, 
+	const char *error_message)
+{
+	JabberStream *js = (JabberStream *) data;
+
+	if (error_message) {
+		purple_debug_error("jabber", "Google STUN lookup failed: %s\n",
+			error_message);
+		g_slist_free(hosts);
+		return;
+	}
+
+	if (hosts && g_slist_next(hosts)) {
+		struct sockaddr *addr = g_slist_next(hosts)->data; 
+		char dst[INET6_ADDRSTRLEN];
+		int port;
+
+		if (addr->sa_family == AF_INET6) {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, 
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
+		} else {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, 
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in *) addr)->sin_port);
+		}
+
+		if (js) {
+			if (js->stun_ip) {
+				g_free(js->stun_ip);
+			}
+			js->stun_ip = g_strdup(dst);
+			purple_debug_info("jabber", "set Google STUN IP address: %s\n", dst);
+			js->stun_port = port;
+			purple_debug_info("jabber", "set Google STUN port: %d\n", port);
+			purple_debug_info("jabber", "set Google STUN port: %d\n", port);
+			/* unmark ongoing query */
+			js->stun_query = NULL;
+		}
+	}
+
+	g_slist_free(hosts);
+}
+
+static void
+jabber_google_jingle_info_cb(JabberStream *js, xmlnode *result,
+	gpointer nullus)
+{	
+	if (result) {
+		const xmlnode *query = 
+			xmlnode_get_child_with_namespace(result, "query", 
+				GOOGLE_JINGLE_INFO_NAMESPACE);
+
+		if (query) {
+			const xmlnode *stun = xmlnode_get_child(query, "stun");
+
+			purple_debug_info("jabber", "got google:jingleinfo\n");
+
+			if (stun) {
+				xmlnode *server = xmlnode_get_child(stun, "server");
+
+				if (server) {
+					const gchar *host = xmlnode_get_attrib(server, "host");
+					const gchar *udp = xmlnode_get_attrib(server, "udp");
+
+					if (host && udp) {
+						int port = atoi(udp);
+						/* if there, would already be an ongoing query, 
+						 cancel it */
+						if (js->stun_query)
+							purple_dnsquery_destroy(js->stun_query);
+
+						js->stun_query = purple_dnsquery_a(host, port, 
+							jabber_google_stun_lookup_cb, js);
+					}
+				}
+			}
+			/* should perhaps handle relays later on, or maybe wait until
+			 Google supports a common standard... */
+		}
+	}
+}
+
+void
+jabber_google_handle_jingle_info(JabberStream *js, xmlnode *packet)
+{
+	jabber_google_jingle_info_cb(js, packet, NULL);
+}
+
+void
+jabber_google_send_jingle_info(JabberStream *js)
+{
+	JabberIq *jingle_info = 
+		jabber_iq_new_query(js, JABBER_IQ_GET, GOOGLE_JINGLE_INFO_NAMESPACE);
+
+	jabber_iq_set_callback(jingle_info, jabber_google_jingle_info_cb,
+		NULL);
+	purple_debug_info("jabber", "sending google:jingleinfo query\n");
+	jabber_iq_send(jingle_info);
+}
--- a/libpurple/protocols/jabber/google.h	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/google.h	Thu Feb 05 00:31:35 2009 +0000
@@ -25,6 +25,9 @@
  * such that they don't intermingle with code for the XMPP RFCs and XEPs :) */
 
 #include "jabber.h"
+#include "media.h"
+
+#define GOOGLE_JINGLE_INFO_NAMESPACE "google:jingleinfo"
 
 void jabber_gmail_init(JabberStream *js);
 void jabber_gmail_poke(JabberStream *js, xmlnode *node);
@@ -45,6 +48,10 @@
 
 char *jabber_google_format_to_html(const char *text);
 
+PurpleMedia *jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type);
+void jabber_google_session_parse(JabberStream *js, xmlnode *node);
 
+void jabber_google_handle_jingle_info(JabberStream *js, xmlnode *packet);
+void jabber_google_send_jingle_info(JabberStream *js);
 
 #endif   /* _PURPLE_GOOGLE_H_ */
--- a/libpurple/protocols/jabber/iq.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Thu Feb 05 00:31:35 2009 +0000
@@ -28,6 +28,7 @@
 #include "disco.h"
 #include "google.h"
 #include "iq.h"
+#include "jingle/jingle.h"
 #include "oob.h"
 #include "roster.h"
 #include "si.h"
@@ -370,6 +371,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);
@@ -392,6 +398,13 @@
 		jabber_data_parse(js, packet);
 		return;
 	}
+	
+#ifdef USE_VV
+	if (xmlnode_get_child_with_namespace(packet, "jingle", JINGLE)) {
+		jingle_parse(js, packet);
+		return;
+	}
+#endif
 
 	/* If we get here, send the default error reply mandated by XMPP-CORE */
 	if(!strcmp(type, "set") || !strcmp(type, "get")) {
@@ -432,6 +445,12 @@
 	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(JINGLE, jingle_parse);
+#endif
+	/* handle Google jingleinfo */
+	jabber_iq_register_handler(GOOGLE_JINGLE_INFO_NAMESPACE, 
+		jabber_google_handle_jingle_info);
 }
 
 void jabber_iq_uninit(void)
--- a/libpurple/protocols/jabber/jabber.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Feb 05 00:31:35 2009 +0000
@@ -59,6 +59,15 @@
 #include "pep.h"
 #include "adhoccommands.h"
 
+#include "jingle/jingle.h"
+#include "jingle/rtp.h"
+
+#ifdef USE_VV
+#include <gst/farsight/fs-conference-iface.h>
+
+#define GTALK_CAP "http://www.google.com/xmpp/protocol/voice/v1"
+
+#endif
 
 #define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5)
 
@@ -722,6 +731,13 @@
 	js->old_length = 0;
 	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
+
+	js->stun_ip = NULL;
+	js->stun_port = 0;
+	js->stun_query = NULL;
 
 	if(!js->user) {
 		purple_connection_error_reason (gc,
@@ -729,14 +745,14 @@
 			_("Invalid XMPP ID"));
 		return;
 	}
-	
+
 	if (!js->user->domain || *(js->user->domain) == '\0') {
 		purple_connection_error_reason (gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID. Domain must be set."));
 		return;
 	}
-	
+
 	if((my_jb = jabber_buddy_find(js, purple_account_get_username(account), TRUE)))
 		my_jb->subscription |= JABBER_SUB_BOTH;
 
@@ -1210,6 +1226,10 @@
 	server = connect_server[0] ? connect_server : js->user->domain;
 	js->certificate_CN = g_strdup(server);
 
+	js->stun_ip = NULL;
+	js->stun_port = 0;
+	js->stun_query = NULL;
+
 	jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
 
 	if(purple_account_get_bool(account, "old_ssl", FALSE)) {
@@ -1307,6 +1327,11 @@
 {
 	JabberStream *js = gc->proto_data;
 
+#ifdef USE_VV
+	/* Close all of the open Jingle sessions on this stream */
+	jingle_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.
@@ -1408,6 +1433,15 @@
 	g_free(js->srv_rec);
 	js->srv_rec = NULL;
 
+	g_free(js->stun_ip);
+	js->stun_ip = NULL;
+
+	/* cancel DNS query for STUN, if one is ongoing */
+	if (js->stun_query) {
+		purple_dnsquery_destroy(js->stun_query);
+		js->stun_query = NULL;
+	}
+		
 	g_free(js);
 
 	gc->proto_data = NULL;
@@ -2046,7 +2080,7 @@
 	JabberID *jid;
 	JabberBuddy *jb;
 	JabberBuddyResource *jbr;
-	
+
 	if(!(jid = jabber_id_new(who)))
 		return;
 
@@ -2526,6 +2560,86 @@
 {
 	return TRUE;
 }
+#ifdef USE_VV
+
+PurpleMedia *
+jabber_initiate_media(PurpleConnection *gc, const char *who, 
+		      PurpleMediaSessionType type)
+{
+	JabberStream *js = (JabberStream *) gc->proto_data;
+	JabberBuddy *jb;
+
+	if (!js) {
+		purple_debug_error("jabber",
+				"jabber_initiate_media: NULL stream\n");
+		return NULL;
+	}
+
+	jb = jabber_buddy_find(js, who, FALSE);
+
+	if (!jb) {
+		purple_debug_error("jabber", "Could not find buddy\n");
+		return NULL;
+	}
+
+	if (type & PURPLE_MEDIA_AUDIO &&
+			!jabber_buddy_has_capability(jb,
+			JINGLE_APP_RTP_SUPPORT_AUDIO) &&
+			jabber_buddy_has_capability(jb, GTALK_CAP))
+		return jabber_google_session_initiate(gc->proto_data, who, type);
+	else
+		return jingle_rtp_initiate_media(gc->proto_data, who, type);
+}
+
+gboolean jabber_can_do_media(PurpleConnection *gc, const char *who, 
+                             PurpleMediaSessionType 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;
+	}
+
+	if (!jabber_buddy_has_capability(jb, JINGLE_TRANSPORT_ICEUDP) &&
+			!jabber_buddy_has_capability(jb,
+			JINGLE_TRANSPORT_RAWUDP) &&
+			!jabber_buddy_has_capability(jb, GTALK_CAP)) {
+		purple_debug_info("jingle-rtp", "Buddy doesn't support "
+				"the same transport types\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, JINGLE_APP_RTP_SUPPORT_AUDIO) ||
+				jabber_buddy_has_capability(jb, GTALK_CAP)) && 
+				jabber_buddy_has_capability(jb, JINGLE_APP_RTP_SUPPORT_VIDEO);
+	} else if (type == (PURPLE_MEDIA_AUDIO)) {
+		purple_debug_info("jabber", 
+				  "Checking audio XEP support for %s\n", who);
+		return jabber_buddy_has_capability(jb, JINGLE_APP_RTP_SUPPORT_AUDIO) ||
+				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, JINGLE_APP_RTP_SUPPORT_VIDEO);
+	}
+
+	return FALSE;
+}
+
+#endif
 
 void jabber_register_commands(void)
 {
--- a/libpurple/protocols/jabber/jabber.h	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Thu Feb 05 00:31:35 2009 +0000
@@ -54,8 +54,11 @@
 #include "circbuffer.h"
 #include "connection.h"
 #include "dnssrv.h"
+#include "media.h"
+#include "mediamanager.h"
 #include "roomlist.h"
 #include "sslconn.h"
+#include "dnsquery.h"
 
 #include "jutil.h"
 #include "xmlnode.h"
@@ -242,6 +245,18 @@
 	 * for when we lookup buddy icons from a url
 	 */
 	GSList *url_datas;
+
+	/* keep a hash table of JingleSessions */
+	GHashTable *sessions;
+#ifdef USE_VV
+	GHashTable *medias;
+#endif
+
+	/* maybe this should only be present when USE_VV? */
+	gchar *stun_ip;
+	int stun_port;
+	PurpleDnsQueryData *stun_query;
+	/* later add stuff to handle TURN relays... */
 };
 
 typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
@@ -312,4 +327,9 @@
 void jabber_register_commands(void);
 void jabber_init_plugin(PurplePlugin *plugin);
 
+#ifdef USE_VV
+PurpleMedia *jabber_initiate_media(PurpleConnection *gc, const char *who, PurpleMediaSessionType type);
+gboolean jabber_can_do_media(PurpleConnection *gc, const char *who, PurpleMediaSessionType type);
+#endif
+
 #endif /* _PURPLE_JABBER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/content.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,456 @@
+/**
+ * @file content.c
+ *
+ * purple
+ *
+ * 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 "internal.h"
+
+#include "debug.h"
+#include "content.h"
+#include "jingle.h"
+
+#include <string.h>
+
+struct _JingleContentPrivate
+{
+	JingleSession *session;
+	gchar *description_type;
+	gchar *creator;
+	gchar *disposition;
+	gchar *name;
+	gchar *senders;
+	JingleTransport *transport;
+	JingleTransport *pending_transport;
+};
+
+#define JINGLE_CONTENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_CONTENT, JingleContentPrivate))
+
+static void jingle_content_class_init (JingleContentClass *klass);
+static void jingle_content_init (JingleContent *content);
+static void jingle_content_finalize (GObject *object);
+static void jingle_content_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void jingle_content_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static xmlnode *jingle_content_to_xml_internal(JingleContent *content, xmlnode *jingle, JingleActionType action);
+static JingleContent *jingle_content_parse_internal(xmlnode *content);
+
+static GObjectClass *parent_class = NULL;
+
+enum {
+	PROP_0,
+	PROP_SESSION,
+	PROP_CREATOR,
+	PROP_DISPOSITION,
+	PROP_NAME,
+	PROP_SENDERS,
+	PROP_TRANSPORT,
+	PROP_PENDING_TRANSPORT,
+};
+
+GType
+jingle_content_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(JingleContentClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) jingle_content_class_init,
+			NULL,
+			NULL,
+			sizeof(JingleContent),
+			0,
+			(GInstanceInitFunc) jingle_content_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "JingleContent", &info, 0);
+	}
+	return type;
+}
+
+static void
+jingle_content_class_init (JingleContentClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+	
+	gobject_class->finalize = jingle_content_finalize;
+	gobject_class->set_property = jingle_content_set_property;
+	gobject_class->get_property = jingle_content_get_property;
+	klass->to_xml = jingle_content_to_xml_internal;
+	klass->parse = jingle_content_parse_internal;
+
+	g_object_class_install_property(gobject_class, PROP_SESSION,
+			g_param_spec_object("session",
+			"Jingle Session",
+			"The jingle session parent of this content.",
+			JINGLE_TYPE_SESSION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_CREATOR,
+			g_param_spec_string("creator",
+			"Creator",
+			"The participant that created this content.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_DISPOSITION,
+			g_param_spec_string("disposition",
+			"Disposition",
+			"The disposition of the content.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_NAME,
+			g_param_spec_string("name",
+			"Name",
+			"The name of this content.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_SENDERS,
+			g_param_spec_string("senders",
+			"Senders",
+			"The sender of this content.",
+			NULL,
+			G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_TRANSPORT,
+			g_param_spec_object("transport",
+			"transport",
+			"The transport of this content.",
+			JINGLE_TYPE_TRANSPORT,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_PENDING_TRANSPORT,
+			g_param_spec_object("pending-transport",
+			"Pending transport",
+			"The pending transport contained within this content",
+			JINGLE_TYPE_TRANSPORT,
+			G_PARAM_READWRITE));
+
+	g_type_class_add_private(klass, sizeof(JingleContentPrivate));
+}
+
+static void
+jingle_content_init (JingleContent *content)
+{
+	content->priv = JINGLE_CONTENT_GET_PRIVATE(content);
+	memset(content->priv, 0, sizeof(content->priv));
+}
+
+static void
+jingle_content_finalize (GObject *content)
+{
+	JingleContentPrivate *priv = JINGLE_CONTENT_GET_PRIVATE(content);
+	purple_debug_info("jingle","jingle_content_finalize\n");
+	
+	g_free(priv->description_type);
+	g_free(priv->creator);
+	g_free(priv->disposition);
+	g_free(priv->name);
+	g_free(priv->senders);
+	g_object_unref(priv->transport);
+	if (priv->pending_transport)
+		g_object_unref(priv->pending_transport);
+
+	parent_class->finalize(content);
+}
+
+static void
+jingle_content_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	JingleContent *content;
+	g_return_if_fail(JINGLE_IS_CONTENT(object));
+
+	content = JINGLE_CONTENT(object);
+
+	switch (prop_id) {
+		case PROP_SESSION:
+			content->priv->session = g_value_get_object(value);
+			break;
+		case PROP_CREATOR:
+			g_free(content->priv->creator);
+			content->priv->creator = g_value_dup_string(value);
+			break;
+		case PROP_DISPOSITION:
+			g_free(content->priv->disposition);
+			content->priv->disposition = g_value_dup_string(value);
+			break;
+		case PROP_NAME:
+			g_free(content->priv->name);
+			content->priv->name = g_value_dup_string(value);
+			break;
+		case PROP_SENDERS:
+			g_free(content->priv->senders);
+			content->priv->senders = g_value_dup_string(value);
+			break;
+		case PROP_TRANSPORT:
+			if (content->priv->transport)
+				g_object_unref(content->priv->transport);
+			content->priv->transport = g_value_get_object(value);
+			g_object_ref(content->priv->transport);
+			break;
+		case PROP_PENDING_TRANSPORT:
+			if (content->priv->pending_transport)
+				g_object_unref(content->priv->pending_transport);
+			content->priv->pending_transport = g_value_get_object(value);
+			g_object_ref(content->priv->pending_transport);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+jingle_content_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	JingleContent *content;
+	g_return_if_fail(JINGLE_IS_CONTENT(object));
+	
+	content = JINGLE_CONTENT(object);
+
+	switch (prop_id) {
+		case PROP_SESSION:
+			g_value_set_object(value, content->priv->session);
+			break;
+		case PROP_CREATOR:
+			g_value_set_string(value, content->priv->creator);
+			break;
+		case PROP_DISPOSITION:
+			g_value_set_string(value, content->priv->disposition);
+			break;
+		case PROP_NAME:
+			g_value_set_string(value, content->priv->name);
+			break;
+		case PROP_SENDERS:
+			g_value_set_string(value, content->priv->senders);
+			break;
+		case PROP_TRANSPORT:
+			g_value_set_object(value, content->priv->transport);
+			break;
+		case PROP_PENDING_TRANSPORT:
+			g_value_set_object(value, content->priv->pending_transport);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+JingleContent *
+jingle_content_create(const gchar *type, const gchar *creator,
+		const gchar *disposition, const gchar *name,
+		const gchar *senders, JingleTransport *transport)
+{
+	
+
+	JingleContent *content = g_object_new(jingle_get_type(type),
+			"creator", creator,
+			"disposition", disposition != NULL ? disposition : "session",
+			"name", name,
+			"senders", senders != NULL ? senders : "both",
+			"transport", transport,
+			NULL);
+	return content;
+}
+
+JingleSession *jingle_content_get_session(JingleContent *content)
+{
+	JingleSession *session;
+	g_object_get(content, "session", &session, NULL);
+	return session;
+}
+
+const gchar *
+jingle_content_get_description_type(JingleContent *content)
+{
+	return JINGLE_CONTENT_GET_CLASS(content)->description_type;
+}
+
+gchar *
+jingle_content_get_creator(JingleContent *content)
+{
+	gchar *creator;
+	g_object_get(content, "creator", &creator, NULL);
+	return creator;
+}
+
+gchar *
+jingle_content_get_disposition(JingleContent *content)
+{
+	gchar *disposition;
+	g_object_get(content, "disposition", &disposition, NULL);
+	return disposition;
+}
+
+gchar *
+jingle_content_get_name(JingleContent *content)
+{
+	gchar *name;
+	g_object_get(content, "name", &name, NULL);
+	return name;
+}
+
+gchar *
+jingle_content_get_senders(JingleContent *content)
+{
+	gchar *senders;
+	g_object_get(content, "senders", &senders, NULL);
+	return senders;
+}
+
+JingleTransport *
+jingle_content_get_transport(JingleContent *content)
+{
+	JingleTransport *transport;
+	g_object_get(content, "transport", &transport, NULL);
+	return transport;
+}
+
+void
+jingle_content_set_session(JingleContent *content, JingleSession *session)
+{
+	JINGLE_IS_CONTENT(content);
+	JINGLE_IS_SESSION(session);
+	g_object_set(content, "session", session, NULL);
+}
+
+JingleTransport *
+jingle_content_get_pending_transport(JingleContent *content)
+{
+	JingleTransport *pending_transport;
+	g_object_get(content, "pending_transport", &pending_transport, NULL);
+	return pending_transport;
+}
+
+void
+jingle_content_set_pending_transport(JingleContent *content, JingleTransport *transport)
+{
+	g_object_set(content, "pending-transport", transport, NULL);
+}
+
+void
+jingle_content_accept_transport(JingleContent *content)
+{
+	if (content->priv->transport)
+		g_object_unref(content->priv->transport);
+	content->priv->transport = content->priv->pending_transport;
+	content->priv->pending_transport = NULL;
+}
+
+void
+jingle_content_remove_pending_transport(JingleContent *content)
+{
+	if (content->priv->pending_transport) {
+		g_object_unref(content->priv->pending_transport);
+		content->priv->pending_transport = NULL;
+	}
+}
+
+void
+jingle_content_modify(JingleContent *content, const gchar *senders)
+{
+	g_object_set(content, "senders", senders, NULL);
+}
+
+static JingleContent *
+jingle_content_parse_internal(xmlnode *content)
+{
+	xmlnode *description = xmlnode_get_child(content, "description");
+	const gchar *type = xmlnode_get_namespace(description);
+	const gchar *creator = xmlnode_get_attrib(content, "creator");
+	const gchar *disposition = xmlnode_get_attrib(content, "disposition");
+	const gchar *senders = xmlnode_get_attrib(content, "senders");
+	const gchar *name = xmlnode_get_attrib(content, "name");
+	JingleTransport *transport =
+			jingle_transport_parse(xmlnode_get_child(content, "transport"));
+
+	if (senders == NULL)
+		senders = "both";
+
+	return jingle_content_create(type, creator, disposition, name, senders, transport);
+}
+
+JingleContent *
+jingle_content_parse(xmlnode *content)
+{
+	const gchar *type = xmlnode_get_namespace(xmlnode_get_child(content, "description"));
+	return JINGLE_CONTENT_CLASS(g_type_class_ref(jingle_get_type(type)))->parse(content);
+}
+
+static xmlnode *
+jingle_content_to_xml_internal(JingleContent *content, xmlnode *jingle, JingleActionType action)
+{
+	xmlnode *node = xmlnode_new_child(jingle, "content");
+	gchar *creator = jingle_content_get_creator(content);
+	gchar *name = jingle_content_get_name(content);
+	gchar *senders = jingle_content_get_senders(content);
+	gchar *disposition = jingle_content_get_disposition(content);
+
+	xmlnode_set_attrib(node, "creator", creator);
+	xmlnode_set_attrib(node, "name", name);
+	xmlnode_set_attrib(node, "senders", senders);
+	if (strcmp("session", disposition))
+		xmlnode_set_attrib(node, "disposition", disposition);
+
+	g_free(disposition);
+	g_free(senders);
+	g_free(name);
+	g_free(creator);
+
+	if (action != JINGLE_CONTENT_REMOVE) {
+		JingleTransport *transport;
+
+		if (action != JINGLE_TRANSPORT_ACCEPT &&
+				action != JINGLE_TRANSPORT_INFO &&
+				action != JINGLE_TRANSPORT_REJECT &&
+				action != JINGLE_TRANSPORT_REPLACE) {
+			xmlnode *description = xmlnode_new_child(node, "description");
+
+			xmlnode_set_namespace(description,
+					jingle_content_get_description_type(content));
+		}
+
+		if (action != JINGLE_TRANSPORT_REJECT && action == JINGLE_TRANSPORT_REPLACE)
+			transport = jingle_content_get_pending_transport(content);
+		else
+			transport = jingle_content_get_transport(content);
+
+		jingle_transport_to_xml(transport, node, action);
+	}
+
+	return node;
+}
+
+xmlnode *
+jingle_content_to_xml(JingleContent *content, xmlnode *jingle, JingleActionType action)
+{
+	g_return_val_if_fail(JINGLE_IS_CONTENT(content), NULL);
+	return JINGLE_CONTENT_GET_CLASS(content)->to_xml(content, jingle, action);
+}
+
+void
+jingle_content_handle_action(JingleContent *content, xmlnode *xmlcontent, JingleActionType action)
+{
+	g_return_if_fail(JINGLE_IS_CONTENT(content));
+	JINGLE_CONTENT_GET_CLASS(content)->handle_action(content, xmlcontent, action);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/content.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,117 @@
+/**
+ * @file content.h
+ *
+ * purple
+ *
+ * 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 JINGLE_CONTENT_H
+#define JINGLE_CONTENT_H
+
+
+#include "jabber.h"
+#include "jingle.h"
+#include "session.h"
+#include "transport.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define JINGLE_TYPE_CONTENT            (jingle_content_get_type())
+#define JINGLE_CONTENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_CONTENT, JingleContent))
+#define JINGLE_CONTENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_CONTENT, JingleContentClass))
+#define JINGLE_IS_CONTENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_CONTENT))
+#define JINGLE_IS_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_CONTENT))
+#define JINGLE_CONTENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_CONTENT, JingleContentClass))
+
+/** @copydoc _JingleContent */
+typedef struct _JingleContent JingleContent;
+/** @copydoc _JingleContentClass */
+typedef struct _JingleContentClass JingleContentClass;
+/** @copydoc _JingleContentPrivate */
+typedef struct _JingleContentPrivate JingleContentPrivate;
+
+/** The content class */
+struct _JingleContentClass
+{
+	GObjectClass parent_class;     /**< The parent class. */
+
+	xmlnode *(*to_xml) (JingleContent *content, xmlnode *jingle, JingleActionType action);
+	JingleContent *(*parse) (xmlnode *content);
+	void (*handle_action) (JingleContent *content, xmlnode *xmlcontent, JingleActionType action);
+	const gchar *description_type;
+};
+
+/** The content class's private data */
+struct _JingleContent
+{
+	GObject parent;                /**< The parent of this object. */
+	JingleContentPrivate *priv;      /**< The private data of this object. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the content class's GType
+ *
+ * @return The content class's GType.
+ */
+GType jingle_content_get_type(void);
+
+JingleContent *jingle_content_create(const gchar *type, const gchar *creator,
+		const gchar *disposition, const gchar *name,
+		const gchar *senders, JingleTransport *transport);
+
+JingleSession *jingle_content_get_session(JingleContent *content);
+const gchar *jingle_content_get_description_type(JingleContent *content);
+gchar *jingle_content_get_creator(JingleContent *content);
+gchar *jingle_content_get_disposition(JingleContent *content);
+gchar *jingle_content_get_name(JingleContent *content);
+gchar *jingle_content_get_senders(JingleContent *content);
+JingleTransport *jingle_content_get_transport(JingleContent *content);
+JingleTransport *jingle_content_get_pending_transport(JingleContent *content);
+
+void jingle_content_set_session(JingleContent *content, JingleSession *session);
+void jingle_content_set_pending_transport(JingleContent *content, JingleTransport *transport);
+void jingle_content_accept_transport(JingleContent *content);
+void jingle_content_remove_pending_transport(JingleContent *content);
+void jingle_content_modify(JingleContent *content, const gchar *senders);
+
+#define jingle_content_create_content_accept(session) \
+	jingle_session_to_packet(session, JINGLE_CONTENT_ACCEPT)
+#define jingle_content_create_content_add(session) \
+	jingle_session_to_packet(session, JINGLE_CONTENT_ADD)
+#define jingle_content_create_content_modify(session) \
+	jingle_session_to_packet(session, JINGLE_CONTENT_MODIFY)
+#define jingle_content_create_content_remove(session) \
+	jingle_session_to_packet(session, JINGLE_CONTENT_REMOVE)
+
+JingleContent *jingle_content_parse(xmlnode *content);
+xmlnode *jingle_content_to_xml(JingleContent *content, xmlnode *jingle, JingleActionType action);
+void jingle_content_handle_action(JingleContent *content, xmlnode *xmlcontent, JingleActionType action);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_CONTENT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/iceudp.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,376 @@
+/**
+ * @file iceudp.c
+ *
+ * purple
+ *
+ * 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 "internal.h"
+
+#include "iceudp.h"
+#include "jingle.h"
+#include "debug.h"
+
+#include <string.h>
+
+struct _JingleIceUdpPrivate
+{
+	GList *local_candidates;
+	GList *remote_candidates;
+};
+
+#define JINGLE_ICEUDP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_ICEUDP, JingleIceUdpPrivate))
+
+static void jingle_iceudp_class_init (JingleIceUdpClass *klass);
+static void jingle_iceudp_init (JingleIceUdp *iceudp);
+static void jingle_iceudp_finalize (GObject *object);
+static void jingle_iceudp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void jingle_iceudp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static JingleTransport *jingle_iceudp_parse_internal(xmlnode *iceudp);
+static xmlnode *jingle_iceudp_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action);
+
+static JingleTransportClass *parent_class = NULL;
+
+enum {
+	PROP_0,
+	PROP_LOCAL_CANDIDATES,
+	PROP_REMOTE_CANDIDATES,
+};
+
+static JingleIceUdpCandidate *
+jingle_iceudp_candidate_copy(JingleIceUdpCandidate *candidate)
+{
+	JingleIceUdpCandidate *new_candidate = g_new0(JingleIceUdpCandidate, 1);
+	new_candidate->component = candidate->component;
+	new_candidate->foundation = g_strdup(candidate->foundation);
+	new_candidate->generation = candidate->generation;
+	new_candidate->ip = g_strdup(candidate->ip);
+	new_candidate->network = candidate->network;
+	new_candidate->port = candidate->port;
+	new_candidate->priority = candidate->priority;
+	new_candidate->protocol = g_strdup(candidate->protocol);
+	new_candidate->type = g_strdup(candidate->type);
+
+	new_candidate->username = g_strdup(candidate->username);
+	new_candidate->password = g_strdup(candidate->password);
+
+	return new_candidate;
+}
+
+static void
+jingle_iceudp_candidate_free(JingleIceUdpCandidate *candidate)
+{
+	g_free(candidate->foundation);
+	g_free(candidate->ip);
+	g_free(candidate->protocol);
+	g_free(candidate->type);
+
+	g_free(candidate->username);
+	g_free(candidate->password);
+}
+
+GType
+jingle_iceudp_candidate_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		type = g_boxed_type_register_static("JingleIceUdpCandidate",
+				(GBoxedCopyFunc)jingle_iceudp_candidate_copy,
+				(GBoxedFreeFunc)jingle_iceudp_candidate_free);
+	}
+	return type;
+}
+
+JingleIceUdpCandidate *
+jingle_iceudp_candidate_new(guint component, const gchar *foundation,
+		guint generation, const gchar *ip, guint network,
+		guint port, guint priority, const gchar *protocol,
+		const gchar *type, const gchar *username, const gchar *password)
+{
+	JingleIceUdpCandidate *candidate = g_new0(JingleIceUdpCandidate, 1);
+	candidate->component = component;
+	candidate->foundation = g_strdup(foundation);
+	candidate->generation = generation;
+	candidate->ip = g_strdup(ip);
+	candidate->network = network;
+	candidate->port = port;
+	candidate->priority = priority;
+	candidate->protocol = g_strdup(protocol);
+	candidate->type = g_strdup(type);
+
+	candidate->username = g_strdup(username);
+	candidate->password = g_strdup(password);
+	return candidate;
+}
+
+GType
+jingle_iceudp_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(JingleIceUdpClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) jingle_iceudp_class_init,
+			NULL,
+			NULL,
+			sizeof(JingleIceUdp),
+			0,
+			(GInstanceInitFunc) jingle_iceudp_init,
+			NULL
+		};
+		type = g_type_register_static(JINGLE_TYPE_TRANSPORT, "JingleIceUdp", &info, 0);
+	}
+	return type;
+}
+
+static void
+jingle_iceudp_class_init (JingleIceUdpClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = jingle_iceudp_finalize;
+	gobject_class->set_property = jingle_iceudp_set_property;
+	gobject_class->get_property = jingle_iceudp_get_property;
+	klass->parent_class.to_xml = jingle_iceudp_to_xml_internal;
+	klass->parent_class.parse = jingle_iceudp_parse_internal;
+	klass->parent_class.transport_type = JINGLE_TRANSPORT_ICEUDP;
+
+	g_object_class_install_property(gobject_class, PROP_LOCAL_CANDIDATES,
+			g_param_spec_pointer("local-candidates",
+			"Local candidates",
+			"The local candidates for this transport.",
+			G_PARAM_READABLE));
+
+	g_object_class_install_property(gobject_class, PROP_REMOTE_CANDIDATES,
+			g_param_spec_pointer("remote-candidates",
+			"Remote candidates",
+			"The remote candidates for this transport.",
+			G_PARAM_READABLE));
+
+	g_type_class_add_private(klass, sizeof(JingleIceUdpPrivate));
+}
+
+static void
+jingle_iceudp_init (JingleIceUdp *iceudp)
+{
+	iceudp->priv = JINGLE_ICEUDP_GET_PRIVATE(iceudp);
+	memset(iceudp->priv, 0, sizeof(iceudp->priv));
+}
+
+static void
+jingle_iceudp_finalize (GObject *iceudp)
+{
+/*	JingleIceUdpPrivate *priv = JINGLE_ICEUDP_GET_PRIVATE(iceudp); */
+	purple_debug_info("jingle","jingle_iceudp_finalize\n");
+}
+
+static void
+jingle_iceudp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	JingleIceUdp *iceudp;
+	g_return_if_fail(JINGLE_IS_ICEUDP(object));
+
+	iceudp = JINGLE_ICEUDP(object);
+
+	switch (prop_id) {
+		case PROP_LOCAL_CANDIDATES:
+			iceudp->priv->local_candidates =
+					g_value_get_pointer(value);
+			break;
+		case PROP_REMOTE_CANDIDATES:
+			iceudp->priv->remote_candidates =
+					g_value_get_pointer(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+jingle_iceudp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	JingleIceUdp *iceudp;
+	g_return_if_fail(JINGLE_IS_ICEUDP(object));
+	
+	iceudp = JINGLE_ICEUDP(object);
+
+	switch (prop_id) {
+		case PROP_LOCAL_CANDIDATES:
+			g_value_set_pointer(value, iceudp->priv->local_candidates);
+			break;
+		case PROP_REMOTE_CANDIDATES:
+			g_value_set_pointer(value, iceudp->priv->remote_candidates);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+void
+jingle_iceudp_add_local_candidate(JingleIceUdp *iceudp, JingleIceUdpCandidate *candidate)
+{
+	GList *iter = iceudp->priv->local_candidates;
+
+	for (; iter; iter = g_list_next(iter)) {
+		JingleIceUdpCandidate *c = iter->data;
+		if ((c->component == candidate->component) &&
+				!strcmp(c->foundation, candidate->foundation)) {
+			guint generation = c->generation + 1;
+
+			g_boxed_free(JINGLE_TYPE_ICEUDP_CANDIDATE, c);
+			iceudp->priv->local_candidates = g_list_delete_link(
+					iceudp->priv->local_candidates, iter);
+
+			candidate->generation = generation;
+
+			iceudp->priv->local_candidates = g_list_append(
+					iceudp->priv->local_candidates, candidate);
+			return;
+		}
+	}
+
+	iceudp->priv->local_candidates = g_list_append(
+			iceudp->priv->local_candidates, candidate);
+}
+
+GList *
+jingle_iceudp_get_remote_candidates(JingleIceUdp *iceudp)
+{
+	return g_list_copy(iceudp->priv->remote_candidates);
+}
+
+static JingleIceUdpCandidate *
+jingle_iceudp_get_remote_candidate_by_id(JingleIceUdp *iceudp,
+		guint component, const gchar *foundation)
+{
+	GList *iter = iceudp->priv->remote_candidates;
+	for (; iter; iter = g_list_next(iter)) {
+		JingleIceUdpCandidate *candidate = iter->data;
+		if ((candidate->component == component) &&
+				!strcmp(candidate->foundation, foundation)) {
+			return candidate;
+		}
+	}
+	return NULL;
+}
+
+static void
+jingle_iceudp_add_remote_candidate(JingleIceUdp *iceudp, JingleIceUdpCandidate *candidate)
+{
+	JingleIceUdpPrivate *priv = JINGLE_ICEUDP_GET_PRIVATE(iceudp);
+	JingleIceUdpCandidate *iceudp_candidate =
+			jingle_iceudp_get_remote_candidate_by_id(iceudp,
+					candidate->component, candidate->foundation);
+	if (iceudp_candidate != NULL) {
+		priv->remote_candidates = g_list_remove(
+				priv->remote_candidates, iceudp_candidate);
+		g_boxed_free(JINGLE_TYPE_ICEUDP_CANDIDATE, iceudp_candidate);
+	}
+	priv->remote_candidates = g_list_append(priv->remote_candidates, candidate);
+}
+
+static JingleTransport *
+jingle_iceudp_parse_internal(xmlnode *iceudp)
+{
+	JingleTransport *transport = parent_class->parse(iceudp);
+	xmlnode *candidate = xmlnode_get_child(iceudp, "candidate");
+	JingleIceUdpCandidate *iceudp_candidate = NULL;
+
+	const gchar *username = xmlnode_get_attrib(iceudp, "ufrag");
+	const gchar *password = xmlnode_get_attrib(iceudp, "pwd");
+
+	for (; candidate; candidate = xmlnode_get_next_twin(candidate)) {
+		iceudp_candidate = jingle_iceudp_candidate_new(
+				atoi(xmlnode_get_attrib(candidate, "component")),
+				xmlnode_get_attrib(candidate, "foundation"),
+				atoi(xmlnode_get_attrib(candidate, "generation")),
+				xmlnode_get_attrib(candidate, "ip"),
+				atoi(xmlnode_get_attrib(candidate, "network")),
+				atoi(xmlnode_get_attrib(candidate, "port")),
+				atoi(xmlnode_get_attrib(candidate, "priority")),
+				xmlnode_get_attrib(candidate, "protocol"),
+				xmlnode_get_attrib(candidate, "type"),
+				username, password);
+		jingle_iceudp_add_remote_candidate(JINGLE_ICEUDP(transport), iceudp_candidate);
+	}
+
+	return transport;
+}
+
+static xmlnode *
+jingle_iceudp_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action)
+{
+	xmlnode *node = parent_class->to_xml(transport, content, action);
+
+	if (action == JINGLE_SESSION_INITIATE || action == JINGLE_TRANSPORT_INFO ||
+			action == JINGLE_CONTENT_ADD || action == JINGLE_TRANSPORT_REPLACE) {
+		JingleIceUdpCandidate *candidate = JINGLE_ICEUDP_GET_PRIVATE(
+				transport)->local_candidates->data;
+		xmlnode_set_attrib(node, "pwd", candidate->password);
+		xmlnode_set_attrib(node, "ufrag", candidate->username);
+	}
+
+	if (action == JINGLE_TRANSPORT_INFO || action == JINGLE_SESSION_ACCEPT) {
+		JingleIceUdpPrivate *priv = JINGLE_ICEUDP_GET_PRIVATE(transport);
+		GList *iter = priv->local_candidates;
+
+		for (; iter; iter = g_list_next(iter)) {
+			JingleIceUdpCandidate *candidate = iter->data;
+
+			xmlnode *xmltransport = xmlnode_new_child(node, "candidate");
+			gchar *component = g_strdup_printf("%d", candidate->component);
+			gchar *generation = g_strdup_printf("%d", candidate->generation);
+			gchar *network = g_strdup_printf("%d", candidate->network);
+			gchar *port = g_strdup_printf("%d", candidate->port);
+			gchar *priority = g_strdup_printf("%d", candidate->priority);
+
+			xmlnode_set_attrib(xmltransport, "component", component);
+			xmlnode_set_attrib(xmltransport, "foundation", candidate->foundation);
+			xmlnode_set_attrib(xmltransport, "generation", generation);
+			xmlnode_set_attrib(xmltransport, "ip", candidate->ip);
+			xmlnode_set_attrib(xmltransport, "network", network);
+			xmlnode_set_attrib(xmltransport, "port", port);
+			xmlnode_set_attrib(xmltransport, "priority", priority);
+			xmlnode_set_attrib(xmltransport, "protocol", candidate->protocol);
+
+			if (action == JINGLE_SESSION_ACCEPT) {
+			/* XXX: fix this, it's dummy data */
+				xmlnode_set_attrib(xmltransport, "rel-addr", "10.0.1.1");
+				xmlnode_set_attrib(xmltransport, "rel-port", "8998");
+				xmlnode_set_attrib(xmltransport, "rem-addr", "192.0.2.1");
+				xmlnode_set_attrib(xmltransport, "rem-port", "3478");
+			}
+
+			xmlnode_set_attrib(xmltransport, "type", candidate->type);
+
+			g_free(component);
+			g_free(generation);
+			g_free(network);
+			g_free(port);
+			g_free(priority);
+		}
+	}
+
+	return node;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/iceudp.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,107 @@
+/**
+ * @file iceudp.h
+ *
+ * purple
+ *
+ * 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 JINGLE_ICEUDP_H
+#define JINGLE_ICEUDP_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "transport.h"
+
+G_BEGIN_DECLS
+
+#define JINGLE_TYPE_ICEUDP            (jingle_iceudp_get_type())
+#define JINGLE_TYPE_ICEUDP_CANDIDATE  (jingle_iceudp_candidate_get_type())
+#define JINGLE_ICEUDP(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_ICEUDP, JingleIceUdp))
+#define JINGLE_ICEUDP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_ICEUDP, JingleIceUdpClass))
+#define JINGLE_IS_ICEUDP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_ICEUDP))
+#define JINGLE_IS_ICEUDP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_ICEUDP))
+#define JINGLE_ICEUDP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_ICEUDP, JingleIceUdpClass))
+
+/** @copydoc _JingleIceUdp */
+typedef struct _JingleIceUdp JingleIceUdp;
+/** @copydoc _JingleIceUdpClass */
+typedef struct _JingleIceUdpClass JingleIceUdpClass;
+/** @copydoc _JingleIceUdpPrivate */
+typedef struct _JingleIceUdpPrivate JingleIceUdpPrivate;
+/** @copydoc _JingleIceUdpCandidate */
+typedef struct _JingleIceUdpCandidate JingleIceUdpCandidate;
+
+/** The iceudp class */
+struct _JingleIceUdpClass
+{
+	JingleTransportClass parent_class;     /**< The parent class. */
+
+	xmlnode *(*to_xml) (JingleTransport *transport, xmlnode *content, JingleActionType action);
+	JingleTransport *(*parse) (xmlnode *transport);
+};
+
+/** The iceudp class's private data */
+struct _JingleIceUdp
+{
+	JingleTransport parent;                /**< The parent of this object. */
+	JingleIceUdpPrivate *priv;      /**< The private data of this object. */
+};
+
+struct _JingleIceUdpCandidate
+{
+	guint component;
+	gchar *foundation;
+	guint generation;
+	gchar *ip;
+	guint network;
+	guint port;
+	guint priority;
+	gchar *protocol;
+	gchar *type;
+
+	gchar *username;
+	gchar *password;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+GType jingle_iceudp_candidate_get_type(void);
+
+/**
+ * Gets the iceudp class's GType
+ *
+ * @return The iceudp class's GType.
+ */
+GType jingle_iceudp_get_type(void);
+
+JingleIceUdpCandidate *jingle_iceudp_candidate_new(guint component,
+		const gchar *foundation, guint generation, const gchar *ip,
+		guint network, guint port, guint priority, const gchar *protocol,
+		const gchar *type, const gchar *username, const gchar *password);
+void jingle_iceudp_add_local_candidate(JingleIceUdp *iceudp, JingleIceUdpCandidate *candidate);
+GList *jingle_iceudp_get_remote_candidates(JingleIceUdp *iceudp);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_ICEUDP_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,470 @@
+/*
+ * @file jingle.c
+ *
+ * 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 "internal.h"
+#include "network.h"
+
+#include "content.h"
+#include "debug.h"
+#include "jingle.h"
+#include <string.h>
+#include "session.h"
+#include "iceudp.h"
+#include "rawudp.h"
+#include "rtp.h"
+
+const gchar *
+jingle_get_action_name(JingleActionType action)
+{
+	switch (action) {
+		case JINGLE_CONTENT_ACCEPT:
+			return "content-accept";
+		case JINGLE_CONTENT_ADD:
+			return "content-add";
+		case JINGLE_CONTENT_MODIFY:
+			return "content-modify";
+		case JINGLE_CONTENT_REMOVE:
+			return "content-remove";
+		case JINGLE_SESSION_ACCEPT:
+			return "session-accept";
+		case JINGLE_SESSION_INFO:
+			return "session-info";
+		case JINGLE_SESSION_INITIATE:
+			return "session-initiate";
+		case JINGLE_SESSION_TERMINATE:
+			return "session-terminate";
+		case JINGLE_TRANSPORT_ACCEPT:
+			return "transport-accept";
+		case JINGLE_TRANSPORT_INFO:
+			return "transport-info";
+		case JINGLE_TRANSPORT_REPLACE:
+			return "transport-replace";
+		default:
+			return "unknown-type";
+	}
+}
+
+JingleActionType
+jingle_get_action_type(const gchar *action)
+{
+	if (!strcmp(action, "content-accept"))
+		return JINGLE_CONTENT_ACCEPT;
+	else if (!strcmp(action, "content-add"))
+		return JINGLE_CONTENT_ADD;
+	else if (!strcmp(action, "content-modify"))
+		return JINGLE_CONTENT_MODIFY;
+	else if (!strcmp(action, "content-remove"))
+		return JINGLE_CONTENT_REMOVE;
+	else if (!strcmp(action, "session-accept"))
+		return JINGLE_SESSION_ACCEPT;
+	else if (!strcmp(action, "session-info"))
+		return JINGLE_SESSION_INFO;
+	else if (!strcmp(action, "session-initiate"))
+		return JINGLE_SESSION_INITIATE;
+	else if (!strcmp(action, "session-terminate"))
+		return JINGLE_SESSION_TERMINATE;
+	else if (!strcmp(action, "transport-accept"))
+		return JINGLE_TRANSPORT_ACCEPT;
+	else if (!strcmp(action, "transport-info"))
+		return JINGLE_TRANSPORT_INFO;
+	else if (!strcmp(action, "transport-replace"))
+		return JINGLE_TRANSPORT_REPLACE;
+	else
+		return JINGLE_UNKNOWN_TYPE;
+}
+
+GType
+jingle_get_type(const gchar *type)
+{
+	if (!strcmp(type, JINGLE_TRANSPORT_RAWUDP))
+		return JINGLE_TYPE_RAWUDP;
+	else if (!strcmp(type, JINGLE_TRANSPORT_ICEUDP))
+		return JINGLE_TYPE_ICEUDP;
+#if 0
+	else if (!strcmp(type, JINGLE_TRANSPORT_SOCKS))
+		return JINGLE_TYPE_SOCKS;
+	else if (!strcmp(type, JINGLE_TRANSPORT_IBB))
+		return JINGLE_TYPE_IBB;
+#endif
+#ifdef USE_VV
+	else if (!strcmp(type, JINGLE_APP_RTP))
+		return JINGLE_TYPE_RTP;
+#endif
+#if 0
+	else if (!strcmp(type, JINGLE_APP_FT))
+		return JINGLE_TYPE_FT;
+	else if (!strcmp(type, JINGLE_APP_XML))
+		return JINGLE_TYPE_XML;
+#endif
+	else
+		return G_TYPE_NONE;
+}
+
+static void
+jingle_handle_content_accept(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		jingle_session_accept_content(session, name, creator);
+		/* signal here */
+	}
+}
+
+static void
+jingle_handle_content_add(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		JingleContent *pending_content =
+				jingle_content_parse(content);
+		if (pending_content == NULL) {
+			purple_debug_error("jingle",
+					"Error parsing \"content-add\" content.\n");
+			/* XXX: send error here */
+		} else {
+			jingle_session_add_pending_content(session,
+					pending_content);
+		}
+	}
+
+	/* XXX: signal here */
+}
+
+static void
+jingle_handle_content_modify(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *local_content = jingle_session_find_content(session, name, creator);
+
+		if (content != NULL) {
+			const gchar *senders = xmlnode_get_attrib(content, "senders");
+			gchar *local_senders = jingle_content_get_senders(local_content);
+			if (strcmp(senders, local_senders))
+				jingle_content_modify(local_content, senders);
+			g_free(local_senders);
+		} else {
+			purple_debug_error("jingle", "content_modify: unknown content\n");
+			/* XXX: send error */
+		}
+	}
+}
+
+static void
+jingle_handle_content_reject(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		jingle_session_remove_pending_content(session, name, creator);
+		/* signal here */
+	}
+}
+
+static void
+jingle_handle_content_remove(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		jingle_session_remove_content(session, name, creator);
+	}
+}
+
+static void
+jingle_handle_session_accept(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	jingle_session_accept_session(session);
+	
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *parsed_content =
+				jingle_session_find_content(session, name, creator);
+		if (parsed_content == NULL) {
+			purple_debug_error("jingle", "Error parsing content\n");
+			/* XXX: send error */
+		} else {
+			jingle_content_handle_action(parsed_content, content,
+					JINGLE_SESSION_ACCEPT);
+		}
+	}
+}
+
+static void
+jingle_handle_session_info(JingleSession *session, xmlnode *jingle)
+{
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+	/* XXX: call signal */
+}
+
+static void
+jingle_handle_session_initiate(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		JingleContent *parsed_content = jingle_content_parse(content);
+		if (parsed_content == NULL) {
+			purple_debug_error("jingle", "Error parsing content\n");
+			/* XXX: send error */
+		} else {
+			jingle_session_add_content(session, parsed_content);
+			jingle_content_handle_action(parsed_content, content,
+					JINGLE_SESSION_INITIATE);
+		}
+	}
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+}
+
+static void
+jingle_handle_session_terminate(JingleSession *session, xmlnode *jingle)
+{
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	jingle_session_handle_action(session, jingle,
+			JINGLE_SESSION_TERMINATE);
+	/* display reason? */
+	g_object_unref(session);
+}
+
+static void
+jingle_handle_transport_accept(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+	
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *content = jingle_session_find_content(session, name, creator);
+		jingle_content_accept_transport(content);
+	}
+}
+
+static void
+jingle_handle_transport_info(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *parsed_content = 
+				jingle_session_find_content(session, name, creator);
+		if (parsed_content == NULL) {
+			purple_debug_error("jingle", "Error parsing content\n");
+			/* XXX: send error */
+		} else {
+			jingle_content_handle_action(parsed_content, content,
+					JINGLE_TRANSPORT_INFO);
+		}
+	}
+}
+
+static void
+jingle_handle_transport_reject(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+	
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *content = jingle_session_find_content(session, name, creator);
+		jingle_content_remove_pending_transport(content);
+	}
+}
+
+static void
+jingle_handle_transport_replace(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		xmlnode *xmltransport = xmlnode_get_child(content, "transport");
+		JingleTransport *transport = jingle_transport_parse(xmltransport);
+		JingleContent *content = jingle_session_find_content(session, name, creator);
+
+		jingle_content_set_pending_transport(content, transport);
+	}
+}
+
+
+void
+jingle_parse(JabberStream *js, xmlnode *packet)
+{
+	const gchar *type = xmlnode_get_attrib(packet, "type");
+	xmlnode *jingle;
+	const gchar *action;
+	const gchar *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 = jingle_session_find_by_sid(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 if ((session = jingle_session_find_by_jid(js,
+				xmlnode_get_attrib(packet, "from")))) {
+			purple_debug_fatal("jingle", "Jingle session with "
+					"jid={%s} already exists\n",
+					xmlnode_get_attrib(packet, "from"));
+			/* send jingle redirect packet */
+			return;
+		} else {
+			session = jingle_session_create(js, sid,
+					xmlnode_get_attrib(packet, "to"),
+					xmlnode_get_attrib(packet, "from"), FALSE);
+			jingle_handle_session_initiate(session, jingle);
+		}
+	} else if (!strcmp(action, "content-accept")) {
+		jingle_handle_content_accept(session, jingle);
+	} else if (!strcmp(action, "content-add")) {
+		jingle_handle_content_add(session, jingle);
+	} else if (!strcmp(action, "content-modify")) {
+		jingle_handle_content_modify(session, jingle);
+	} else if (!strcmp(action, "content-reject")) {
+		jingle_handle_content_reject(session, jingle);
+	} else if (!strcmp(action, "content-remove")) {
+		jingle_handle_content_remove(session, jingle);
+	} else if (!strcmp(action, "session-accept")) {
+		jingle_handle_session_accept(session, jingle);
+	} else if (!strcmp(action, "session-info")) {
+		jingle_handle_session_info(session, jingle);
+	} else if (!strcmp(action, "session-terminate")) {
+		jingle_handle_session_terminate(session, jingle);
+	} else if (!strcmp(action, "transport-accept")) {
+		jingle_handle_transport_accept(session, jingle);
+	} else if (!strcmp(action, "transport-info")) {
+		jingle_handle_transport_info(session, jingle);
+	} else if (!strcmp(action, "transport-reject")) {
+		jingle_handle_transport_reject(session, jingle);
+	} else if (!strcmp(action, "transport-replace")) {
+		jingle_handle_transport_replace(session, jingle);
+	}
+}
+
+static void
+jingle_terminate_sessions_gh(gpointer key, gpointer value, gpointer user_data)
+{
+	g_object_unref(value);
+}
+
+void
+jingle_terminate_sessions(JabberStream *js)
+{
+	if (js->sessions)
+		g_hash_table_foreach(js->sessions,
+				jingle_terminate_sessions_gh, NULL);
+}
+
+GParameter *
+jingle_get_params(JabberStream *js, guint *num)
+{
+	/* don't set a STUN server if one is set globally in prefs, in that case
+	 this will be handled in media.c */
+	gboolean has_account_stun = js->stun_ip && !purple_network_get_stun_ip();
+	guint num_params = has_account_stun ? 2 : 0;
+	GParameter *params = NULL;
+
+	if (num_params > 0) {
+		params = g_new0(GParameter, num_params);
+
+		purple_debug_info("jabber", 
+						  "setting param stun-ip for stream using Google auto-config: %s\n",
+						  js->stun_ip);
+		params[0].name = "stun-ip";
+		g_value_init(&params[0].value, G_TYPE_STRING);
+		g_value_set_string(&params[0].value, js->stun_ip);
+		purple_debug_info("jabber", 
+						  "setting param stun-port for stream using Google auto-config: %d\n",
+						  js->stun_port);
+		params[1].name = "stun-port";
+		g_value_init(&params[1].value, G_TYPE_UINT);
+		g_value_set_uint(&params[1].value, js->stun_port);
+	}
+
+	*num = num_params;
+	return params;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,85 @@
+/*
+ * @file jingle.h
+ *
+ * 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 "jabber.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define JINGLE "urn:xmpp:jingle:0"
+#define JINGLE_ERROR "urn:xmpp:jingle:errors:0"
+#define JINGLE_APP_FT "urn:xmpp:jingle:apps:file-transfer:0"
+#define JINGLE_APP_RTP "urn:xmpp:jingle:apps:rtp:0"
+#define JINGLE_APP_RTP_ERROR "urn:xmpp:jingle:apps:rtp:errors:0"
+#define JINGLE_APP_RTP_INFO "urn:xmpp:jingle:apps:rtp:info:0"
+#define JINGLE_APP_RTP_SUPPORT_AUDIO "urn:xmpp:jingle:apps:rtp:audio"
+#define JINGLE_APP_RTP_SUPPORT_VIDEO "urn:xmpp:jingle:apps:rtp:video"
+#define JINGLE_APP_XML "urn:xmpp:tmp:jingle:apps:xmlstream"
+#define JINGLE_DTMF "urn:xmpp:jingle:dtmf:0"
+#define JINGLE_TRANSPORT_SOCKS "urn:xmpp:jingle:transports:bytestreams:0"
+#define JINGLE_TRANSPORT_IBB "urn:xmpp:jingle:transports:ibb:0"
+#define JINGLE_TRANSPORT_ICEUDP "urn:xmpp:jingle:transports:ice-udp:0"
+#define JINGLE_TRANSPORT_RAWUDP "urn:xmpp:jingle:transports:raw-udp:0"
+#define JINGLE_TRANSPORT_RAWUDP_INFO "urn:xmpp:jingle:transports:raw-udp:info:0"
+
+typedef enum {
+	JINGLE_UNKNOWN_TYPE,
+	JINGLE_CONTENT_ACCEPT,
+	JINGLE_CONTENT_ADD,
+	JINGLE_CONTENT_MODIFY,
+	JINGLE_CONTENT_REJECT,
+	JINGLE_CONTENT_REMOVE,
+	JINGLE_SESSION_ACCEPT,
+	JINGLE_SESSION_INFO,
+	JINGLE_SESSION_INITIATE,
+	JINGLE_SESSION_TERMINATE,
+	JINGLE_TRANSPORT_ACCEPT,
+	JINGLE_TRANSPORT_INFO,
+	JINGLE_TRANSPORT_REJECT,
+	JINGLE_TRANSPORT_REPLACE,
+} JingleActionType;
+
+const gchar *jingle_get_action_name(JingleActionType action);
+JingleActionType jingle_get_action_type(const gchar *action);
+
+GType jingle_get_type(const gchar *type);
+
+void jingle_parse(JabberStream *js, xmlnode *packet);
+
+void jingle_terminate_sessions(JabberStream *js);
+
+/* create a GParam array given autoconfigured STUN (and later perhaps TURN).
+	if google_talk is TRUE, set compatability mode to GOOGLE_TALK */
+GParameter *jingle_get_params(JabberStream *js, guint *num_params);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/rawudp.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,325 @@
+/**
+ * @file rawudp.c
+ *
+ * purple
+ *
+ * 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 "internal.h"
+
+#include "rawudp.h"
+#include "jingle.h"
+#include "debug.h"
+
+#include <string.h>
+
+struct _JingleRawUdpPrivate
+{
+	GList *local_candidates;
+	GList *remote_candidates;
+};
+
+#define JINGLE_RAWUDP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_RAWUDP, JingleRawUdpPrivate))
+
+static void jingle_rawudp_class_init (JingleRawUdpClass *klass);
+static void jingle_rawudp_init (JingleRawUdp *rawudp);
+static void jingle_rawudp_finalize (GObject *object);
+static void jingle_rawudp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void jingle_rawudp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static JingleTransport *jingle_rawudp_parse_internal(xmlnode *rawudp);
+static xmlnode *jingle_rawudp_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action);
+
+static JingleTransportClass *parent_class = NULL;
+
+enum {
+	PROP_0,
+	PROP_LOCAL_CANDIDATES,
+	PROP_REMOTE_CANDIDATES,
+};
+
+static JingleRawUdpCandidate *
+jingle_rawudp_candidate_copy(JingleRawUdpCandidate *candidate)
+{
+	JingleRawUdpCandidate *new_candidate = g_new0(JingleRawUdpCandidate, 1);
+	new_candidate->generation = candidate->generation;
+	new_candidate->component = candidate->component;
+	new_candidate->id = g_strdup(candidate->id);
+	new_candidate->ip = g_strdup(candidate->ip);
+	new_candidate->port = candidate->port;
+	return new_candidate;
+}
+
+static void
+jingle_rawudp_candidate_free(JingleRawUdpCandidate *candidate)
+{
+	g_free(candidate->id);
+	g_free(candidate->ip);
+}
+
+GType
+jingle_rawudp_candidate_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		type = g_boxed_type_register_static("JingleRawUdpCandidate",
+				(GBoxedCopyFunc)jingle_rawudp_candidate_copy,
+				(GBoxedFreeFunc)jingle_rawudp_candidate_free);
+	}
+	return type;
+}
+
+JingleRawUdpCandidate *
+jingle_rawudp_candidate_new(const gchar *id, guint generation, guint component, const gchar *ip, guint port)
+{
+	JingleRawUdpCandidate *candidate = g_new0(JingleRawUdpCandidate, 1);
+	candidate->generation = generation;
+	candidate->component = component;
+	candidate->id = g_strdup(id);
+	candidate->ip = g_strdup(ip);
+	candidate->port = port;
+	return candidate;
+}
+
+GType
+jingle_rawudp_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(JingleRawUdpClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) jingle_rawudp_class_init,
+			NULL,
+			NULL,
+			sizeof(JingleRawUdp),
+			0,
+			(GInstanceInitFunc) jingle_rawudp_init,
+			NULL
+		};
+		type = g_type_register_static(JINGLE_TYPE_TRANSPORT, "JingleRawUdp", &info, 0);
+	}
+	return type;
+}
+
+static void
+jingle_rawudp_class_init (JingleRawUdpClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = jingle_rawudp_finalize;
+	gobject_class->set_property = jingle_rawudp_set_property;
+	gobject_class->get_property = jingle_rawudp_get_property;
+	klass->parent_class.to_xml = jingle_rawudp_to_xml_internal;
+	klass->parent_class.parse = jingle_rawudp_parse_internal;
+	klass->parent_class.transport_type = JINGLE_TRANSPORT_RAWUDP;
+
+	g_object_class_install_property(gobject_class, PROP_LOCAL_CANDIDATES,
+			g_param_spec_pointer("local-candidates",
+			"Local candidates",
+			"The local candidates for this transport.",
+			G_PARAM_READABLE));
+
+	g_object_class_install_property(gobject_class, PROP_REMOTE_CANDIDATES,
+			g_param_spec_pointer("remote-candidates",
+			"Remote candidates",
+			"The remote candidates for this transport.",
+			G_PARAM_READABLE));
+
+	g_type_class_add_private(klass, sizeof(JingleRawUdpPrivate));
+}
+
+static void
+jingle_rawudp_init (JingleRawUdp *rawudp)
+{
+	rawudp->priv = JINGLE_RAWUDP_GET_PRIVATE(rawudp);
+	memset(rawudp->priv, 0, sizeof(rawudp->priv));
+}
+
+static void
+jingle_rawudp_finalize (GObject *rawudp)
+{
+/*	JingleRawUdpPrivate *priv = JINGLE_RAWUDP_GET_PRIVATE(rawudp); */
+	purple_debug_info("jingle","jingle_rawudp_finalize\n");
+}
+
+static void
+jingle_rawudp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	JingleRawUdp *rawudp;
+	g_return_if_fail(JINGLE_IS_RAWUDP(object));
+
+	rawudp = JINGLE_RAWUDP(object);
+
+	switch (prop_id) {
+		case PROP_LOCAL_CANDIDATES:
+			rawudp->priv->local_candidates =
+					g_value_get_pointer(value);
+			break;
+		case PROP_REMOTE_CANDIDATES:
+			rawudp->priv->remote_candidates =
+					g_value_get_pointer(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+jingle_rawudp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	JingleRawUdp *rawudp;
+	g_return_if_fail(JINGLE_IS_RAWUDP(object));
+	
+	rawudp = JINGLE_RAWUDP(object);
+
+	switch (prop_id) {
+		case PROP_LOCAL_CANDIDATES:
+			g_value_set_pointer(value, rawudp->priv->local_candidates);
+			break;
+		case PROP_REMOTE_CANDIDATES:
+			g_value_set_pointer(value, rawudp->priv->remote_candidates);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+void
+jingle_rawudp_add_local_candidate(JingleRawUdp *rawudp, JingleRawUdpCandidate *candidate)
+{
+	GList *iter = rawudp->priv->local_candidates;
+
+	for (; iter; iter = g_list_next(iter)) {
+		JingleRawUdpCandidate *c = iter->data;
+		if (!strcmp(c->id, candidate->id)) {
+			guint generation = c->generation + 1;
+
+			g_boxed_free(JINGLE_TYPE_RAWUDP_CANDIDATE, c);
+			rawudp->priv->local_candidates = g_list_delete_link(
+					rawudp->priv->local_candidates, iter);
+
+			candidate->generation = generation;
+
+			rawudp->priv->local_candidates = g_list_append(
+					rawudp->priv->local_candidates, candidate);
+			return;
+		}
+	}
+
+	rawudp->priv->local_candidates = g_list_append(
+			rawudp->priv->local_candidates, candidate);
+}
+
+GList *
+jingle_rawudp_get_remote_candidates(JingleRawUdp *rawudp)
+{
+	return g_list_copy(rawudp->priv->remote_candidates);
+}
+
+static JingleRawUdpCandidate *
+jingle_rawudp_get_remote_candidate_by_id(JingleRawUdp *rawudp, gchar *id)
+{
+	GList *iter = rawudp->priv->remote_candidates;
+	for (; iter; iter = g_list_next(iter)) {
+		JingleRawUdpCandidate *candidate = iter->data;
+		if (!strcmp(candidate->id, id)) {
+			return candidate;
+		}
+	}
+	return NULL;
+}
+
+static void
+jingle_rawudp_add_remote_candidate(JingleRawUdp *rawudp, JingleRawUdpCandidate *candidate)
+{
+	JingleRawUdpPrivate *priv = JINGLE_RAWUDP_GET_PRIVATE(rawudp);
+	JingleRawUdpCandidate *rawudp_candidate =
+			jingle_rawudp_get_remote_candidate_by_id(rawudp, candidate->id);
+	if (rawudp_candidate != NULL) {
+		priv->remote_candidates = g_list_remove(
+				priv->remote_candidates, rawudp_candidate);
+		g_boxed_free(JINGLE_TYPE_RAWUDP_CANDIDATE, rawudp_candidate);
+	}
+	priv->remote_candidates = g_list_append(priv->remote_candidates, candidate);
+}
+
+static JingleTransport *
+jingle_rawudp_parse_internal(xmlnode *rawudp)
+{
+	JingleTransport *transport = parent_class->parse(rawudp);
+	JingleRawUdpPrivate *priv = JINGLE_RAWUDP_GET_PRIVATE(transport);
+	xmlnode *candidate = xmlnode_get_child(rawudp, "candidate");
+	JingleRawUdpCandidate *rawudp_candidate = NULL;
+
+	for (; candidate; candidate = xmlnode_get_next_twin(candidate)) {
+		rawudp_candidate = jingle_rawudp_candidate_new(
+				xmlnode_get_attrib(candidate, "id"),
+				atoi(xmlnode_get_attrib(candidate, "generation")),
+				atoi(xmlnode_get_attrib(candidate, "component")),
+				xmlnode_get_attrib(candidate, "ip"),
+				atoi(xmlnode_get_attrib(candidate, "port")));
+		jingle_rawudp_add_remote_candidate(JINGLE_RAWUDP(transport), rawudp_candidate);
+	}
+
+	if (rawudp_candidate != NULL &&
+			g_list_length(priv->remote_candidates) == 1) {
+		/* manufacture rtcp candidate */
+		rawudp_candidate = g_boxed_copy(JINGLE_TYPE_RAWUDP_CANDIDATE, rawudp_candidate);
+		rawudp_candidate->component = 2;
+		rawudp_candidate->port = rawudp_candidate->port + 1;
+		jingle_rawudp_add_remote_candidate(JINGLE_RAWUDP(transport), rawudp_candidate);
+	}
+
+	return transport;
+}
+
+static xmlnode *
+jingle_rawudp_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action)
+{
+	xmlnode *node = parent_class->to_xml(transport, content, action);
+
+	if (action == JINGLE_SESSION_INITIATE || action == JINGLE_TRANSPORT_INFO) {
+		JingleRawUdpPrivate *priv = JINGLE_RAWUDP_GET_PRIVATE(transport);
+		GList *iter = priv->local_candidates;
+
+		for (; iter; iter = g_list_next(iter)) {
+			JingleRawUdpCandidate *candidate = iter->data;
+
+			xmlnode *xmltransport = xmlnode_new_child(node, "candidate");
+			gchar *generation = g_strdup_printf("%d", candidate->generation);
+			gchar *component = g_strdup_printf("%d", candidate->component);
+			gchar *port = g_strdup_printf("%d", candidate->port);
+
+			xmlnode_set_attrib(xmltransport, "generation", generation);
+			xmlnode_set_attrib(xmltransport, "component", component);
+			xmlnode_set_attrib(xmltransport, "id", candidate->id);
+			xmlnode_set_attrib(xmltransport, "ip", candidate->ip);
+			xmlnode_set_attrib(xmltransport, "port", port);
+
+			g_free(port);
+			g_free(generation);
+		}
+	}
+
+	return node;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/rawudp.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,98 @@
+/**
+ * @file rawudp.h
+ *
+ * purple
+ *
+ * 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 JINGLE_RAWUDP_H
+#define JINGLE_RAWUDP_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "transport.h"
+
+G_BEGIN_DECLS
+
+#define JINGLE_TYPE_RAWUDP            (jingle_rawudp_get_type())
+#define JINGLE_TYPE_RAWUDP_CANDIDATE  (jingle_rawudp_candidate_get_type())
+#define JINGLE_RAWUDP(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_RAWUDP, JingleRawUdp))
+#define JINGLE_RAWUDP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_RAWUDP, JingleRawUdpClass))
+#define JINGLE_IS_RAWUDP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_RAWUDP))
+#define JINGLE_IS_RAWUDP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_RAWUDP))
+#define JINGLE_RAWUDP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_RAWUDP, JingleRawUdpClass))
+
+/** @copydoc _JingleRawUdp */
+typedef struct _JingleRawUdp JingleRawUdp;
+/** @copydoc _JingleRawUdpClass */
+typedef struct _JingleRawUdpClass JingleRawUdpClass;
+/** @copydoc _JingleRawUdpPrivate */
+typedef struct _JingleRawUdpPrivate JingleRawUdpPrivate;
+/** @copydoc _JingleRawUdpCandidate */
+typedef struct _JingleRawUdpCandidate JingleRawUdpCandidate;
+
+/** The rawudp class */
+struct _JingleRawUdpClass
+{
+	JingleTransportClass parent_class;     /**< The parent class. */
+
+	xmlnode *(*to_xml) (JingleTransport *transport, xmlnode *content, JingleActionType action);
+	JingleTransport *(*parse) (xmlnode *transport);
+};
+
+/** The rawudp class's private data */
+struct _JingleRawUdp
+{
+	JingleTransport parent;                /**< The parent of this object. */
+	JingleRawUdpPrivate *priv;      /**< The private data of this object. */
+};
+
+struct _JingleRawUdpCandidate
+{
+	guint generation;
+	guint component;
+	gchar *id;
+	gchar *ip;
+	guint port;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+GType jingle_rawudp_candidate_get_type(void);
+
+/**
+ * Gets the rawudp class's GType
+ *
+ * @return The rawudp class's GType.
+ */
+GType jingle_rawudp_get_type(void);
+
+JingleRawUdpCandidate *jingle_rawudp_candidate_new(const gchar *id,
+		guint generation, guint component, const gchar *ip, guint port);
+void jingle_rawudp_add_local_candidate(JingleRawUdp *rawudp, JingleRawUdpCandidate *candidate);
+GList *jingle_rawudp_get_remote_candidates(JingleRawUdp *rawudp);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_RAWUDP_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/rtp.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,742 @@
+/**
+ * @file rtp.c
+ *
+ * purple
+ *
+ * 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"
+
+#ifdef USE_VV
+
+#include "jabber.h"
+#include "jingle.h"
+#include "media.h"
+#include "mediamanager.h"
+#include "iceudp.h"
+#include "rawudp.h"
+#include "rtp.h"
+#include "session.h"
+#include "debug.h"
+
+#include <string.h>
+
+struct _JingleRtpPrivate
+{
+	gchar *media_type;
+};
+
+#define JINGLE_RTP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_RTP, JingleRtpPrivate))
+
+static void jingle_rtp_class_init (JingleRtpClass *klass);
+static void jingle_rtp_init (JingleRtp *rtp);
+static void jingle_rtp_finalize (GObject *object);
+static void jingle_rtp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void jingle_rtp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static JingleContent *jingle_rtp_parse_internal(xmlnode *rtp);
+static xmlnode *jingle_rtp_to_xml_internal(JingleContent *rtp, xmlnode *content, JingleActionType action);
+static void jingle_rtp_handle_action_internal(JingleContent *content, xmlnode *jingle, JingleActionType action);
+
+static PurpleMedia *jingle_rtp_get_media(JingleSession *session);
+
+static JingleContentClass *parent_class = NULL;
+#if 0
+enum {
+	LAST_SIGNAL
+};
+static guint jingle_rtp_signals[LAST_SIGNAL] = {0};
+#endif
+
+enum {
+	PROP_0,
+	PROP_MEDIA_TYPE,
+};
+
+GType
+jingle_rtp_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(JingleRtpClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) jingle_rtp_class_init,
+			NULL,
+			NULL,
+			sizeof(JingleRtp),
+			0,
+			(GInstanceInitFunc) jingle_rtp_init,
+			NULL
+		};
+		type = g_type_register_static(JINGLE_TYPE_CONTENT, "JingleRtp", &info, 0);
+	}
+	return type;
+}
+
+static void
+jingle_rtp_class_init (JingleRtpClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = jingle_rtp_finalize;
+	gobject_class->set_property = jingle_rtp_set_property;
+	gobject_class->get_property = jingle_rtp_get_property;
+	klass->parent_class.to_xml = jingle_rtp_to_xml_internal;
+	klass->parent_class.parse = jingle_rtp_parse_internal;
+	klass->parent_class.description_type = JINGLE_APP_RTP;
+	klass->parent_class.handle_action = jingle_rtp_handle_action_internal;
+
+	g_object_class_install_property(gobject_class, PROP_MEDIA_TYPE,
+			g_param_spec_string("media-type",
+			"Media Type",
+			"The media type (\"audio\" or \"video\") for this rtp session.",
+			NULL,
+			G_PARAM_READWRITE));
+	g_type_class_add_private(klass, sizeof(JingleRtpPrivate));
+}
+
+static void
+jingle_rtp_init (JingleRtp *rtp)
+{
+	rtp->priv = JINGLE_RTP_GET_PRIVATE(rtp);
+	memset(rtp->priv, 0, sizeof(rtp->priv));
+}
+
+static void
+jingle_rtp_finalize (GObject *rtp)
+{
+	JingleRtpPrivate *priv = JINGLE_RTP_GET_PRIVATE(rtp);
+	purple_debug_info("jingle-rtp","jingle_rtp_finalize\n");
+
+	g_free(priv->media_type);
+}
+
+static void
+jingle_rtp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	JingleRtp *rtp;
+	g_return_if_fail(JINGLE_IS_RTP(object));
+
+	rtp = JINGLE_RTP(object);
+
+	switch (prop_id) {
+		case PROP_MEDIA_TYPE:
+			g_free(rtp->priv->media_type);
+			rtp->priv->media_type = g_value_dup_string(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+jingle_rtp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	JingleRtp *rtp;
+	g_return_if_fail(JINGLE_IS_RTP(object));
+	
+	rtp = JINGLE_RTP(object);
+
+	switch (prop_id) {
+		case PROP_MEDIA_TYPE:
+			g_value_set_string(value, rtp->priv->media_type);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+gchar *
+jingle_rtp_get_media_type(JingleContent *content)
+{
+	gchar *media_type;
+	g_object_get(content, "media-type", &media_type, NULL);
+	return media_type;
+}
+
+static PurpleMedia *
+jingle_rtp_get_media(JingleSession *session)
+{
+	JabberStream *js = jingle_session_get_js(session);
+	gchar *sid = jingle_session_get_sid(session);
+
+	PurpleMedia *media = (PurpleMedia *) (js->medias) ?
+			  g_hash_table_lookup(js->medias, sid) : NULL;
+	g_free(sid);
+
+	return media;
+}
+
+static JingleTransport *
+jingle_rtp_candidates_to_transport(JingleSession *session, GType type, guint generation, GList *candidates)
+{
+	if (type == JINGLE_TYPE_RAWUDP) {
+		gchar *id = jabber_get_next_id(jingle_session_get_js(session));
+		JingleTransport *transport = jingle_transport_create(JINGLE_TRANSPORT_RAWUDP);
+		JingleRawUdpCandidate *rawudp_candidate;
+		for (; candidates; candidates = g_list_next(candidates)) {
+			PurpleMediaCandidate *candidate = candidates->data;
+			id = jabber_get_next_id(jingle_session_get_js(session));
+			rawudp_candidate = jingle_rawudp_candidate_new(id,
+					generation, candidate->component_id,
+					candidate->ip, candidate->port);
+			jingle_rawudp_add_local_candidate(JINGLE_RAWUDP(transport), rawudp_candidate);
+		}
+		g_free(id);
+		return transport;
+	} else if (type == JINGLE_TYPE_ICEUDP) {
+		JingleTransport *transport = jingle_transport_create(JINGLE_TRANSPORT_ICEUDP);
+		JingleIceUdpCandidate *iceudp_candidate;
+		for (; candidates; candidates = g_list_next(candidates)) {
+			PurpleMediaCandidate *candidate = candidates->data;
+			iceudp_candidate = jingle_iceudp_candidate_new(candidate->component_id,
+					candidate->foundation, generation, candidate->ip,
+					0, candidate->port, candidate->priority, "udp",
+					candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_HOST ? "host" :
+					candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX ? "srflx" :
+					candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX ? "prflx" :
+					candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_RELAY ? "relay" : "",
+					candidate->username, candidate->password);
+			jingle_iceudp_add_local_candidate(JINGLE_ICEUDP(transport), iceudp_candidate);
+		}
+		return transport;
+	} else {
+		return NULL;
+	}
+}
+
+static GList *
+jingle_rtp_transport_to_candidates(JingleTransport *transport)
+{
+	const gchar *type = jingle_transport_get_transport_type(transport);
+	GList *ret = NULL;
+	if (!strcmp(type, JINGLE_TRANSPORT_RAWUDP)) {
+		GList *candidates = jingle_rawudp_get_remote_candidates(JINGLE_RAWUDP(transport));
+
+		for (; candidates; candidates = g_list_delete_link(candidates, candidates)) {
+			JingleRawUdpCandidate *candidate = candidates->data;
+			ret = g_list_append(ret, purple_media_candidate_new(
+					"", candidate->component,
+					PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX,
+					PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
+					candidate->ip, candidate->port));
+		}
+
+		return ret;
+	} else if (!strcmp(type, JINGLE_TRANSPORT_ICEUDP)) {
+		GList *candidates = jingle_iceudp_get_remote_candidates(JINGLE_ICEUDP(transport));
+
+		for (; candidates; candidates = g_list_delete_link(candidates, candidates)) {
+			JingleIceUdpCandidate *candidate = candidates->data;
+			PurpleMediaCandidate *new_candidate = purple_media_candidate_new(
+					candidate->foundation, candidate->component,
+					!strcmp(candidate->type, "host") ?
+					PURPLE_MEDIA_CANDIDATE_TYPE_HOST :
+					!strcmp(candidate->type, "srflx") ?
+					PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX :
+					!strcmp(candidate->type, "prflx") ?
+					PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX :
+					!strcmp(candidate->type, "relay") ?
+					PURPLE_MEDIA_CANDIDATE_TYPE_RELAY : 0,
+					PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
+					candidate->ip, candidate->port);
+			new_candidate->username = g_strdup(candidate->username);
+			new_candidate->password = g_strdup(candidate->password);
+			new_candidate->priority = candidate->priority;
+			ret = g_list_append(ret, new_candidate);
+		}
+
+		return ret;
+	} else {
+		return NULL;
+	}
+}
+
+static void
+jingle_rtp_accepted_cb(PurpleMedia *media, gchar *sid, gchar *name,
+		JingleSession *session)
+{
+	purple_debug_info("jingle-rtp", "jingle_rtp_accepted_cb\n");
+}
+
+static void
+jingle_rtp_codecs_changed_cb(PurpleMedia *media, gchar *sid,
+		JingleSession *session)
+{
+	purple_debug_info("jingle-rtp", "jingle_rtp_codecs_changed_cb: "
+			"session_id: %s jingle_session: %p\n", sid, session);
+}
+
+static void
+jingle_rtp_new_candidate_cb(PurpleMedia *media, gchar *sid, gchar *name, PurpleMediaCandidate *candidate, JingleSession *session)
+{
+	purple_debug_info("jingle-rtp", "jingle_rtp_new_candidate_cb\n");
+}
+
+static void
+jingle_rtp_ready_cb(PurpleMedia *media, gchar *sid, gchar *name, JingleSession *session)
+{
+	purple_debug_info("rtp", "ready-new: session: %s name: %s\n", sid, name);
+
+	if (sid == NULL && name == NULL) {
+		if (jingle_session_is_initiator(session) == TRUE) {
+			GList *contents = jingle_session_get_contents(session);
+
+			jabber_iq_send(jingle_session_to_packet(session, JINGLE_SESSION_INITIATE));
+
+			for (; contents; contents = g_list_next(contents)) {
+				JingleContent *content = (JingleContent *)contents->data;
+				JingleTransport *transport = jingle_content_get_transport(content);
+				if (JINGLE_IS_ICEUDP(transport))
+					jabber_iq_send(jingle_session_to_packet(session,
+							JINGLE_TRANSPORT_INFO));
+			}
+		} else {
+			jabber_iq_send(jingle_session_to_packet(session, JINGLE_TRANSPORT_INFO));
+			jabber_iq_send(jingle_session_to_packet(session, JINGLE_SESSION_ACCEPT));
+		}
+	} else if (sid != NULL && name != NULL) {
+		JingleContent *content = jingle_session_find_content(session, sid, "initiator");
+		JingleTransport *oldtransport = jingle_content_get_transport(content);
+		GList *candidates = purple_media_get_local_candidates(media, sid, name);
+		JingleTransport *transport =
+				JINGLE_TRANSPORT(jingle_rtp_candidates_to_transport(
+				session, JINGLE_IS_RAWUDP(oldtransport) ?
+					JINGLE_TYPE_RAWUDP : JINGLE_TYPE_ICEUDP,
+				0, candidates));
+		g_list_free(candidates);
+
+		jingle_content_set_pending_transport(content, transport);
+		jingle_content_accept_transport(content);
+	}
+}
+
+static void
+jingle_rtp_state_changed_cb(PurpleMedia *media, PurpleMediaStateChangedType type,
+		gchar *sid, gchar *name, JingleSession *session)
+{
+	purple_debug_info("jingle-rtp", "state-changed: type %d id: %s name: %s\n", type, sid, name);
+
+	if ((type == PURPLE_MEDIA_STATE_CHANGED_REJECTED ||
+			type == PURPLE_MEDIA_STATE_CHANGED_HANGUP) &&
+			sid == NULL && name == NULL) {
+		gchar *sid = jingle_session_get_sid(session);
+		jabber_iq_send(jingle_session_to_packet(session,
+				JINGLE_SESSION_TERMINATE));
+		g_hash_table_remove(jingle_session_get_js(session)->medias, sid);
+		g_free(sid);
+		g_object_unref(session);
+	}
+}
+
+static PurpleMedia *
+jingle_rtp_create_media(JingleContent *content)
+{
+	JingleSession *session = jingle_content_get_session(content);
+	JabberStream *js = jingle_session_get_js(session);
+	gchar *remote_jid = jingle_session_get_remote_jid(session);
+	gchar *sid = jingle_session_get_sid(session);
+
+	PurpleMedia *media = purple_media_manager_create_media(purple_media_manager_get(), 
+						  js->gc, "fsrtpconference", remote_jid,
+						  jingle_session_is_initiator(session));
+	g_free(remote_jid);
+
+	if (!media) {
+		purple_debug_error("jingle-rtp", "Couldn't create media session\n");
+		return NULL;
+	}
+
+	/* insert it into the hash table */
+	if (!js->medias) {
+		purple_debug_info("jingle-rtp", "Creating hash table for media\n");
+		js->medias = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+	purple_debug_info("jingle-rtp", "inserting media with sid: %s into table\n", sid);
+	g_hash_table_insert(js->medias, sid, media);
+
+	/* connect callbacks */
+	g_signal_connect(G_OBJECT(media), "accepted",
+				 G_CALLBACK(jingle_rtp_accepted_cb), session);
+	g_signal_connect(G_OBJECT(media), "codecs-changed",
+				 G_CALLBACK(jingle_rtp_codecs_changed_cb), session);
+	g_signal_connect(G_OBJECT(media), "new-candidate",
+				 G_CALLBACK(jingle_rtp_new_candidate_cb), session);
+	g_signal_connect(G_OBJECT(media), "ready-new",
+				 G_CALLBACK(jingle_rtp_ready_cb), session);
+	g_signal_connect(G_OBJECT(media), "state-changed",
+				 G_CALLBACK(jingle_rtp_state_changed_cb), session);
+
+	g_object_unref(session);
+	return media;
+}
+
+static gboolean
+jingle_rtp_init_media(JingleContent *content)
+{
+	JingleSession *session = jingle_content_get_session(content);
+	PurpleMedia *media = jingle_rtp_get_media(session);
+	gchar *media_type;
+	gchar *remote_jid;
+	gchar *senders;
+	gchar *name;
+	const gchar *transmitter;
+	gboolean is_audio;
+	PurpleMediaSessionType type;
+	JingleTransport *transport;
+	GParameter *params = NULL;
+	guint num_params;
+
+	/* maybe this create ought to just be in initiate and handle initiate */
+	if (media == NULL)
+		media = jingle_rtp_create_media(content);
+
+	if (media == NULL)
+		return FALSE;
+
+	name = jingle_content_get_name(content);
+	media_type = jingle_rtp_get_media_type(content);
+	remote_jid = jingle_session_get_remote_jid(session);
+	senders = jingle_content_get_senders(content);
+	transport = jingle_content_get_transport(content);
+
+	if (JINGLE_IS_RAWUDP(transport))
+		transmitter = "rawudp";
+	else if (JINGLE_IS_ICEUDP(transport))
+		transmitter = "nice";
+	else
+		transmitter = "notransmitter";
+
+	is_audio = !strcmp(media_type, "audio");
+
+	if (!strcmp(senders, "both"))
+		type = is_audio == TRUE ? PURPLE_MEDIA_AUDIO
+				: PURPLE_MEDIA_VIDEO;
+	else if (!strcmp(senders, "initiator")
+			&& jingle_session_is_initiator(session))
+		type = is_audio == TRUE ? PURPLE_MEDIA_SEND_AUDIO
+				: PURPLE_MEDIA_SEND_VIDEO;
+	else
+		type = is_audio == TRUE ? PURPLE_MEDIA_RECV_AUDIO
+				: PURPLE_MEDIA_RECV_VIDEO;
+
+	params = 
+		jingle_get_params(jingle_session_get_js(session), &num_params);
+	purple_media_add_stream(media, name, remote_jid,
+			type, transmitter, num_params, params);
+
+	g_free(name);
+	g_free(media_type);
+	g_free(remote_jid);
+	g_free(senders);
+	g_free(params);
+	g_object_unref(session);
+
+	return TRUE;
+}
+
+static GList *
+jingle_rtp_parse_codecs(xmlnode *description)
+{
+	GList *codecs = NULL;
+	xmlnode *codec_element = NULL;
+	const char *encoding_name,*id, *clock_rate;
+	PurpleMediaCodec *codec;
+	const gchar *media = xmlnode_get_attrib(description, "media");
+	PurpleMediaSessionType type =
+			!strcmp(media, "video") ? PURPLE_MEDIA_VIDEO :
+			!strcmp(media, "audio") ? PURPLE_MEDIA_AUDIO : 0;
+
+	for (codec_element = xmlnode_get_child(description, "payload-type") ;
+		 codec_element ;
+		 codec_element = xmlnode_get_next_twin(codec_element)) {
+		xmlnode *param;
+		gchar *codec_str;
+		encoding_name = xmlnode_get_attrib(codec_element, "name");
+
+		id = xmlnode_get_attrib(codec_element, "id");
+		clock_rate = xmlnode_get_attrib(codec_element, "clockrate");
+
+		codec = purple_media_codec_new(atoi(id), encoding_name, 
+				     type, 
+				     clock_rate ? atoi(clock_rate) : 0);
+
+		for (param = xmlnode_get_child(codec_element, "parameter");
+				param; param = xmlnode_get_next_twin(param)) {
+			purple_media_codec_add_optional_parameter(codec,
+					xmlnode_get_attrib(param, "name"),
+					xmlnode_get_attrib(param, "value"));
+		}
+
+		codec_str = purple_media_codec_to_string(codec);
+		purple_debug_info("jingle-rtp", "received codec: %s\n", codec_str);
+		g_free(codec_str);
+
+		codecs = g_list_append(codecs, codec);
+	}
+	return codecs;
+}
+
+static JingleContent *
+jingle_rtp_parse_internal(xmlnode *rtp)
+{
+	JingleContent *content = parent_class->parse(rtp);
+	xmlnode *description = xmlnode_get_child(rtp, "description");
+	const gchar *media_type = xmlnode_get_attrib(description, "media");
+	purple_debug_info("jingle-rtp", "rtp parse\n");
+	g_object_set(content, "media-type", media_type, NULL);
+	return content;
+}
+
+static void
+jingle_rtp_add_payloads(xmlnode *description, GList *codecs)
+{
+	for (; codecs ; codecs = codecs->next) {
+		PurpleMediaCodec *codec = (PurpleMediaCodec*)codecs->data;
+		GList *iter = codec->optional_params;
+		char id[8], clockrate[10], channels[10];
+		gchar *codec_str;
+		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);
+
+		for (; iter; iter = g_list_next(iter)) {
+			PurpleMediaCodecParameter *mparam = iter->data;
+			xmlnode *param = xmlnode_new_child(payload, "parameter");
+			xmlnode_set_attrib(param, "name", mparam->name);
+			xmlnode_set_attrib(param, "value", mparam->value);
+		}
+
+		codec_str = purple_media_codec_to_string(codec);
+		purple_debug_info("jingle", "adding codec: %s\n", codec_str);
+		g_free(codec_str);
+	}
+}
+
+static xmlnode *
+jingle_rtp_to_xml_internal(JingleContent *rtp, xmlnode *content, JingleActionType action)
+{
+	xmlnode *node = parent_class->to_xml(rtp, content, action);
+	xmlnode *description = xmlnode_get_child(node, "description");
+	if (description != NULL) {
+		JingleSession *session = jingle_content_get_session(rtp);
+		PurpleMedia *media = jingle_rtp_get_media(session);
+		gchar *media_type = jingle_rtp_get_media_type(rtp);
+		gchar *name = jingle_content_get_name(rtp);
+		GList *codecs = purple_media_get_codecs(media, name);
+
+		xmlnode_set_attrib(description, "media", media_type);
+
+		g_free(media_type);
+		g_free(name);
+		g_object_unref(session);
+
+		jingle_rtp_add_payloads(description, codecs);
+	}
+	return node;
+}
+
+static void
+jingle_rtp_handle_action_internal(JingleContent *content, xmlnode *xmlcontent, JingleActionType action)
+{
+	switch (action) {
+		case JINGLE_SESSION_ACCEPT: {
+			JingleSession *session = jingle_content_get_session(content);
+			xmlnode *description = xmlnode_get_child(xmlcontent, "description");
+			GList *codecs = jingle_rtp_parse_codecs(description);
+
+			purple_media_set_remote_codecs(jingle_rtp_get_media(session),
+					jingle_content_get_name(content),
+					jingle_session_get_remote_jid(session), codecs);
+
+			/* This needs to be for the entire session, not a single content */
+			/* very hacky */
+			if (xmlnode_get_next_twin(xmlcontent) == NULL)
+				purple_media_accept(jingle_rtp_get_media(session));
+
+			g_object_unref(session);
+			break;
+		}
+		case JINGLE_SESSION_INITIATE: {
+			JingleSession *session = jingle_content_get_session(content);
+			JingleTransport *transport = jingle_transport_parse(
+					xmlnode_get_child(xmlcontent, "transport"));
+			xmlnode *description = xmlnode_get_child(xmlcontent, "description");
+			GList *candidates = jingle_rtp_transport_to_candidates(transport);
+			GList *codecs = jingle_rtp_parse_codecs(description);
+
+			if (jingle_rtp_init_media(content) == FALSE) {
+				/* XXX: send error */
+				jabber_iq_send(jingle_session_to_packet(session,
+						 JINGLE_SESSION_TERMINATE));
+				g_object_unref(session);
+				break;
+			}
+
+			purple_media_set_remote_codecs(jingle_rtp_get_media(session),
+					jingle_content_get_name(content),
+					jingle_session_get_remote_jid(session), codecs);
+
+			if (JINGLE_IS_RAWUDP(transport)) {
+				purple_media_add_remote_candidates(jingle_rtp_get_media(session),
+						jingle_content_get_name(content),
+						jingle_session_get_remote_jid(session),
+						candidates);
+			}
+
+			g_object_unref(session);
+			break;
+		}
+		case JINGLE_SESSION_TERMINATE: {
+			JingleSession *session = jingle_content_get_session(content);
+			PurpleMedia *media = jingle_rtp_get_media(session);
+
+			if (media != NULL) {
+				gchar *sid = jingle_session_get_sid(session);
+				purple_media_end(media, NULL, NULL);
+				g_hash_table_remove(jingle_session_get_js(
+						session)->medias, sid);
+				g_free(sid);
+			}
+
+			g_object_unref(session);
+			break;
+		}
+		case JINGLE_TRANSPORT_INFO: {
+			JingleSession *session = jingle_content_get_session(content);
+			JingleTransport *transport = jingle_transport_parse(
+					xmlnode_get_child(xmlcontent, "transport"));
+			GList *candidates = jingle_rtp_transport_to_candidates(transport);
+
+			purple_media_add_remote_candidates(jingle_rtp_get_media(session),
+					jingle_content_get_name(content),
+					jingle_session_get_remote_jid(session),
+					candidates);
+			g_object_unref(session);
+			break;
+		}
+		default:
+			break;
+	}
+}
+
+PurpleMedia *
+jingle_rtp_initiate_media(JabberStream *js, const gchar *who, 
+		      PurpleMediaSessionType type)
+{
+	/* create content negotiation */
+	JingleSession *session;
+	JingleContent *content;
+	JingleTransport *transport;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	PurpleMedia *media;
+	const gchar *transport_type;
+	
+	gchar *jid = NULL, *me = NULL, *sid = NULL;
+
+	/* construct JID to send to */
+	jb = jabber_buddy_find(js, who, FALSE);
+	if (!jb) {
+		purple_debug_error("jingle-rtp", "Could not find Jabber buddy\n");
+		return NULL;
+	}
+	jbr = jabber_buddy_find_resource(jb, NULL);
+	if (!jbr) {
+		purple_debug_error("jingle-rtp", "Could not find buddy's resource\n");
+	}
+
+	if (jabber_resource_has_capability(jbr, JINGLE_TRANSPORT_ICEUDP)) {
+		transport_type = JINGLE_TRANSPORT_ICEUDP;
+	} else if (jabber_resource_has_capability(jbr, JINGLE_TRANSPORT_RAWUDP)) {
+		transport_type = JINGLE_TRANSPORT_RAWUDP;
+	} else {
+		purple_debug_error("jingle-rtp", "Resource doesn't support "
+				"the same transport types\n");
+		return NULL;
+	}
+
+	if ((strchr(who, '/') == NULL) && jbr && (jbr->name != NULL)) {
+		jid = g_strdup_printf("%s/%s", who, jbr->name);
+	} else {
+		jid = g_strdup(who);
+	}
+	
+	/* set ourselves as initiator */
+	me = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, js->user->resource);
+
+	sid = jabber_get_next_id(js);
+	session = jingle_session_create(js, sid, me, jid, TRUE);
+	g_free(sid);
+
+
+	if (type & PURPLE_MEDIA_AUDIO) {
+		transport = jingle_transport_create(transport_type);
+		content = jingle_content_create(JINGLE_APP_RTP, "initiator",
+				"session", "audio-session", "both", transport);
+		jingle_session_add_content(session, content);
+		JINGLE_RTP(content)->priv->media_type = g_strdup("audio");
+		jingle_rtp_init_media(content);
+	}
+	if (type & PURPLE_MEDIA_VIDEO) {
+		transport = jingle_transport_create(transport_type);
+		content = jingle_content_create(JINGLE_APP_RTP, "initiator",
+				"session", "video-session", "both", transport);
+		jingle_session_add_content(session, content);
+		JINGLE_RTP(content)->priv->media_type = g_strdup("video");
+		jingle_rtp_init_media(content);
+	}
+
+	if ((media = jingle_rtp_get_media(session)) == NULL) {
+		return NULL;
+	}
+
+	g_free(jid);
+	g_free(me);
+
+	return media;
+}
+
+void
+jingle_rtp_terminate_session(JabberStream *js, const gchar *who)
+{
+	JingleSession *session;
+/* XXX: This may cause file transfers and xml sessions to stop as well */
+	session = jingle_session_find_by_jid(js, who);
+
+	if (session) {
+		PurpleMedia *media = jingle_rtp_get_media(session);
+		if (media) {
+			purple_debug_info("jingle-rtp", "hanging up media\n");
+			purple_media_hangup(media);
+		}
+	}
+}
+
+#endif /* USE_VV */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/rtp.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,91 @@
+/**
+ * @file rtp.h
+ *
+ * purple
+ *
+ * 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 JINGLE_RTP_H
+#define JINGLE_RTP_H
+
+#include "config.h"
+
+#ifdef USE_VV
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "content.h"
+#include "media.h"
+#include "xmlnode.h"
+
+G_BEGIN_DECLS
+
+#define JINGLE_TYPE_RTP            (jingle_rtp_get_type())
+#define JINGLE_RTP(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_RTP, JingleRtp))
+#define JINGLE_RTP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_RTP, JingleRtpClass))
+#define JINGLE_IS_RTP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_RTP))
+#define JINGLE_IS_RTP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_RTP))
+#define JINGLE_RTP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_RTP, JingleRtpClass))
+
+/** @copydoc _JingleRtp */
+typedef struct _JingleRtp JingleRtp;
+/** @copydoc _JingleRtpClass */
+typedef struct _JingleRtpClass JingleRtpClass;
+/** @copydoc _JingleRtpPrivate */
+typedef struct _JingleRtpPrivate JingleRtpPrivate;
+
+/** The rtp class */
+struct _JingleRtpClass
+{
+	JingleContentClass parent_class;     /**< The parent class. */
+};
+
+/** The rtp class's private data */
+struct _JingleRtp
+{
+	JingleContent parent;                /**< The parent of this object. */
+	JingleRtpPrivate *priv;      /**< The private data of this object. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the rtp class's GType
+ *
+ * @return The rtp class's GType.
+ */
+GType jingle_rtp_get_type(void);
+
+gchar *jingle_rtp_get_media_type(JingleContent *content);
+
+PurpleMedia *jingle_rtp_initiate_media(JabberStream *js,
+				   const gchar *who,
+				   PurpleMediaSessionType type);
+void jingle_rtp_terminate_session(JabberStream *js, const gchar *who);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* USE_VV */
+
+#endif /* JINGLE_RTP_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/session.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,592 @@
+/**
+ * @file session.c
+ *
+ * purple
+ *
+ * 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 "internal.h"
+
+#include "content.h"
+#include "debug.h"
+#include "session.h"
+#include "jingle.h"
+
+#include <string.h>
+
+struct _JingleSessionPrivate
+{
+	gchar *sid;
+	JabberStream *js;
+	gchar *remote_jid;
+	gchar *local_jid;
+	gboolean is_initiator;
+	gboolean state;
+	GList *contents;
+	GList *pending_contents;
+};
+
+#define JINGLE_SESSION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_SESSION, JingleSessionPrivate))
+
+static void jingle_session_class_init (JingleSessionClass *klass);
+static void jingle_session_init (JingleSession *session);
+static void jingle_session_finalize (GObject *object);
+static void jingle_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void jingle_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+
+static GObjectClass *parent_class = NULL;
+
+enum {
+	PROP_0,
+	PROP_SID,
+	PROP_JS,
+	PROP_REMOTE_JID,
+	PROP_LOCAL_JID,
+	PROP_IS_INITIATOR,
+	PROP_STATE,
+	PROP_CONTENTS,
+	PROP_PENDING_CONTENTS,
+};
+
+GType
+jingle_session_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(JingleSessionClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) jingle_session_class_init,
+			NULL,
+			NULL,
+			sizeof(JingleSession),
+			0,
+			(GInstanceInitFunc) jingle_session_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "JingleSession", &info, 0);
+	}
+	return type;
+}
+
+static void
+jingle_session_class_init (JingleSessionClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+	
+	gobject_class->finalize = jingle_session_finalize;
+	gobject_class->set_property = jingle_session_set_property;
+	gobject_class->get_property = jingle_session_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_SID,
+			g_param_spec_string("sid",
+			"Session ID",
+			"The unique session ID of the Jingle Session.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_JS,
+			g_param_spec_pointer("js",
+			"JabberStream",
+			"The Jabber stream associated with this session.",
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_REMOTE_JID,
+			g_param_spec_string("remote-jid",
+			"Remote JID",
+			"The JID of the remote participant.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_LOCAL_JID,
+			g_param_spec_string("local-jid",
+			"Local JID",
+			"The JID of the local participant.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_IS_INITIATOR,
+			g_param_spec_boolean("is-initiator",
+			"Is Initiator",
+			"Whether or not the local JID is the initiator of the session.",
+			FALSE,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_STATE,
+			g_param_spec_boolean("state",
+			"State",
+			"The state of the session (PENDING=FALSE, ACTIVE=TRUE).",
+			FALSE,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property(gobject_class, PROP_CONTENTS,
+			g_param_spec_pointer("contents",
+			"Contents",
+			"The active contents contained within this session",
+			G_PARAM_READABLE));
+
+	g_object_class_install_property(gobject_class, PROP_PENDING_CONTENTS,
+			g_param_spec_pointer("pending-contents",
+			"Pending contents",
+			"The pending contents contained within this session",
+			G_PARAM_READABLE));
+
+	g_type_class_add_private(klass, sizeof(JingleSessionPrivate));
+}
+
+static void
+jingle_session_init (JingleSession *session)
+{
+	session->priv = JINGLE_SESSION_GET_PRIVATE(session);
+	memset(session->priv, 0, sizeof(session->priv));
+}
+
+static void
+jingle_session_finalize (GObject *session)
+{
+	JingleSessionPrivate *priv = JINGLE_SESSION_GET_PRIVATE(session);
+	purple_debug_info("jingle","jingle_session_finalize\n");
+
+	g_hash_table_remove(priv->js->sessions, priv->sid);
+
+	g_free(priv->sid);
+	g_free(priv->remote_jid);
+	g_free(priv->local_jid);
+
+	for (; priv->contents; priv->contents =
+			g_list_delete_link(priv->contents, priv->contents)) {
+		g_object_unref(priv->contents->data);
+	}
+	for (; priv->pending_contents; priv->pending_contents =
+			g_list_delete_link(priv->pending_contents, priv->pending_contents)) {
+		g_object_unref(priv->pending_contents->data);
+	}
+
+	parent_class->finalize(session);
+}
+
+static void
+jingle_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	JingleSession *session;
+	g_return_if_fail(JINGLE_IS_SESSION(object));
+
+	session = JINGLE_SESSION(object);
+
+	switch (prop_id) {
+		case PROP_SID:
+			g_free(session->priv->sid);
+			session->priv->sid = g_value_dup_string(value);
+			break;
+		case PROP_JS:
+			session->priv->js = g_value_get_pointer(value);
+			break;
+		case PROP_REMOTE_JID:
+			g_free(session->priv->remote_jid);
+			session->priv->remote_jid = g_value_dup_string(value);
+			break;
+		case PROP_LOCAL_JID:
+			g_free(session->priv->local_jid);
+			session->priv->local_jid = g_value_dup_string(value);
+			break;
+		case PROP_IS_INITIATOR:
+			session->priv->is_initiator = g_value_get_boolean(value);
+			break;
+		case PROP_STATE:
+			session->priv->state = g_value_get_boolean(value);
+			break;
+		case PROP_CONTENTS:
+			session->priv->contents = g_value_get_pointer(value);
+			break;
+		case PROP_PENDING_CONTENTS:
+			session->priv->pending_contents = g_value_get_pointer(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+jingle_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	JingleSession *session;
+	g_return_if_fail(JINGLE_IS_SESSION(object));
+	
+	session = JINGLE_SESSION(object);
+
+	switch (prop_id) {
+		case PROP_SID:
+			g_value_set_string(value, session->priv->sid);
+			break;
+		case PROP_JS:
+			g_value_set_pointer(value, session->priv->js);
+			break;
+		case PROP_REMOTE_JID:
+			g_value_set_string(value, session->priv->remote_jid);
+			break;
+		case PROP_LOCAL_JID:
+			g_value_set_string(value, session->priv->local_jid);
+			break;
+		case PROP_IS_INITIATOR:
+			g_value_set_boolean(value, session->priv->is_initiator);
+			break;
+		case PROP_STATE:
+			g_value_set_boolean(value, session->priv->state);
+			break;
+		case PROP_CONTENTS:
+			g_value_set_pointer(value, session->priv->contents);
+			break;
+		case PROP_PENDING_CONTENTS:
+			g_value_set_pointer(value, session->priv->pending_contents);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+
+JingleSession *
+jingle_session_create(JabberStream *js, const gchar *sid,
+			const gchar *local_jid, const gchar *remote_jid,
+			gboolean is_initiator)
+{
+	JingleSession *session = g_object_new(jingle_session_get_type(),
+			"js", js,
+			"sid", sid,
+			"local-jid", local_jid,
+			"remote-jid", remote_jid,
+			"is_initiator", is_initiator,
+			NULL);
+
+	/* 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", sid);
+	g_hash_table_insert(js->sessions, g_strdup(sid), session);
+
+	return session;
+}
+
+JabberStream *
+jingle_session_get_js(JingleSession *session)
+{
+	JabberStream *js;
+	g_object_get(session, "js", &js, NULL);
+	return js;
+}
+
+gchar *
+jingle_session_get_sid(JingleSession *session)
+{
+	gchar *sid;
+	g_object_get(session, "sid", &sid, NULL);
+	return sid;
+}
+
+gchar *
+jingle_session_get_local_jid(JingleSession *session)
+{
+	gchar *local_jid;
+	g_object_get(session, "local-jid", &local_jid, NULL);
+	return local_jid;
+}
+
+gchar *
+jingle_session_get_remote_jid(JingleSession *session)
+{
+	gchar *remote_jid;
+	g_object_get(session, "remote-jid", &remote_jid, NULL);
+	return remote_jid;
+}
+
+gboolean
+jingle_session_is_initiator(JingleSession *session)
+{
+	gboolean is_initiator;
+	g_object_get(session, "is-initiator", &is_initiator, NULL);
+	return is_initiator;
+}
+
+gboolean
+jingle_session_get_state(JingleSession *session)
+{
+	gboolean state;
+	g_object_get(session, "state", &state, NULL);
+	return state;
+}
+
+GList *
+jingle_session_get_contents(JingleSession *session)
+{
+	GList *contents;
+	g_object_get(session, "contents", &contents, NULL);
+	return contents;
+}
+
+GList *
+jingle_session_get_pending_contents(JingleSession *session)
+{
+	GList *pending_contents;
+	g_object_get(session, "pending-contents", &pending_contents, NULL);
+	return pending_contents;
+}
+
+JingleSession *
+jingle_session_find_by_sid(JabberStream *js, const gchar *sid)
+{
+	purple_debug_info("jingle", "find_by_id %s\n", sid);
+	purple_debug_info("jingle", "lookup: %p\n", (js->sessions) ?
+			  g_hash_table_lookup(js->sessions, sid) : NULL);  
+	return (JingleSession *) (js->sessions) ?
+			  g_hash_table_lookup(js->sessions, sid) : NULL;
+}
+
+static gboolean find_by_jid_ghr(gpointer key,
+		gpointer value, gpointer user_data)
+{
+	JingleSession *session = (JingleSession *)value;
+	const gchar *jid = user_data;
+	gboolean use_bare = strchr(jid, '/') == NULL;
+	gchar *remote_jid = jingle_session_get_remote_jid(session);
+	gchar *cmp_jid = use_bare ? jabber_get_bare_jid(remote_jid)
+				  : g_strdup(remote_jid);
+	g_free(remote_jid);
+	if (!strcmp(jid, cmp_jid)) {
+		g_free(cmp_jid);
+		return TRUE;
+	}
+	g_free(cmp_jid);
+
+	return FALSE;
+}
+
+JingleSession *
+jingle_session_find_by_jid(JabberStream *js, const gchar *jid)
+{
+	return js->sessions != NULL ?
+			g_hash_table_find(js->sessions,
+			find_by_jid_ghr, (gpointer)jid) : NULL; 
+}
+
+static xmlnode *
+jingle_add_jingle_packet(JingleSession *session,
+			 JabberIq *iq, JingleActionType action)
+{
+	xmlnode *jingle = iq ?
+			xmlnode_new_child(iq->node, "jingle") :
+			xmlnode_new("jingle");
+	gchar *local_jid = jingle_session_get_local_jid(session);
+	gchar *remote_jid = jingle_session_get_remote_jid(session);
+
+	xmlnode_set_namespace(jingle, JINGLE);
+	xmlnode_set_attrib(jingle, "action", jingle_get_action_name(action));
+
+	if (jingle_session_is_initiator(session)) {
+		xmlnode_set_attrib(jingle, "initiator",
+				jingle_session_get_local_jid(session));
+		xmlnode_set_attrib(jingle, "responder",
+				jingle_session_get_remote_jid(session));
+	} else {
+		xmlnode_set_attrib(jingle, "initiator",
+				jingle_session_get_remote_jid(session));
+		xmlnode_set_attrib(jingle, "responder",
+				jingle_session_get_local_jid(session));
+	}
+
+	g_free(local_jid);
+	g_free(remote_jid);
+
+	xmlnode_set_attrib(jingle, "sid", jingle_session_get_sid(session));
+	
+	return jingle;
+}
+
+JabberIq *
+jingle_session_create_ack(JingleSession *session, const xmlnode *jingle)
+{
+	JabberIq *result = jabber_iq_new(
+			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 *
+jingle_create_iq(JingleSession *session)
+{
+	JabberStream *js = jingle_session_get_js(session);
+	JabberIq *result = jabber_iq_new(js, JABBER_IQ_SET);
+	gchar *from = jingle_session_get_local_jid(session);
+	gchar *to = jingle_session_get_remote_jid(session);
+
+	xmlnode_set_attrib(result->node, "from", from);
+	xmlnode_set_attrib(result->node, "to", to);
+
+	g_free(from);
+	g_free(to);
+	return result;
+}
+
+xmlnode *
+jingle_session_to_xml(JingleSession *session, xmlnode *jingle, JingleActionType action)
+{
+	if (action != JINGLE_SESSION_INFO && action != JINGLE_SESSION_TERMINATE) {
+		GList *iter;
+		if (action == JINGLE_CONTENT_ACCEPT
+				|| action == JINGLE_CONTENT_ADD
+				|| action == JINGLE_CONTENT_REMOVE)
+			iter = jingle_session_get_pending_contents(session);
+		else
+			iter = jingle_session_get_contents(session);
+
+		for (; iter; iter = g_list_next(iter)) {
+			jingle_content_to_xml(iter->data, jingle, action);
+		}
+	}
+	return jingle;
+}
+
+JabberIq *
+jingle_session_to_packet(JingleSession *session, JingleActionType action)
+{
+	JabberIq *iq = jingle_create_iq(session);
+	xmlnode *jingle = jingle_add_jingle_packet(session, iq, action);
+	jingle_session_to_xml(session, jingle, action);
+	return iq;
+}
+
+void jingle_session_handle_action(JingleSession *session, xmlnode *jingle, JingleActionType action)
+{
+	GList *iter;
+	if (action == JINGLE_CONTENT_ADD || action == JINGLE_CONTENT_REMOVE)
+		iter = jingle_session_get_pending_contents(session);
+	else
+		iter = jingle_session_get_contents(session);
+
+	for (; iter; iter = g_list_next(iter)) {
+		jingle_content_handle_action(iter->data, jingle, action);
+	}
+}
+
+JingleContent *
+jingle_session_find_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	GList *iter = session->priv->contents;
+	for (; iter; iter = g_list_next(iter)) {
+		JingleContent *content = iter->data;
+		gchar *cname = jingle_content_get_name(content);
+		gchar *ccreator = jingle_content_get_creator(content);
+		gboolean result = (!strcmp(name, cname) && !strcmp(creator, ccreator));
+
+		g_free(cname);
+		g_free(ccreator);
+
+		if (result == TRUE)
+			return content;
+	}
+	return NULL;
+}
+
+JingleContent *
+jingle_session_find_pending_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	GList *iter = session->priv->pending_contents;
+	for (; iter; iter = g_list_next(iter)) {
+		JingleContent *content = iter->data;
+		gchar *cname = jingle_content_get_name(content);
+		gchar *ccreator = jingle_content_get_creator(content);
+		gboolean result = (!strcmp(name, cname) && !strcmp(creator, ccreator));
+
+		g_free(cname);
+		g_free(ccreator);
+
+		if (result == TRUE)
+			return content;
+	}
+	return NULL;
+}
+
+void
+jingle_session_add_content(JingleSession *session, JingleContent* content)
+{
+	session->priv->contents =
+			g_list_append(session->priv->contents, content);
+	jingle_content_set_session(content, session);
+}
+
+void
+jingle_session_remove_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	JingleContent *content =
+			jingle_session_find_content(session, name, creator);
+
+	if (content) {
+		session->priv->contents =
+				g_list_remove(session->priv->contents, content);
+		g_object_unref(content);
+	}
+}
+
+void
+jingle_session_add_pending_content(JingleSession *session, JingleContent* content)
+{
+	session->priv->pending_contents =
+			g_list_append(session->priv->pending_contents, content);
+	jingle_content_set_session(content, session);
+}
+
+void
+jingle_session_remove_pending_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	JingleContent *content = jingle_session_find_pending_content(session, name, creator);
+
+	if (content) {
+		session->priv->pending_contents =
+				g_list_remove(session->priv->pending_contents, content);
+		g_object_unref(content);
+	}
+}
+
+void
+jingle_session_accept_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	JingleContent *content = jingle_session_find_pending_content(session, name, creator);
+
+	if (content) {
+		g_object_ref(content);
+		jingle_session_remove_pending_content(session, name, creator);
+		jingle_session_add_content(session, content);
+	}
+}
+
+void
+jingle_session_accept_session(JingleSession *session)
+{
+	session->priv->state = TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/session.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,113 @@
+/**
+ * @file session.h
+ *
+ * purple
+ *
+ * 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 JINGLE_SESSION_H
+#define JINGLE_SESSION_H
+
+#include "iq.h"
+#include "jabber.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define JINGLE_TYPE_SESSION            (jingle_session_get_type())
+#define JINGLE_SESSION(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_SESSION, JingleSession))
+#define JINGLE_SESSION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_SESSION, JingleSessionClass))
+#define JINGLE_IS_SESSION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_SESSION))
+#define JINGLE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_SESSION))
+#define JINGLE_SESSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_SESSION, JingleSessionClass))
+
+/** @copydoc _JingleSession */
+typedef struct _JingleSession JingleSession;
+/** @copydoc _JingleSessionClass */
+typedef struct _JingleSessionClass JingleSessionClass;
+/** @copydoc _JingleSessionPrivate */
+typedef struct _JingleSessionPrivate JingleSessionPrivate;
+
+/** The session class */
+struct _JingleSessionClass
+{
+	GObjectClass parent_class;     /**< The parent class. */
+};
+
+/** The session class's private data */
+struct _JingleSession
+{
+	GObject parent;                /**< The parent of this object. */
+	JingleSessionPrivate *priv;      /**< The private data of this object. */
+};
+
+struct _JingleContent;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the session class's GType
+ *
+ * @return The session class's GType.
+ */
+GType jingle_session_get_type(void);
+
+JingleSession *jingle_session_create(JabberStream *js, const gchar *sid,
+				     const gchar *local_jid, const gchar *remote_jid,
+				     gboolean is_initiator);
+JabberStream *jingle_session_get_js(JingleSession *session);
+gchar *jingle_session_get_sid(JingleSession *session);
+gchar *jingle_session_get_local_jid(JingleSession *session);
+gchar *jingle_session_get_remote_jid(JingleSession *session);
+gboolean jingle_session_is_initiator(JingleSession *session);
+gboolean jingle_session_get_state(JingleSession *session);
+
+GList *jingle_session_get_contents(JingleSession *session);
+GList *jingle_session_get_pending_contents(JingleSession *session);
+
+JingleSession *jingle_session_find_by_sid(JabberStream *js, const gchar *sid);
+JingleSession *jingle_session_find_by_jid(JabberStream *js, const gchar *jid);
+
+JabberIq *jingle_session_create_ack(JingleSession *session, const xmlnode *jingle);
+xmlnode *jingle_session_to_xml(JingleSession *session, xmlnode *parent, JingleActionType action);
+JabberIq *jingle_session_to_packet(JingleSession *session, JingleActionType action);
+
+void jingle_session_handle_action(JingleSession *session, xmlnode *jingle, JingleActionType action);
+
+struct _JingleContent *jingle_session_find_content(JingleSession *session,
+					const gchar *name, const gchar *creator);
+struct _JingleContent *jingle_session_find_pending_content(JingleSession *session,
+					const gchar *name, const gchar *creator);
+
+void jingle_session_add_content(JingleSession *session, struct _JingleContent* content);
+void jingle_session_remove_content(JingleSession *session, const gchar *name, const gchar *creator);
+void jingle_session_add_pending_content(JingleSession *session, struct _JingleContent* content);
+void jingle_session_remove_pending_content(JingleSession *session, const gchar *name, const gchar *creator);
+void jingle_session_accept_content(JingleSession *session, const gchar *name, const gchar *creator);
+void jingle_session_accept_session(JingleSession *session);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_SESSION_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/transport.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,174 @@
+/**
+ * @file transport.c
+ *
+ * purple
+ *
+ * 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 "internal.h"
+
+#include "transport.h"
+#include "jingle.h"
+#include "debug.h"
+
+#include <string.h>
+
+struct _JingleTransportPrivate
+{
+	void *dummy;
+};
+
+#define JINGLE_TRANSPORT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_TRANSPORT, JingleTransportPrivate))
+
+static void jingle_transport_class_init (JingleTransportClass *klass);
+static void jingle_transport_init (JingleTransport *transport);
+static void jingle_transport_finalize (GObject *object);
+static void jingle_transport_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void jingle_transport_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+JingleTransport *jingle_transport_parse_internal(xmlnode *transport);
+xmlnode *jingle_transport_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action);
+
+static GObjectClass *parent_class = NULL;
+
+enum {
+	PROP_0,
+};
+
+GType
+jingle_transport_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(JingleTransportClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) jingle_transport_class_init,
+			NULL,
+			NULL,
+			sizeof(JingleTransport),
+			0,
+			(GInstanceInitFunc) jingle_transport_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "JingleTransport", &info, 0);
+	}
+	return type;
+}
+
+static void
+jingle_transport_class_init (JingleTransportClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = jingle_transport_finalize;
+	gobject_class->set_property = jingle_transport_set_property;
+	gobject_class->get_property = jingle_transport_get_property;
+	klass->to_xml = jingle_transport_to_xml_internal;
+	klass->parse = jingle_transport_parse_internal;
+
+	g_type_class_add_private(klass, sizeof(JingleTransportPrivate));
+}
+
+static void
+jingle_transport_init (JingleTransport *transport)
+{
+	transport->priv = JINGLE_TRANSPORT_GET_PRIVATE(transport);
+	memset(transport->priv, 0, sizeof(transport->priv));
+}
+
+static void
+jingle_transport_finalize (GObject *transport)
+{
+	/* JingleTransportPrivate *priv = JINGLE_TRANSPORT_GET_PRIVATE(transport); */
+	purple_debug_info("jingle","jingle_transport_finalize\n");
+
+	parent_class->finalize(transport);
+}
+
+static void
+jingle_transport_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	JingleTransport *transport;
+	g_return_if_fail(JINGLE_IS_TRANSPORT(object));
+
+	transport = JINGLE_TRANSPORT(object);
+
+	switch (prop_id) {
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+jingle_transport_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	JingleTransport *transport;
+	g_return_if_fail(JINGLE_IS_TRANSPORT(object));
+	
+	transport = JINGLE_TRANSPORT(object);
+
+	switch (prop_id) {
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+JingleTransport *
+jingle_transport_create(const gchar *type)
+{
+	return g_object_new(jingle_get_type(type), NULL);
+}
+
+const gchar *
+jingle_transport_get_transport_type(JingleTransport *transport)
+{
+	return JINGLE_TRANSPORT_GET_CLASS(transport)->transport_type;
+}
+
+JingleTransport *
+jingle_transport_parse_internal(xmlnode *transport)
+{
+	const gchar *type = xmlnode_get_namespace(transport);
+	return jingle_transport_create(type);
+}
+
+xmlnode *
+jingle_transport_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action)
+{
+	xmlnode *node = xmlnode_new_child(content, "transport");
+	xmlnode_set_namespace(node, jingle_transport_get_transport_type(transport));
+	return node;
+}
+
+JingleTransport *
+jingle_transport_parse(xmlnode *transport)
+{
+	const gchar *type = xmlnode_get_namespace(transport);
+	return JINGLE_TRANSPORT_CLASS(g_type_class_ref(jingle_get_type(type)))->parse(transport);
+}
+
+xmlnode *
+jingle_transport_to_xml(JingleTransport *transport, xmlnode *content, JingleActionType action)
+{
+	g_return_val_if_fail(JINGLE_IS_TRANSPORT(transport), NULL);
+	return JINGLE_TRANSPORT_GET_CLASS(transport)->to_xml(transport, content, action);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/transport.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,88 @@
+/**
+ * @file transport.h
+ *
+ * purple
+ *
+ * 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 JINGLE_TRANSPORT_H
+#define JINGLE_TRANSPORT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "jingle.h"
+#include "xmlnode.h"
+
+G_BEGIN_DECLS
+
+#define JINGLE_TYPE_TRANSPORT            (jingle_transport_get_type())
+#define JINGLE_TRANSPORT(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_TRANSPORT, JingleTransport))
+#define JINGLE_TRANSPORT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_TRANSPORT, JingleTransportClass))
+#define JINGLE_IS_TRANSPORT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_TRANSPORT))
+#define JINGLE_IS_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_TRANSPORT))
+#define JINGLE_TRANSPORT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_TRANSPORT, JingleTransportClass))
+
+/** @copydoc _JingleTransport */
+typedef struct _JingleTransport JingleTransport;
+/** @copydoc _JingleTransportClass */
+typedef struct _JingleTransportClass JingleTransportClass;
+/** @copydoc _JingleTransportPrivate */
+typedef struct _JingleTransportPrivate JingleTransportPrivate;
+
+/** The transport class */
+struct _JingleTransportClass
+{
+	GObjectClass parent_class;     /**< The parent class. */
+
+	const gchar *transport_type;
+	xmlnode *(*to_xml) (JingleTransport *transport, xmlnode *content, JingleActionType action);
+	JingleTransport *(*parse) (xmlnode *transport);
+};
+
+/** The transport class's private data */
+struct _JingleTransport
+{
+	GObject parent;                /**< The parent of this object. */
+	JingleTransportPrivate *priv;      /**< The private data of this object. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the transport class's GType
+ *
+ * @return The transport class's GType.
+ */
+GType jingle_transport_get_type(void);
+
+JingleTransport *jingle_transport_create(const gchar *type);
+const gchar *jingle_transport_get_transport_type(JingleTransport *transport);
+void jingle_transport_add_candidate();
+
+JingleTransport *jingle_transport_parse(xmlnode *transport);
+xmlnode *jingle_transport_to_xml(JingleTransport *transport, xmlnode *content, JingleActionType action);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_TRANSPORT_H */
+
--- a/libpurple/protocols/jabber/libxmpp.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Thu Feb 05 00:31:35 2009 +0000
@@ -116,9 +116,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)
@@ -289,6 +295,9 @@
 					   jabber_custom_smileys_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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Thu Feb 05 00:31:35 2009 +0000
@@ -264,6 +264,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/msn/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -91,4 +91,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/msn/msn.c	Thu Feb 05 00:31:35 2009 +0000
@@ -2585,9 +2585,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 =
@@ -2666,3 +2667,4 @@
 }
 
 PURPLE_INIT_PLUGIN(msn, init_plugin, info);
+
--- a/libpurple/protocols/msnp9/Makefile.am	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/msnp9/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/msnp9/msn.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/myspace/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -41,4 +41,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Thu Feb 05 00:31:35 2009 +0000
@@ -3077,9 +3077,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/novell/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -50,4 +50,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/novell/novell.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/null/nullprpl.c	Thu Feb 05 00:31:35 2009 +0000
@@ -1130,11 +1130,13 @@
   NULL,                                /* whiteboard_prpl_ops */
   NULL,                                /* send_raw */
   NULL,                                /* roomlist_room_serialize */
-  NULL,                                /* padding... */
-  NULL,
+  NULL,                                /* unregister_user */
+  NULL,                                /* send_attention */
+  NULL,                                /* get_attention_types */
+  sizeof(PurplePluginProtocolInfo),    /* struct_size */
   NULL,
-	sizeof(PurplePluginProtocolInfo),    /* struct_size */
-  NULL
+  NULL,                                 /* initiate_media */
+  NULL                                  /* can_do_media */	
 };
 
 static void nullprpl_init(PurplePlugin *plugin)
--- a/libpurple/protocols/oscar/Makefile.am	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/oscar/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -75,4 +75,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Thu Feb 05 00:31:35 2009 +0000
@@ -26,7 +26,6 @@
 
 
 #include "oscarcommon.h"
-
 static GHashTable *
 icq_get_account_text_table(PurpleAccount *account)
 {
@@ -107,6 +106,8 @@
 
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	icq_get_account_text_table, /* get_account_text_table */
+	NULL,					/* initiate_media */
+	NULL					/* can_do_media */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/qq/Makefile.am	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/qq/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -74,4 +74,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/qq/qq.c	Thu Feb 05 00:31:35 2009 +0000
@@ -991,7 +991,9 @@
 	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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/sametime/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -33,5 +33,6 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(MEANWHILE_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	-DG_LOG_DOMAIN=\"sametime\"
 
--- a/libpurple/protocols/sametime/sametime.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Thu Feb 05 00:31:35 2009 +0000
@@ -5192,7 +5192,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/silc/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -34,7 +34,7 @@
 st = $(SILC_CFLAGS)
 pkg_LTLIBRARIES          = libsilcpurple.la
 libsilcpurple_la_SOURCES = $(SILCSOURCES)
-libsilcpurple_la_LIBADD  = $(GLIB_LIBS) $(SILC_LIBS)
+libsilcpurple_la_LIBADD  = $(GLIB_LIBS) $(SILC_LIBS) $(FARSIGHT_LIBS)
 
 endif
 
@@ -43,4 +43,7 @@
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
-	$(SILC_CFLAGS)
+	$(SILC_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
--- a/libpurple/protocols/silc/silc.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/silc/silc.c	Thu Feb 05 00:31:35 2009 +0000
@@ -2109,13 +2109,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 =
@@ -2249,3 +2249,4 @@
 }
 
 PURPLE_INIT_PLUGIN(silc, init_plugin, info);
+
--- a/libpurple/protocols/silc10/silc.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/silc10/silc.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/simple/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/simple/simple.c	Thu Feb 05 00:31:35 2009 +0000
@@ -2088,13 +2088,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 */
 };
 
 
@@ -2160,3 +2160,4 @@
 }
 
 PURPLE_INIT_PLUGIN(simple, _init_plugin, info);
+
--- a/libpurple/protocols/yahoo/Makefile.am	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/yahoo/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -53,4 +53,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
\ No newline at end of file
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/yahoo/yahoo.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Thu Feb 05 00:31:35 2009 +0000
@@ -4445,6 +4445,8 @@
 
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	yahoo_get_account_text_table,    /* get_account_text_table */
+	NULL, /* initiate_media */
+	NULL  /* can_do_media */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/zephyr/Makefile.am	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/zephyr/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -105,5 +105,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/protocols/zephyr/zephyr.c	Thu Feb 05 00:31:35 2009 +0000
@@ -2917,7 +2917,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.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/prpl.c	Thu Feb 05 00:31:35 2009 +0000
@@ -496,6 +496,61 @@
 	got_attention(gc, id, who, type_code);
 }
 
+PurpleMedia *
+purple_prpl_initiate_media(PurpleAccount *account,
+			   const char *who,
+			   PurpleMediaSessionType type)
+{
+#ifdef USE_VV
+	PurpleConnection *gc = NULL;
+	PurplePlugin *prpl = NULL;
+	PurplePluginProtocolInfo *prpl_info = NULL;
+
+	if (account)
+		gc = purple_account_get_connection(account);
+	if (gc)
+		prpl = purple_connection_get_prpl(gc);
+	if (prpl)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+
+	if (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, initiate_media)) {
+		/* should check that the protocol supports this media type here? */
+		return prpl_info->initiate_media(gc, who, type);
+	} else {
+		return NULL;
+	}
+#else
+	return NULL;
+#endif
+}
+
+gboolean
+purple_prpl_can_do_media(PurpleAccount *account,
+			 const char *who, 
+			 PurpleMediaSessionType type)
+{
+#ifdef USE_VV
+	PurpleConnection *gc = NULL;
+	PurplePlugin *prpl = NULL;
+	PurplePluginProtocolInfo *prpl_info = NULL;
+	
+	if (account)
+		gc = purple_account_get_connection(account);
+	if (gc)
+		prpl = purple_connection_get_prpl(gc);
+	if (prpl)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+	
+	if (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, can_do_media)) {
+		return prpl_info->can_do_media(gc, who, type);
+	} else {
+		return FALSE;
+	}
+#else
+	return FALSE;
+#endif
+}
+
 /**************************************************************************
  * Protocol Plugin Subsystem API
  **************************************************************************/
@@ -517,3 +572,4 @@
 
 	return NULL;
 }
+
--- a/libpurple/prpl.h	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/prpl.h	Thu Feb 05 00:31:35 2009 +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,7 +73,6 @@
 #include "status.h"
 #include "whiteboard.h"
 
-
 /** @copydoc PurpleBuddyIconSpec */
 struct _PurpleBuddyIconSpec {
 	/** This is a comma-delimited list of image formats or @c NULL if icons
@@ -413,7 +413,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);
@@ -449,6 +449,28 @@
 	 *         destroyed by the caller when it's no longer needed.
 	 */
 	GHashTable *(*get_account_text_table)(PurpleAccount *account);
+
+	/**
+	 * Initiate a media session with the given contact.
+	 *
+	 * @param conn The connection to initiate the media session on.
+	 * @param who The remote user to initiate the session with.
+	 * @param type The type of media session to initiate.
+	 * @return The newly created media object.
+	 */
+	PurpleMedia  *(*initiate_media)(PurpleConnection *gc, const char *who,
+					PurpleMediaSessionType type);
+
+	/**
+	 * Checks to see if the given contact supports the given type of media session.
+	 *
+	 * @param conn The connection the contact is on.
+	 * @param who The remote user to check for media capability with.
+	 * @param type The type of media session to check for.
+	 * @return @c TRUE The contact supports the given media type, or @c FALSE otherwise.
+	 */
+	gboolean (*can_do_media)(PurpleConnection *gc, const char *who,
+				 PurpleMediaSessionType type);
 };
 
 #define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \
@@ -738,6 +760,32 @@
  */
 void purple_prpl_got_attention_in_chat(PurpleConnection *gc, int id, const char *who, guint type_code);
 
+/**
+ * Determines if the contact supports the given media session type.
+ *
+ * @param account The account the user is on.
+ * @param who The name of the contact to check capabilities for.
+ * @param type The type of media session to check for.
+ *
+ * @return @c TRUE if the contact supports the session type, else @c FALSE.
+ */
+gboolean purple_prpl_can_do_media(PurpleAccount *account,
+				  const char *who, 
+				  PurpleMediaSessionType type);
+
+/**
+ * Initiates a media session with the given contact.
+ *
+ * @param account The account the user is on.
+ * @param who The name of the contact to start a session with.
+ * @param type The type of media session to start.
+ *
+ * @return The newly created session object.
+ */
+PurpleMedia *purple_prpl_initiate_media(PurpleAccount *account,
+					const char *who,
+					PurpleMediaSessionType type);
+
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/xmlnode.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/libpurple/xmlnode.c	Thu Feb 05 00:31:35 2009 +0000
@@ -309,6 +309,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 Feb 04 05:15:49 2009 +0000
+++ b/libpurple/xmlnode.h	Thu Feb 05 00:31:35 2009 +0000
@@ -246,6 +246,15 @@
 const char *xmlnode_get_prefix(const 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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/Makefile.am	Thu Feb 05 00:31:35 2009 +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.h \
 	gtkmenutray.h \
 	gtknickcolors.h \
 	gtknotify.h \
@@ -197,6 +199,8 @@
 	$(STARTUP_NOTIFICATION_LIBS) \
 	$(LIBXML_LIBS) \
 	$(GTK_LIBS) \
+	$(FARSIGHT_LIBS) \
+	$(GSTPROPS_LIBS) \
 	$(top_builddir)/libpurple/libpurple.la
 
 if USE_INTERNAL_LIBGADU
@@ -221,5 +225,7 @@
 	$(GTKSPELL_CFLAGS) \
 	$(STARTUP_NOTIFICATION_CFLAGS) \
 	$(LIBXML_CFLAGS) \
-	$(INTGG_CFLAGS)
+	$(INTGG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTPROPS_CFLAGS)
 endif  # ENABLE_GTK
--- a/pidgin/Makefile.mingw	Wed Feb 04 05:15:49 2009 +0000
+++ b/pidgin/Makefile.mingw	Thu Feb 05 00:31:35 2009 +0000
@@ -73,6 +73,7 @@
 			gtkimhtmltoolbar.c \
 			gtklog.c \
 			gtkmain.c \
+			gtkmedia.c \
 			gtkmenutray.c \
 			gtknotify.c \
 			gtkplugin.c \
--- a/pidgin/gtkblist.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkblist.c	Thu Feb 05 00:31:35 2009 +0000
@@ -337,6 +337,29 @@
 	pidgin_dialogs_im_with_user(b->account, b->name);
 }
 
+#ifdef USE_VV
+static void gtk_blist_menu_audio_call_cb(GtkWidget *w, PurpleBuddy *b)
+{
+	purple_prpl_initiate_media(purple_buddy_get_account(b),
+		purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO);
+}
+
+static void gtk_blist_menu_video_call_cb(GtkWidget *w, PurpleBuddy *b)
+{
+	/* if the buddy supports both audio and video, start a combined call,
+	 otherwise start a pure video session */
+	if (purple_prpl_can_do_media(purple_buddy_get_account(b),
+		purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO)) {
+		purple_prpl_initiate_media(purple_buddy_get_account(b),
+			purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO);
+	} else {
+		purple_prpl_initiate_media(purple_buddy_get_account(b),
+			purple_buddy_get_name(b), PURPLE_MEDIA_VIDEO);
+	}
+}
+
+#endif
+
 static void gtk_blist_menu_send_file_cb(GtkWidget *w, PurpleBuddy *b)
 {
 	serv_send_file(b->account->gc, b->name, NULL);
@@ -1438,6 +1461,30 @@
 	}
 	pidgin_new_item_from_stock(menu, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW,
 			G_CALLBACK(gtk_blist_menu_im_cb), buddy, 0, 0, NULL);
+	
+#ifdef USE_VV
+	if (prpl_info && prpl_info->can_do_media) {
+		PurpleConnection *gc = 
+			purple_account_get_connection(purple_buddy_get_account(buddy));
+		const gchar *who = purple_buddy_get_name(buddy);
+		if (prpl_info->can_do_media(gc, who, PURPLE_MEDIA_AUDIO)) {
+			pidgin_new_item_from_stock(menu, _("_Audio Call"),
+				PIDGIN_STOCK_TOOLBAR_AUDIO_CALL,
+				G_CALLBACK(gtk_blist_menu_audio_call_cb), buddy, 0, 0, NULL);
+		}
+		if (prpl_info->can_do_media(gc, who, PURPLE_MEDIA_VIDEO | PURPLE_MEDIA_AUDIO)) {
+			pidgin_new_item_from_stock(menu, _("Audio/_Video Call"),
+				PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
+				G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
+		} else if (prpl_info->can_do_media(gc, who, PURPLE_MEDIA_VIDEO)) {
+			pidgin_new_item_from_stock(menu, _("_Video Call"),
+				PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
+				G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
+		}
+	}
+	
+#endif
+	
 	if (prpl_info && prpl_info->send_file) {
 		if (!prpl_info->can_receive_file ||
 			prpl_info->can_receive_file(buddy->account->gc, buddy->name))
--- a/pidgin/gtkconv.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkconv.c	Thu Feb 05 00:31:35 2009 +0000
@@ -1199,6 +1199,23 @@
 	gtk_widget_grab_focus(s->entry);
 }
 
+#ifdef USE_VV
+static void 
+menu_initiate_media_call_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = (PidginWindow *)data;
+	PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win);
+	PurpleAccount *account = purple_conversation_get_account(conv);
+
+	purple_prpl_initiate_media(account,
+			purple_conversation_get_name(conv),
+			action == 0 ? PURPLE_MEDIA_AUDIO :
+			action == 1 ? PURPLE_MEDIA_VIDEO :
+			action == 2 ? PURPLE_MEDIA_AUDIO |
+			PURPLE_MEDIA_VIDEO : PURPLE_MEDIA_NONE);
+}
+#endif
+
 static void
 menu_send_file_cb(gpointer data, guint action, GtkWidget *widget)
 {
@@ -3109,6 +3126,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_media_call_cb, 0,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL },
+	{ N_("/Conversation/Media/_Video Call"), NULL, menu_initiate_media_call_cb, 1,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL },
+	{ N_("/Conversation/Media/Audio\\/Video _Call"), NULL, menu_initiate_media_call_cb, 2,
+		"<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 },
@@ -3419,6 +3447,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 =
@@ -6401,6 +6441,36 @@
 		else
 			buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
 
+#ifdef USE_VV
+		/* check if account support voice calls, and if the current buddy
+			supports it */
+		if (account != NULL && purple_conversation_get_type(conv)
+					== PURPLE_CONV_TYPE_IM) {
+			gboolean audio = purple_prpl_can_do_media(account,
+					purple_conversation_get_name(conv),
+					PURPLE_MEDIA_AUDIO);
+			gboolean video = purple_prpl_can_do_media(account,
+					purple_conversation_get_name(conv),
+					PURPLE_MEDIA_VIDEO);
+			gboolean av = purple_prpl_can_do_media(account,
+					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));
@@ -6938,7 +7008,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);
 
@@ -7779,7 +7849,6 @@
                                 hide_new_pref_cb, NULL);
 
 
-
 	/**********************************************************************
 	 * Register signals
 	 **********************************************************************/
--- a/pidgin/gtkconvwin.h	Wed Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkconvwin.h	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkdebug.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkdialogs.c	Thu Feb 05 00:31:35 2009 +0000
@@ -637,6 +637,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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkimhtmltoolbar.h	Thu Feb 05 00:31:35 2009 +0000
@@ -76,6 +76,7 @@
 	char *sml;
 	GtkWidget *strikethrough;
 	GtkWidget *insert_hr;
+	GtkWidget *call;
 };
 
 struct _GtkIMHtmlToolbarClass {
--- a/pidgin/gtkmain.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkmain.c	Thu Feb 05 00:31:35 2009 +0000
@@ -53,6 +53,7 @@
 #include "gtkft.h"
 #include "gtkidle.h"
 #include "gtklog.h"
+#include "gtkmedia.h"
 #include "gtknotify.h"
 #include "gtkplugin.h"
 #include "gtkpounce.h"
@@ -310,6 +311,7 @@
 	pidgin_log_init();
 	pidgin_docklet_init();
 	pidgin_smileys_init();
+	pidgin_medias_init();
 }
 
 static GHashTable *ui_info = NULL;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkmedia.c	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,872 @@
+/**
+ * @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 "mediamanager.h"
+#include "pidgin.h"
+#include "request.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;
+	gchar *screenname;
+	GstElement *send_level;
+	GstElement *recv_level;
+
+	GtkWidget *statusbar;
+
+	GtkWidget *calling;
+	GtkWidget *accept;
+	GtkWidget *reject;
+	GtkWidget *hangup;
+	GtkWidget *mute;
+
+	GtkWidget *send_progress;
+	GtkWidget *recv_progress;
+
+	PidginMediaState state;
+
+	GtkWidget *display;
+	GtkWidget *send_widget;
+	GtkWidget *recv_widget;
+	GtkWidget *local_video;
+	GtkWidget *remote_video;
+	PurpleConnection *pc;
+};
+
+#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_dispose (GObject *object);
+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 GtkWindowClass *parent_class = NULL;
+
+
+#if 0
+enum {
+	LAST_SIGNAL
+};
+static guint pidgin_media_signals[LAST_SIGNAL] = {0};
+#endif
+
+enum {
+	PROP_0,
+	PROP_MEDIA,
+	PROP_SCREENNAME,
+	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_WINDOW, "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->dispose = pidgin_media_dispose;
+	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_SCREENNAME,
+			g_param_spec_string("screenname",
+			"Screenname",
+			"The screenname of the user this session is with.",
+			NULL,
+			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));
+
+	g_type_class_add_private(klass, sizeof(PidginMediaPrivate));
+}
+
+static void
+pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media)
+{
+	purple_media_mute(media->priv->media,
+			gtk_toggle_button_get_active(toggle));
+}
+
+static gboolean
+pidgin_media_delete_event_cb(GtkWidget *widget,
+		GdkEvent *event, PidginMedia *media)
+{
+	if (media->priv->media)
+		purple_media_hangup(media->priv->media);
+	return FALSE;
+}
+
+static int
+pidgin_x_error_handler(Display *display, XErrorEvent *event)
+{
+	const gchar *error_type;
+	switch (event->error_code) {
+#define XERRORCASE(type) case type: error_type = #type; break
+		XERRORCASE(BadAccess);
+		XERRORCASE(BadAlloc);
+		XERRORCASE(BadAtom);
+		XERRORCASE(BadColor);
+		XERRORCASE(BadCursor);
+		XERRORCASE(BadDrawable);
+		XERRORCASE(BadFont);
+		XERRORCASE(BadGC);
+		XERRORCASE(BadIDChoice);
+		XERRORCASE(BadImplementation);
+		XERRORCASE(BadLength);
+		XERRORCASE(BadMatch);
+		XERRORCASE(BadName);
+		XERRORCASE(BadPixmap);
+		XERRORCASE(BadRequest);
+		XERRORCASE(BadValue);
+		XERRORCASE(BadWindow);
+#undef XERRORCASE
+		default:
+			error_type = "unknown";
+			break;
+	}
+	purple_debug_error("media", "A %s Xlib error has occurred. "
+			"The program would normally crash now.\n",
+			error_type);
+	return 0;
+}
+
+static void
+pidgin_media_init (PidginMedia *media)
+{
+	GtkWidget *vbox, *hbox;
+	media->priv = PIDGIN_MEDIA_GET_PRIVATE(media);
+
+	XSetErrorHandler(pidgin_x_error_handler);
+
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtk_container_add(GTK_CONTAINER(media), vbox);
+
+	media->priv->statusbar = gtk_statusbar_new();
+	gtk_box_pack_end(GTK_BOX(vbox), media->priv->statusbar,
+			FALSE, FALSE, 0);
+	gtk_statusbar_push(GTK_STATUSBAR(media->priv->statusbar),
+			0, _("Connecting..."));
+	gtk_widget_show(media->priv->statusbar);
+
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+	gtk_widget_show(GTK_WIDGET(hbox));
+
+	media->priv->calling = gtk_label_new("Calling...");
+	media->priv->hangup = gtk_button_new_with_mnemonic("_Hangup");
+	media->priv->accept = gtk_button_new_with_mnemonic("_Accept");
+	media->priv->reject = gtk_button_new_with_mnemonic("_Reject");
+	media->priv->mute = gtk_toggle_button_new_with_mnemonic("_Mute");
+
+	g_signal_connect(media->priv->mute, "toggled",
+			G_CALLBACK(pidgin_media_mute_toggled), media);
+
+	gtk_box_pack_end(GTK_BOX(hbox), media->priv->reject, FALSE, FALSE, 0);
+	gtk_box_pack_end(GTK_BOX(hbox), media->priv->accept, FALSE, FALSE, 0);
+	gtk_box_pack_end(GTK_BOX(hbox), media->priv->hangup, FALSE, FALSE, 0);
+	gtk_box_pack_end(GTK_BOX(hbox), media->priv->mute, FALSE, FALSE, 0);
+	gtk_box_pack_end(GTK_BOX(hbox), media->priv->calling, 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);
+	gtk_box_pack_start(GTK_BOX(vbox), media->priv->display,
+			TRUE, TRUE, PIDGIN_HIG_BOX_SPACE);
+	gtk_widget_show(vbox);
+
+	g_signal_connect(G_OBJECT(media), "delete-event",
+			G_CALLBACK(pidgin_media_delete_event_cb), media);
+}
+
+static gboolean
+level_message_cb(GstBus *bus, GstMessage *message, PidginMedia *gtkmedia)
+{
+	gdouble rms_db;
+	gdouble percent;
+	const GValue *list;
+	const GValue *value;
+
+	GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(message));
+	GtkWidget *progress;
+
+	if (message->type != GST_MESSAGE_ELEMENT)
+		return TRUE;
+
+	if (gst_structure_has_name(
+			gst_message_get_structure(message), "level"))
+		return TRUE;
+
+	if (src == gtkmedia->priv->send_level)
+		progress = gtkmedia->priv->send_progress;
+	else if (src == gtkmedia->priv->recv_level)
+		progress = gtkmedia->priv->recv_progress;
+	else
+		return TRUE;
+
+	list = gst_structure_get_value(
+			gst_message_get_structure(message), "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;
+
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(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_dispose(GObject *media)
+{
+	PidginMedia *gtkmedia = PIDGIN_MEDIA(media);
+	purple_debug_info("gtkmedia", "pidgin_media_dispose\n");
+
+	if (gtkmedia->priv->media) {
+		purple_media_remove_output_windows(gtkmedia->priv->media);
+		pidgin_media_disconnect_levels(gtkmedia->priv->media, gtkmedia);
+		g_object_unref(gtkmedia->priv->media);
+		gtkmedia->priv->media = NULL;
+	}
+
+	if (gtkmedia->priv->send_level) {
+		gst_object_unref(gtkmedia->priv->send_level);
+		gtkmedia->priv->send_level = NULL;
+	}
+
+	if (gtkmedia->priv->recv_level) {
+		gst_object_unref(gtkmedia->priv->recv_level);
+		gtkmedia->priv->recv_level = NULL;
+	}
+
+	G_OBJECT_CLASS(parent_class)->dispose(media);
+}
+
+static void
+pidgin_media_finalize(GObject *media)
+{
+	/* PidginMedia *gtkmedia = PIDGIN_MEDIA(media); */
+	purple_debug_info("gtkmedia", "pidgin_media_finalize\n");
+
+	G_OBJECT_CLASS(parent_class)->finalize(media);
+}
+
+static void
+pidgin_media_emit_message(PidginMedia *gtkmedia, const char *msg)
+{
+	PurpleConversation *conv = purple_find_conversation_with_account(
+			PURPLE_CONV_TYPE_ANY, gtkmedia->priv->screenname,
+			purple_connection_get_account(gtkmedia->priv->pc));
+	if (conv != NULL)
+		purple_conversation_write(conv, NULL, msg,
+				PURPLE_MESSAGE_SYSTEM, time(NULL));
+}
+
+typedef struct
+{
+	PidginMedia *gtkmedia;
+	gchar *session_id;
+	gchar *participant;
+} PidginMediaRealizeData;
+
+static gboolean
+realize_cb_cb(PidginMediaRealizeData *data)
+{
+	PidginMediaPrivate *priv = data->gtkmedia->priv;
+	gulong window_id;
+
+	if (data->participant == NULL)
+		window_id = GDK_WINDOW_XWINDOW(priv->local_video->window);
+	else
+		window_id = GDK_WINDOW_XWINDOW(priv->remote_video->window);
+
+	purple_media_set_output_window(priv->media, data->session_id,
+			data->participant, window_id);
+
+	g_free(data->session_id);
+	g_free(data->participant);
+	g_free(data);
+	return FALSE;
+}
+
+static void
+realize_cb(GtkWidget *widget, PidginMediaRealizeData *data)
+{
+	g_timeout_add(0, (GSourceFunc)realize_cb_cb, data);
+}
+
+static void
+pidgin_media_error_cb(PidginMedia *media, const char *error, PidginMedia *gtkmedia)
+{
+	PurpleConversation *conv = purple_find_conversation_with_account(
+			PURPLE_CONV_TYPE_ANY, gtkmedia->priv->screenname,
+			purple_connection_get_account(gtkmedia->priv->pc));
+	if (conv != NULL)
+		purple_conversation_write(conv, NULL, error,
+				PURPLE_MESSAGE_ERROR, time(NULL));
+	gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar),
+			0, error);
+}
+
+static void
+pidgin_media_accepted_cb(PurpleMedia *media, const gchar *session_id,
+		const gchar *participant, PidginMedia *gtkmedia)
+{
+	pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED);
+	pidgin_media_emit_message(gtkmedia, _("Call in progress."));
+	gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar),
+			0, _("Call in progress."));
+	gtk_widget_show(GTK_WIDGET(gtkmedia));
+}
+
+static gboolean
+plug_delete_event_cb(GtkWidget *widget, gpointer data)
+{
+	return TRUE;
+}
+
+static gboolean
+plug_removed_cb(GtkWidget *widget, gpointer data)
+{
+	return TRUE;
+}
+
+static void
+socket_realize_cb(GtkWidget *widget, gpointer data)
+{
+	gtk_socket_add_id(GTK_SOCKET(widget),
+			gtk_plug_get_id(GTK_PLUG(data)));
+}
+
+static void
+pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia, const gchar *sid)
+{
+	GstElement *pipeline = purple_media_get_pipeline(media);
+	GtkWidget *send_widget = NULL, *recv_widget = NULL;
+	gboolean is_initiator;
+	PurpleMediaSessionType type =
+			purple_media_get_session_type(media, sid);
+
+	if (gtkmedia->priv->recv_widget == NULL
+			&& type & (PURPLE_MEDIA_RECV_VIDEO |
+			PURPLE_MEDIA_RECV_AUDIO)) {
+		recv_widget = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);	
+		gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display),
+				recv_widget, TRUE, TRUE, 0);
+		gtk_widget_show(recv_widget);
+	} else
+		recv_widget = gtkmedia->priv->recv_widget;
+	if (gtkmedia->priv->send_widget == NULL
+			&& type & (PURPLE_MEDIA_SEND_VIDEO |
+			PURPLE_MEDIA_SEND_AUDIO)) {
+		send_widget = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+		gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display),
+				send_widget, TRUE, TRUE, 0);
+		gtk_widget_show(send_widget);
+	} else
+		send_widget = gtkmedia->priv->send_widget;
+
+	if (type & PURPLE_MEDIA_RECV_VIDEO) {
+		PidginMediaRealizeData *data;
+		GtkWidget *aspect;
+		GtkWidget *remote_video;
+		GtkWidget *plug;
+		GtkWidget *socket;
+		GdkColor color = {0, 0, 0, 0};
+
+		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);
+
+		plug = gtk_plug_new(0);
+		g_signal_connect(G_OBJECT(plug), "delete-event",
+				G_CALLBACK(plug_delete_event_cb), plug);
+		gtk_widget_show(plug);
+
+		socket = gtk_socket_new();
+		g_signal_connect(G_OBJECT(socket), "realize",
+				G_CALLBACK(socket_realize_cb), plug);
+		g_signal_connect(G_OBJECT(socket), "plug-removed",
+				G_CALLBACK(plug_removed_cb), NULL);
+		gtk_container_add(GTK_CONTAINER(aspect), socket);
+		gtk_widget_show(socket);
+
+		data = g_new0(PidginMediaRealizeData, 1);
+		data->gtkmedia = gtkmedia;
+		data->session_id = g_strdup(sid);
+		data->participant = g_strdup(gtkmedia->priv->screenname);
+
+		remote_video = gtk_drawing_area_new();
+		gtk_widget_modify_bg(remote_video, GTK_STATE_NORMAL, &color);
+		g_signal_connect(G_OBJECT(remote_video), "realize",
+				G_CALLBACK(realize_cb), data);
+		gtk_container_add(GTK_CONTAINER(plug), remote_video);
+		gtk_widget_set_size_request (GTK_WIDGET(remote_video), 320, 240);
+		gtk_widget_show(remote_video);
+		gtk_widget_show(aspect);
+
+		gtkmedia->priv->remote_video = remote_video;
+	}
+	if (type & PURPLE_MEDIA_SEND_VIDEO) {
+		PidginMediaRealizeData *data;
+		GtkWidget *aspect;
+		GtkWidget *local_video;
+		GtkWidget *plug;
+		GtkWidget *socket;
+		GdkColor color = {0, 0, 0, 0};
+
+		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);
+
+		plug = gtk_plug_new(0);
+		g_signal_connect(G_OBJECT(plug), "delete-event",
+				G_CALLBACK(plug_delete_event_cb), plug);
+		gtk_widget_show(plug);
+
+		socket = gtk_socket_new();
+		g_signal_connect(G_OBJECT(socket), "realize",
+				G_CALLBACK(socket_realize_cb), plug);
+		g_signal_connect(G_OBJECT(socket), "plug-removed",
+				G_CALLBACK(plug_removed_cb), NULL);
+		gtk_container_add(GTK_CONTAINER(aspect), socket);
+		gtk_widget_show(socket);
+
+		data = g_new0(PidginMediaRealizeData, 1);
+		data->gtkmedia = gtkmedia;
+		data->session_id = g_strdup(sid);
+		data->participant = NULL;
+
+		local_video = gtk_drawing_area_new();
+		gtk_widget_modify_bg(local_video, GTK_STATE_NORMAL, &color);
+		g_signal_connect(G_OBJECT(local_video), "realize",
+				G_CALLBACK(realize_cb), data);
+		gtk_container_add(GTK_CONTAINER(plug), local_video);
+		gtk_widget_set_size_request (GTK_WIDGET(local_video), 160, 120);
+
+		gtk_widget_show(local_video);
+		gtk_widget_show(aspect);
+
+		gtkmedia->priv->local_video = local_video;
+	}
+
+	if (type & PURPLE_MEDIA_RECV_AUDIO) {
+		gtkmedia->priv->recv_progress = gtk_progress_bar_new();
+		gtk_widget_set_size_request(gtkmedia->priv->recv_progress, 70, 10);
+		gtk_box_pack_end(GTK_BOX(recv_widget),
+				   gtkmedia->priv->recv_progress, FALSE, FALSE, 0);
+		gtk_widget_show(gtkmedia->priv->recv_progress);
+	}
+	if (type & PURPLE_MEDIA_SEND_AUDIO) {
+		gtkmedia->priv->send_progress = gtk_progress_bar_new();
+		gtk_widget_set_size_request(gtkmedia->priv->send_progress, 70, 10);
+		gtk_box_pack_end(GTK_BOX(send_widget),
+				   gtkmedia->priv->send_progress, FALSE, FALSE, 0);
+		gtk_widget_show(gtkmedia->priv->send_progress);
+
+		gtk_widget_show(gtkmedia->priv->mute);
+	}
+
+
+	if (type & PURPLE_MEDIA_AUDIO) {
+		GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
+		g_signal_connect(G_OBJECT(bus), "message::element",
+				G_CALLBACK(level_message_cb), gtkmedia);
+		gst_object_unref(bus);
+	}
+
+	if (send_widget != NULL)
+		gtkmedia->priv->send_widget = send_widget;
+	if (recv_widget != NULL)
+		gtkmedia->priv->recv_widget = recv_widget;
+
+	g_object_get(G_OBJECT(media), "initiator", &is_initiator, NULL);
+
+	if (is_initiator == FALSE) {
+		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."),
+						  gtkmedia->priv->screenname);
+		} else if (type & PURPLE_MEDIA_AUDIO) {
+			message = g_strdup_printf(_("%s wishes to start an audio session with you."),
+						  gtkmedia->priv->screenname);
+		} else if (type & PURPLE_MEDIA_VIDEO) {
+			message = g_strdup_printf(_("%s wishes to start a video session with you."),
+						  gtkmedia->priv->screenname);
+		}
+		pidgin_media_emit_message(gtkmedia, message);
+		g_free(message);
+	}
+
+	gtk_widget_show(gtkmedia->priv->display);
+}
+
+static void
+pidgin_media_state_changed_cb(PurpleMedia *media,
+		PurpleMediaStateChangedType type,
+		gchar *sid, gchar *name, PidginMedia *gtkmedia)
+{
+	purple_debug_info("gtkmedia", "type: %d sid: %s name: %s\n",
+			type, sid, name);
+	if (sid == NULL && name == NULL) {
+		if (type == PURPLE_MEDIA_STATE_CHANGED_END) {
+			pidgin_media_emit_message(gtkmedia,
+					_("The call has been terminated."));
+			gtk_widget_destroy(GTK_WIDGET(gtkmedia));
+			
+		} else if (type == PURPLE_MEDIA_STATE_CHANGED_REJECTED) {
+			pidgin_media_emit_message(gtkmedia,
+					_("You have rejected the call."));
+		}
+	} else if (type == PURPLE_MEDIA_STATE_CHANGED_NEW &&
+			sid != NULL && name != NULL) {
+		pidgin_media_ready_cb(media, gtkmedia, sid);
+	}
+}
+
+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:
+		{
+			gboolean initiator;
+			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_object_get(G_OBJECT(media->priv->media),
+					"initiator", &initiator, NULL);
+			if (initiator == TRUE)
+				pidgin_media_set_state(media, PIDGIN_MEDIA_WAITING);
+			else
+				pidgin_media_set_state(media, PIDGIN_MEDIA_REQUESTED);
+
+			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), "error",
+				G_CALLBACK(pidgin_media_error_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "accepted",
+				G_CALLBACK(pidgin_media_accepted_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "state-changed",
+				G_CALLBACK(pidgin_media_state_changed_cb), media);
+			break;
+		}
+		case PROP_SCREENNAME:
+			if (media->priv->screenname)
+				g_free(media->priv->screenname);
+			media->priv->screenname = g_value_dup_string(value);
+			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_SCREENNAME:
+			g_value_set_string(value, media->priv->screenname);
+			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, const gchar *screenname)
+{
+	PidginMedia *gtkmedia = g_object_new(pidgin_media_get_type(),
+					     "media", media,
+					     "screenname", screenname, 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;
+	}
+}
+
+static gboolean
+pidgin_media_new_cb(PurpleMediaManager *manager, PurpleMedia *media,
+		PurpleConnection *pc, gchar *screenname, gpointer nul)
+{
+	PidginMedia *gtkmedia = PIDGIN_MEDIA(
+			pidgin_media_new(media, screenname));
+	gboolean initiator;
+	PurpleBuddy *buddy = purple_find_buddy(
+			purple_connection_get_account(pc), screenname);
+	const gchar *alias = buddy ? 
+			purple_buddy_get_contact_alias(buddy) : screenname; 
+	gtkmedia->priv->pc = pc;
+	gtk_window_set_title(GTK_WINDOW(gtkmedia), alias);
+
+	g_object_get(G_OBJECT(media), "initiator", &initiator, NULL);
+	if (initiator == FALSE) {
+		gchar *message = g_strdup_printf("%s wishes to start a "
+				"media session with you\n", alias);
+		purple_request_accept_cancel(media, "Media invitation",
+				message, NULL, 1, (void*)pc, screenname,
+				NULL, media, purple_media_accept,
+				purple_media_reject);
+		g_free(message);
+	} else
+		gtk_widget_show(GTK_WIDGET(gtkmedia));
+
+	return TRUE;
+}
+
+static GstElement *
+create_default_video_src(void)
+{
+	GstElement *ret = NULL;
+	purple_media_video_init_src(&ret);
+	return ret;
+}
+
+static GstElement *
+create_default_video_sink(void)
+{
+	GstElement *ret = NULL;
+	purple_media_video_init_recv(&ret);
+	return ret;
+}
+
+static GstElement *
+create_default_audio_src(void)
+{
+	GstElement *ret = NULL, *level = NULL;
+	purple_media_audio_init_src(&ret, &level);
+	return ret;
+}
+
+static GstElement *
+create_default_audio_sink(void)
+{
+	GstElement *ret = NULL, *level = NULL;
+	purple_media_audio_init_recv(&ret, &level);
+	return ret;
+}
+
+static PurpleMediaElementInfo default_video_src =
+{
+	"pidgindefaultvideosrc",	/* id */
+	PURPLE_MEDIA_ELEMENT_VIDEO	/* type */
+			| PURPLE_MEDIA_ELEMENT_SRC
+			| PURPLE_MEDIA_ELEMENT_ONE_SRC
+			| PURPLE_MEDIA_ELEMENT_UNIQUE,
+	create_default_video_src,	/* create */
+};
+
+static PurpleMediaElementInfo default_video_sink =
+{
+	"pidgindefaultvideosink",	/* id */
+	PURPLE_MEDIA_ELEMENT_VIDEO	/* type */
+			| PURPLE_MEDIA_ELEMENT_SINK
+			| PURPLE_MEDIA_ELEMENT_ONE_SINK,
+	create_default_video_sink,	/* create */
+};
+
+static PurpleMediaElementInfo default_audio_src =
+{
+	"pidgindefaultaudiosrc",	/* id */
+	PURPLE_MEDIA_ELEMENT_AUDIO	/* type */
+			| PURPLE_MEDIA_ELEMENT_SRC
+			| PURPLE_MEDIA_ELEMENT_ONE_SRC
+			| PURPLE_MEDIA_ELEMENT_UNIQUE,
+	create_default_audio_src,	/* create */
+};
+
+static PurpleMediaElementInfo default_audio_sink =
+{
+	"pidgindefaultaudiosink",	/* id */
+	PURPLE_MEDIA_ELEMENT_AUDIO	/* type */
+			| PURPLE_MEDIA_ELEMENT_SINK
+			| PURPLE_MEDIA_ELEMENT_ONE_SINK,
+	create_default_audio_sink,	/* create */
+};
+
+void
+pidgin_medias_init(void)
+{
+	PurpleMediaManager *manager = purple_media_manager_get();
+	g_signal_connect(G_OBJECT(manager), "init-media",
+			 G_CALLBACK(pidgin_media_new_cb), NULL);
+
+	purple_debug_info("gtkmedia", "Registering media element types\n");
+	purple_media_manager_set_active_element(manager, &default_video_src);
+	purple_media_manager_set_active_element(manager, &default_video_sink);
+	purple_media_manager_set_active_element(manager, &default_audio_src);
+	purple_media_manager_set_active_element(manager, &default_audio_sink);
+}
+
+#endif  /* USE_VV */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkmedia.h	Thu Feb 05 00:31:35 2009 +0000
@@ -0,0 +1,71 @@
+/**
+ * @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
+{
+	GtkWindowClass parent_class;
+};
+
+struct _PidginMedia
+{
+	GtkWindow parent;
+	PidginMediaPrivate *priv;
+};
+
+GType pidgin_media_get_type(void);
+
+void pidgin_medias_init(void);
+
+GtkWidget *pidgin_media_new(PurpleMedia *media, const gchar *screenname);
+
+G_END_DECLS
+
+#endif  /* USE_VV */
+
+
+#endif  /* __GTKMEDIA_H_ */
--- a/pidgin/gtkprefs.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkprefs.c	Thu Feb 05 00:31:35 2009 +0000
@@ -28,6 +28,9 @@
 #include "pidgin.h"
 
 #include "debug.h"
+#ifdef USE_VV
+#include "mediamanager.h"
+#endif
 #include "notify.h"
 #include "prefs.h"
 #include "proxy.h"
@@ -134,6 +137,26 @@
 	return pidgin_add_widget_to_vbox(GTK_BOX(page), title, sg, entry, TRUE, NULL);
 }
 
+GtkWidget *
+pidgin_prefs_labeled_password(GtkWidget *page, const gchar *title,
+							 const char *key, GtkSizeGroup *sg)
+{
+	GtkWidget *entry;
+	const gchar *value;
+
+	value = purple_prefs_get_string(key);
+
+	entry = gtk_entry_new();
+	gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+	gtk_entry_set_text(GTK_ENTRY(entry), value);
+	g_signal_connect(G_OBJECT(entry), "changed",
+					 G_CALLBACK(entry_set), (char*)key);
+	gtk_widget_show(entry);
+
+	return pidgin_add_widget_to_vbox(GTK_BOX(page), title, sg, entry, TRUE, NULL);
+}
+
+
 static void
 dropdown_set(GObject *w, const char *key)
 {
@@ -145,12 +168,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 +966,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 +976,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 +1011,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:"),
@@ -1141,6 +1162,28 @@
 	purple_network_set_public_ip(gtk_entry_get_text(entry));
 }
 
+static gboolean network_stun_server_changed_cb(GtkWidget *widget, 
+	GdkEventFocus *event, gpointer data)
+{
+	GtkEntry *entry = GTK_ENTRY(widget);
+	purple_prefs_set_string("/purple/network/stun_server",
+		gtk_entry_get_text(entry));
+	purple_network_set_stun_server(gtk_entry_get_text(entry));
+	
+	return FALSE;
+}
+
+static gboolean network_turn_server_changed_cb(GtkWidget *widget, 
+	GdkEventFocus *event, gpointer data)
+{
+	GtkEntry *entry = GTK_ENTRY(widget);
+	purple_prefs_set_string("/purple/network/turn_server",
+		gtk_entry_get_text(entry));
+	purple_network_set_turn_server(gtk_entry_get_text(entry));
+	
+	return FALSE;
+}
+
 static void
 proxy_changed_cb(const char *name, PurplePrefType type,
 				 gconstpointer value, gpointer data)
@@ -1205,10 +1248,27 @@
 	gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
 
 	vbox = pidgin_make_frame (ret, _("IP Address"));
+	
+	table = gtk_table_new(2, 2, FALSE);
+	gtk_container_set_border_width(GTK_CONTAINER(table), 0);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 5);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 10);
+	gtk_container_add(GTK_CONTAINER(vbox), table);
+
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
-	pidgin_prefs_labeled_entry(vbox,_("ST_UN server:"),
-			"/purple/network/stun_server", sg);
-
+	label = gtk_label_new_with_mnemonic(_("ST_UN server:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_size_group_add_widget(sg, label);
+
+	entry = gtk_entry_new();
+	gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
+	g_signal_connect(G_OBJECT(entry), "focus-out-event",
+					 G_CALLBACK(network_stun_server_changed_cb), NULL);
+	gtk_entry_set_text(GTK_ENTRY(entry), 
+		purple_prefs_get_string("/purple/network/stun_server"));
+			
 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtk_container_add(GTK_CONTAINER(vbox), hbox);
 
@@ -1285,6 +1345,43 @@
 	g_signal_connect(G_OBJECT(ports_checkbox), "clicked",
 					 G_CALLBACK(pidgin_toggle_sensitive), spin_button);
 
+	vbox = pidgin_make_frame(ret, _("Relay Server (TURN)"));
+
+	/* TURN server */
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+	label = gtk_label_new_with_mnemonic(_("_Server:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	
+	entry = gtk_entry_new();
+	gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
+	g_signal_connect(G_OBJECT(entry), "focus-out-event",
+					 G_CALLBACK(network_turn_server_changed_cb), NULL);
+	gtk_entry_set_text(GTK_ENTRY(entry), 
+		purple_prefs_get_string("/purple/network/turn_server"));
+	gtk_misc_set_alignment(GTK_MISC(entry), 0, 0.5);
+	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
+	
+	gtk_size_group_add_widget(GTK_SIZE_GROUP(sg), label);
+	gtk_size_group_add_widget(GTK_SIZE_GROUP(sg), entry);
+	
+	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+	spin_button = pidgin_prefs_labeled_spin_button(hbox, _("_Port:"),
+		"/purple/network/turn_port", 0, 65535, sg);
+	gtk_container_add(GTK_CONTAINER(vbox), hbox);
+	
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+	entry = pidgin_prefs_labeled_entry(hbox, "_User name:", 
+		"/purple/network/turn_username", sg);
+
+	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+	entry = pidgin_prefs_labeled_password(hbox, "_Password:",
+		"/purple/network/turn_password", sg);
+	gtk_container_add(GTK_CONTAINER(vbox), hbox);
+	
+	
 	if (purple_running_gnome()) {
 		vbox = pidgin_make_frame(ret, _("Proxy Server &amp; Browser"));
 		prefs_proxy_frame = gtk_vbox_new(FALSE, 0);
@@ -2044,6 +2141,293 @@
 	return ret;
 }
 
+#ifdef USE_VV
+
+/* get a GList of pairs name / device */
+static GList *
+get_device_items(const gchar *plugin)
+{
+	GList *ret = NULL;
+	GList *devices = purple_media_get_devices(plugin);
+	GstElement *element = gst_element_factory_make(plugin, NULL);
+
+	if (element == NULL)
+		return NULL;
+
+	for(; devices ; devices = g_list_delete_link(devices, devices)) {
+		gchar *name;
+		g_object_set(G_OBJECT(element), "device", devices->data, NULL);
+		g_object_get(G_OBJECT(element), "device-name", &name, NULL);
+		ret = g_list_append(ret, name);
+		ret = g_list_append(ret, devices->data);
+	}
+
+	gst_object_unref(element);
+	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 FALSE;
+}
+
+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 = NULL;
+
+	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);
+
+	if (pipeline == NULL) {
+		purple_debug_error("gtkprefs",
+			"Error starting preview: %s\n", p_err->message);
+		g_error_free(p_err);
+		return;
+	}
+
+	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");
+	GList *video_items = get_device_items(plugin);
+	GList *list;
+
+	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), NULL);
+
+	gtk_container_add(GTK_CONTAINER(hbox), preview_button);
+
+	gtk_widget_show_all(hbox);
+}
+
+static void
+prefs_media_input_volume_changed(GtkRange *range)
+{
+	double val = (double)gtk_range_get_value(GTK_RANGE(range));
+	GList *medias = purple_media_manager_get_media(purple_media_manager_get());
+	purple_prefs_set_int("/purple/media/audio/volume/input", val);
+
+	val /= 10.0;
+	for (; medias; medias = g_list_next(medias)) {
+		PurpleMedia *media = PURPLE_MEDIA(medias->data);
+		purple_media_set_input_volume(media, NULL, val);
+	}
+}
+
+static void
+prefs_media_output_volume_changed(GtkRange *range)
+{
+	double val = (double)gtk_range_get_value(GTK_RANGE(range));
+	GList *medias = purple_media_manager_get_media(purple_media_manager_get());
+	purple_prefs_set_int("/purple/media/audio/volume/output", val);
+
+	val /= 10.0;
+	for (; medias; medias = g_list_next(medias)) {
+		PurpleMedia *media = PURPLE_MEDIA(medias->data);
+		purple_media_set_output_volume(media, NULL, NULL, val);
+	}
+}
+
+static GtkWidget *
+media_page()
+{
+	GtkWidget *ret;
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *dd;
+	GtkWidget *preview_button;
+	GtkWidget *sw;
+	GtkSizeGroup *sg, *sg2;
+	const char *plugin = purple_prefs_get_string("/purple/media/video/plugin");
+	const char *device = purple_prefs_get_string("/purple/media/video/device");
+	GList *video_items = get_device_items(plugin);
+	GList *audio_items = get_device_items("alsasrc");
+
+	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);
+	sg2 = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+	vbox = pidgin_make_frame (ret, _("Video Input"));
+	gtk_size_group_add_widget(sg2, vbox);
+
+	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), NULL);
+
+	gtk_container_add(GTK_CONTAINER(hbox), preview_button);
+	gtk_container_add(GTK_CONTAINER(vbox), hbox);
+
+	vbox = pidgin_make_frame (ret, _("Audio Input"));
+	gtk_size_group_add_widget(sg2, vbox);
+	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);
+
+	/* Input Volume */
+	sw = gtk_hscale_new_with_range(0.0, 100.0, 5.0);
+	gtk_range_set_increments(GTK_RANGE(sw), 5.0, 25.0);
+	gtk_range_set_value(GTK_RANGE(sw),
+			purple_prefs_get_int("/purple/media/audio/volume/input"));
+	g_signal_connect (G_OBJECT (sw), "format-value",
+			  G_CALLBACK (prefs_sound_volume_format),
+			  NULL);
+	g_signal_connect (G_OBJECT (sw), "value-changed",
+			  G_CALLBACK (prefs_media_input_volume_changed),
+			  NULL);
+	pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("Volume:"), sg, sw, TRUE, NULL);
+
+	vbox = pidgin_make_frame (ret, _("Audio Output"));
+	gtk_size_group_add_widget(sg2, vbox);
+
+	/* Output Volume */
+	sw = gtk_hscale_new_with_range(0.0, 100.0, 5.0);
+	gtk_range_set_increments(GTK_RANGE(sw), 5.0, 25.0);
+	gtk_range_set_value(GTK_RANGE(sw),
+			purple_prefs_get_int("/purple/media/audio/volume/output"));
+	g_signal_connect (G_OBJECT (sw), "format-value",
+			  G_CALLBACK (prefs_sound_volume_format),
+			  NULL);
+	g_signal_connect (G_OBJECT (sw), "value-changed",
+			  G_CALLBACK (prefs_media_output_volume_changed),
+			  NULL);
+	pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("Volume:"), sg, sw, TRUE, NULL);
+
+	gtk_widget_show_all(ret);
+
+	return ret;
+}
+
+#endif	/* USE_VV */
 
 static void
 set_idle_away(PurpleSavedStatus *status)
@@ -2169,6 +2553,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 */
@@ -2294,6 +2682,18 @@
 	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", "");
+	purple_prefs_add_none("/purple/media/audio/volume");
+	purple_prefs_add_int("/purple/media/audio/volume/input", 10);
+	purple_prefs_add_int("/purple/media/audio/volume/output", 10);
+#endif /* USE_VV */
+
 	pidgin_prefs_update_old();
 }
 
--- a/pidgin/gtkprefs.h	Wed Feb 04 05:15:49 2009 +0000
+++ b/pidgin/gtkprefs.h	Thu Feb 05 00:31:35 2009 +0000
@@ -29,6 +29,7 @@
 
 #include "prefs.h"
 
+
 /**
  * Initializes all UI-specific preferences.
  */
@@ -81,6 +82,22 @@
 										const char *key, GtkSizeGroup *sg);
 
 /**
+ * Add a new entry representing a password (string) preference
+ * The entry will use a password-style text entry (the text is substituded)
+ *
+ * @param page  The page to which the entry will be added
+ * @param title The text to be displayed as the entry label
+ * @param key   The key of the string pref that will be represented by the entry
+ * @param sg    If not NULL, the size group to which the entry will be added
+ *
+ * @return      An hbox containing both the label and the entry.  Can be used to set
+ *               the widgets to sensitive or insensitive based on the value of a
+ *               checkbox.
+ */
+GtkWidget *pidgin_prefs_labeled_password(GtkWidget *page, const gchar *title,
+										const char *key, GtkSizeGroup *sg);
+
+/**
  * Add a new dropdown representing a preference of the specified type
  *
  * @param page  The page to which the dropdown will be added
--- a/pidgin/pidginstock.c	Wed Feb 04 05:15:49 2009 +0000
+++ b/pidgin/pidginstock.c	Thu Feb 05 00:31:35 2009 +0000
@@ -193,6 +193,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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/pidginstock.h	Thu Feb 05 00:31:35 2009 +0000
@@ -153,6 +153,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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/pixmaps/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -429,6 +429,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 \
@@ -442,7 +443,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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/plugins/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/plugins/cap/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/plugins/gestures/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/plugins/gevolution/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/plugins/musicmessaging/Makefile.am	Thu Feb 05 00:31:35 2009 +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 Feb 04 05:15:49 2009 +0000
+++ b/pidgin/plugins/ticker/Makefile.am	Thu Feb 05 00:31:35 2009 +0000
@@ -24,4 +24,5 @@
 	-I$(top_builddir)/libpurple \
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GTK_CFLAGS)
--- a/po/POTFILES.in	Wed Feb 04 05:15:49 2009 +0000
+++ b/po/POTFILES.in	Thu Feb 05 00:31:35 2009 +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