propagate from branch 'im.pidgin.pidgin' (head 16c4d6e6e192fd51a6188c9c9d1ec48e78caca0a) maiku.vv

Thu, 06 Nov 2008 03:20:05 +0000

author
Michael Ruprecht <maiku@pidgin.im>
date
Thu, 06 Nov 2008 03:20:05 +0000
branch
maiku.vv
changeset 26152
d4b5bdf689a0
parent 26151
218a4f61cf0d (diff)
parent 24587
16c4d6e6e192 (current diff)
child 26153
1f14a5cb82f0

propagate from branch 'im.pidgin.pidgin' (head 16c4d6e6e192fd51a6188c9c9d1ec48e78caca0a)
to branch 'im.pidgin.maiku.vv' (head 218a4f61cf0de438c8c86d93fce7c4e28349ee5a)

configure.ac file | annotate | diff | comparison | revisions
libpurple/plugins/ssl/Makefile.am file | annotate | diff | comparison | revisions
libpurple/protocols/bonjour/bonjour.c file | annotate | diff | comparison | revisions
libpurple/protocols/gg/gg.c file | annotate | diff | comparison | revisions
libpurple/protocols/irc/irc.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/iq.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/Makefile.am file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_conv.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_conv.h file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_find.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_find.h file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_free.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_free.h file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_search.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_search.h file | annotate | diff | comparison | revisions
libpurple/protocols/qq/header_info.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/header_info.h file | annotate | diff | comparison | revisions
libpurple/protocols/qq/qq.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/qq_define.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/qq_define.h file | annotate | diff | comparison | revisions
libpurple/protocols/qq/sys_msg.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/sys_msg.h file | annotate | diff | comparison | revisions
libpurple/prpl.c file | annotate | diff | comparison | revisions
libpurple/server.c file | annotate | diff | comparison | revisions
pidgin/Makefile.am file | annotate | diff | comparison | revisions
pidgin/gtkconv.c file | annotate | diff | comparison | revisions
pidgin/gtkconv.h file | annotate | diff | comparison | revisions
pidgin/gtkdialogs.c file | annotate | diff | comparison | revisions
pidgin/gtkprefs.c file | annotate | diff | comparison | revisions
po/POTFILES.in file | annotate | diff | comparison | revisions
--- a/ChangeLog.API	Mon Nov 03 20:36:38 2008 +0000
+++ b/ChangeLog.API	Thu Nov 06 03:20:05 2008 +0000
@@ -1,5 +1,11 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version ?.?.? (??/??/????):
+	libpurple:
+		Added:
+		* PurpleMedia API
+		* xmlnode_get_parent
+
 version 2.5.0 (08/18/2008):
 	libpurple:
 		Added:
--- a/Doxyfile.in	Mon Nov 03 20:36:38 2008 +0000
+++ b/Doxyfile.in	Thu Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/configure.ac	Thu Nov 06 03:20:05 2008 +0000
@@ -47,7 +47,7 @@
 m4_define([purple_major_version], [2])
 m4_define([purple_minor_version], [5])
 m4_define([purple_micro_version], [3])
-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], [3])
-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,
@@ -2453,6 +2506,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	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -26,6 +26,7 @@
 	finch.c \
 	gntidle.c \
 	gntlog.c \
+	gntmedia.c \
 	gntnotify.c \
 	gntplugin.c \
 	gntpounce.c \
@@ -47,6 +48,7 @@
 	finch.h \
 	gntidle.h \
 	gntlog.h \
+	gntmedia.h \
 	gntnotify.h \
 	gntplugin.h \
 	gntpounce.h \
@@ -69,6 +71,7 @@
 	$(INTLLIBS) \
 	$(GLIB_LIBS) \
 	$(LIBXML_LIBS) \
+	$(FARSIGHT_LIBS) \
 	$(GNT_LIBS) \
 	$(GSTREAMER_LIBS) \
 	./libgnt/libgnt.la \
@@ -88,5 +91,6 @@
 	$(GLIB_CFLAGS) \
 	$(DBUS_CFLAGS) \
 	$(LIBXML_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GSTREAMER_CFLAGS) \
 	$(GNT_CFLAGS)
--- a/finch/gntaccount.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/gntaccount.c	Thu Nov 06 03:20:05 2008 +0000
@@ -1097,3 +1097,4 @@
 	return &ui_ops;
 }
 
+
--- a/finch/gntdebug.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/gntdebug.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include "util.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -36,7 +38,6 @@
 #include "gntdebug.h"
 #include "finch.h"
 #include "notify.h"
-#include "util.h"
 
 #include <stdio.h>
 #include <string.h>
@@ -347,6 +348,13 @@
 #ifdef USE_GSTREAMER
 	REGISTER_G_LOG_HANDLER("GStreamer");
 #endif
+#ifdef USE_VV
+#ifdef USE_FARSIGHT
+	REGISTER_G_LOG_HANDLER("farsight");
+	REGISTER_G_LOG_HANDLER("farsight-transmitter");
+	REGISTER_G_LOG_HANDLER("farsight-rtp");
+#endif
+#endif
 
 	g_set_print_handler(print_stderr);   /* Redirect the debug messages to stderr */
 	if (!purple_debug_is_enabled())
--- a/finch/gntft.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/gntft.c	Thu Nov 06 03:20:05 2008 +0000
@@ -25,6 +25,12 @@
  */
 #include "finch.h"
 
+#include "debug.h"
+#include "notify.h"
+#include "ft.h"
+#include "prpl.h"
+#include "util.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -32,12 +38,6 @@
 #include <gntlabel.h>
 #include <gnttree.h>
 
-#include "debug.h"
-#include "notify.h"
-#include "ft.h"
-#include "prpl.h"
-#include "util.h"
-
 #include "gntft.h"
 #include "prefs.h"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/gntmedia.c	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,428 @@
+/**
+ * @file gntmedia.c GNT Media API
+ * @ingroup finch
+ */
+
+/* finch
+ *
+ * Finch is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "finch.h"
+#include "mediamanager.h"
+
+#include "gntconv.h"
+#include "gntmedia.h"
+
+#include "gnt.h"
+#include "gntbutton.h"
+#include "gntbox.h"
+#include "gntlabel.h"
+
+#include "cmds.h"
+#include "conversation.h"
+#include "debug.h"
+
+/* An incredibly large part of the following is from gtkmedia.c */
+#ifdef USE_VV
+
+#undef hangup
+
+struct _FinchMediaPrivate
+{
+	PurpleMedia *media;
+	GstElement *send_level;
+	GstElement *recv_level;
+
+	GntWidget *accept;
+	GntWidget *reject;
+	GntWidget *hangup;
+	GntWidget *calling;
+
+	PurpleConversation *conv;
+};
+
+#define FINCH_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), FINCH_TYPE_MEDIA, FinchMediaPrivate))
+
+static void finch_media_class_init (FinchMediaClass *klass);
+static void finch_media_init (FinchMedia *media);
+static void finch_media_finalize (GObject *object);
+static void finch_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void finch_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+
+static GntBoxClass *parent_class = NULL;
+
+enum {
+	MESSAGE,
+	LAST_SIGNAL
+};
+static guint finch_media_signals[LAST_SIGNAL] = {0};
+
+enum {
+	PROP_0,
+	PROP_MEDIA,
+	PROP_SEND_LEVEL,
+	PROP_RECV_LEVEL
+};
+
+GType
+finch_media_get_type(void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(FinchMediaClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) finch_media_class_init,
+			NULL,
+			NULL,
+			sizeof(FinchMedia),
+			0,
+			(GInstanceInitFunc) finch_media_init,
+			NULL
+		};
+		type = g_type_register_static(GNT_TYPE_BOX, "FinchMedia", &info, 0);
+	}
+	return type;
+}
+
+
+static void
+finch_media_class_init (FinchMediaClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = finch_media_finalize;
+	gobject_class->set_property = finch_media_set_property;
+	gobject_class->get_property = finch_media_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_MEDIA,
+			g_param_spec_object("media",
+			"PurpleMedia",
+			"The PurpleMedia associated with this media.",
+			PURPLE_TYPE_MEDIA,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+	g_object_class_install_property(gobject_class, PROP_SEND_LEVEL,
+			g_param_spec_object("send-level",
+			"Send level",
+			"The GstElement of this media's send 'level'",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+	g_object_class_install_property(gobject_class, PROP_RECV_LEVEL,
+			g_param_spec_object("recv-level",
+			"Receive level",
+			"The GstElement of this media's recv 'level'",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+
+	finch_media_signals[MESSAGE] = g_signal_new("message", G_TYPE_FROM_CLASS(klass),
+					G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					g_cclosure_marshal_VOID__STRING,
+					G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	g_type_class_add_private(klass, sizeof(FinchMediaPrivate));
+}
+
+
+static void
+finch_media_init (FinchMedia *media)
+{
+	media->priv = FINCH_MEDIA_GET_PRIVATE(media);
+
+	media->priv->calling = gnt_label_new(_("Calling ... "));
+	media->priv->hangup = gnt_button_new(_("Hangup"));
+	media->priv->accept = gnt_button_new(_("Accept"));
+	media->priv->reject = gnt_button_new(_("Reject"));
+
+	gnt_box_set_alignment(GNT_BOX(media), GNT_ALIGN_MID);
+
+	gnt_box_add_widget(GNT_BOX(media), media->priv->accept);
+	gnt_box_add_widget(GNT_BOX(media), media->priv->reject);
+}
+
+static void
+finch_media_finalize (GObject *media)
+{
+	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;
+	GstElement *recvbin, *recvlevel;
+
+	GList *sessions = purple_media_get_session_names(media);
+
+	purple_media_audio_init_src(&sendbin, &sendlevel);
+	purple_media_audio_init_recv(&recvbin, &recvlevel);
+
+	for (; sessions; sessions = sessions->next) {
+		purple_media_set_src(media, sessions->data, sendbin);
+		purple_media_set_sink(media, sessions->data, recvbin);
+	}
+	g_list_free(sessions);
+
+	g_object_set(gntmedia, "send-level", sendlevel,
+		     "recv-level", recvlevel,
+		     NULL);
+}
+
+static void
+finch_media_accept_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	GntWidget *parent;
+	GstElement *sendbin = NULL, *recvbin = 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, &recvbin, NULL, NULL);
+	gst_element_set_state(GST_ELEMENT(sendbin), GST_STATE_PLAYING);
+	gst_element_set_state(GST_ELEMENT(recvbin), 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_hangup_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	finch_media_emit_message(gntmedia, _("You have ended the call."));
+	finch_conversation_set_info_widget(gntmedia->priv->conv, NULL);
+	gnt_widget_destroy(GNT_WIDGET(gntmedia));
+	/* XXX: This shouldn't have to be here to free the FinchMedia widget */
+	g_object_unref(gntmedia);
+}
+
+static void
+finch_media_got_hangup_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	finch_media_emit_message(gntmedia, _("The call has been terminated."));
+	finch_conversation_set_info_widget(gntmedia->priv->conv, NULL);
+	gnt_widget_destroy(GNT_WIDGET(gntmedia));
+	/* XXX: This shouldn't have to be here to free the FinchMedia widget */
+	g_object_unref(gntmedia);
+}
+
+static void
+finch_media_reject_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	finch_media_emit_message(gntmedia, _("You have rejected the call."));
+	finch_conversation_set_info_widget(gntmedia->priv->conv, NULL);
+	gnt_widget_destroy(GNT_WIDGET(gntmedia));
+	/* XXX: This shouldn't have to be here to free the FinchMedia widget */
+	g_object_unref(gntmedia);
+}
+
+static void
+finch_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	FinchMedia *media;
+	g_return_if_fail(FINCH_IS_MEDIA(object));
+
+	media = FINCH_MEDIA(object);
+	switch (prop_id) {
+		case PROP_MEDIA:
+			if (media->priv->media)
+				g_object_unref(media->priv->media);
+			media->priv->media = g_value_get_object(value);
+			g_object_ref(media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->accept), "activate",
+				 G_CALLBACK(purple_media_accept), media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->reject), "activate",
+				 G_CALLBACK(purple_media_reject), media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->hangup), "activate",
+				 G_CALLBACK(purple_media_hangup), media->priv->media);
+
+			g_signal_connect(G_OBJECT(media->priv->media), "accepted",
+				G_CALLBACK(finch_media_accept_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media) ,"ready",
+				G_CALLBACK(finch_media_ready_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "wait",
+				G_CALLBACK(finch_media_wait_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "hangup",
+				G_CALLBACK(finch_media_hangup_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "reject",
+				G_CALLBACK(finch_media_reject_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-hangup",
+				G_CALLBACK(finch_media_got_hangup_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-accept",
+				G_CALLBACK(finch_media_accept_cb), media);
+			break;
+		case PROP_SEND_LEVEL:
+			if (media->priv->send_level)
+				gst_object_unref(media->priv->send_level);
+			media->priv->send_level = g_value_get_object(value);
+			g_object_ref(media->priv->send_level);
+			break;
+		case PROP_RECV_LEVEL:
+			if (media->priv->recv_level)
+				gst_object_unref(media->priv->recv_level);
+			media->priv->recv_level = g_value_get_object(value);
+			g_object_ref(media->priv->recv_level);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+finch_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	FinchMedia *media;
+	g_return_if_fail(FINCH_IS_MEDIA(object));
+
+	media = FINCH_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_MEDIA:
+			g_value_set_object(value, media->priv->media);
+			break;
+		case PROP_SEND_LEVEL:
+			g_value_set_object(value, media->priv->send_level);
+			break;
+		case PROP_RECV_LEVEL:
+			g_value_set_object(value, media->priv->recv_level);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+GntWidget *
+finch_media_new(PurpleMedia *media)
+{
+	return GNT_WIDGET(g_object_new(finch_media_get_type(),
+				"media", media,
+				"vertical", FALSE,
+				"homogeneous", FALSE,
+				NULL));
+}
+
+static void
+gntmedia_message_cb(FinchMedia *gntmedia, const char *msg, PurpleConversation *conv)
+{
+	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
+		purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL));
+	}
+}
+
+static gboolean
+finch_new_media(PurpleMediaManager *manager, PurpleMedia *media, gpointer null)
+{
+	GntWidget *gntmedia;
+	PurpleConversation *conv;
+
+	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
+			purple_connection_get_account(purple_media_get_connection(media)),
+			purple_media_get_screenname(media));
+
+	gntmedia = finch_media_new(media);
+	g_signal_connect(G_OBJECT(gntmedia), "message", G_CALLBACK(gntmedia_message_cb), conv);
+	FINCH_MEDIA(gntmedia)->priv->conv = conv;
+	finch_conversation_set_info_widget(conv, gntmedia);
+	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;
+
+	purple_media_wait(media);
+	return PURPLE_CMD_STATUS_OK;
+}
+
+void finch_media_manager_init(void)
+{
+	PurpleMediaManager *manager = purple_media_manager_get();
+	g_signal_connect(G_OBJECT(manager), "init-media", G_CALLBACK(finch_new_media), NULL);
+	purple_cmd_register("call", "", PURPLE_CMD_P_DEFAULT,
+			PURPLE_CMD_FLAG_IM, NULL,
+			call_cmd_cb, _("call: Make an audio call."), NULL);
+}
+
+void finch_media_manager_uninit(void)
+{
+	PurpleMediaManager *manager = purple_media_manager_get();
+	g_signal_handlers_disconnect_by_func(G_OBJECT(manager),
+			G_CALLBACK(finch_new_media), NULL);
+}
+
+#endif  /* USE_VV */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/gntmedia.h	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,77 @@
+/**
+ * @file gntmedia.h GNT Media API
+ * @ingroup finch
+ */
+
+/* finch
+ *
+ * Finch is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#ifndef GNT_MEDIA_H
+#define GNT_MEDIA_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef USE_VV
+
+#include <glib-object.h>
+#include "gntbox.h"
+
+G_BEGIN_DECLS
+
+#define FINCH_TYPE_MEDIA            (finch_media_get_type())
+#define FINCH_MEDIA(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), FINCH_TYPE_MEDIA, FinchMedia))
+#define FINCH_MEDIA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), FINCH_TYPE_MEDIA, FinchMediaClass))
+#define FINCH_IS_MEDIA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), FINCH_TYPE_MEDIA))
+#define FINCH_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FINCH_TYPE_MEDIA))
+#define FINCH_MEDIA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), FINCH_TYPE_MEDIA, FinchMediaClass))
+
+typedef struct _FinchMedia FinchMedia;
+typedef struct _FinchMediaClass FinchMediaClass;
+typedef struct _FinchMediaPrivate FinchMediaPrivate;
+typedef enum _FinchMediaState FinchMediaState;
+
+struct _FinchMediaClass
+{
+	GntBoxClass parent_class;
+};
+
+struct _FinchMedia
+{
+	GntBox parent;
+	FinchMediaPrivate *priv;
+};
+
+GType finch_media_get_type(void);
+
+GntWidget *finch_media_new(PurpleMedia *media);
+
+void finch_media_manager_init(void);
+
+void finch_media_manager_uninit(void);
+
+G_END_DECLS
+
+#endif /* USE_VV */
+
+#endif /* GNT_MEDIA_H */
+
--- a/finch/gntnotify.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/gntnotify.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include <util.h>
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -33,7 +35,6 @@
 
 #include "finch.h"
 
-#include <util.h>
 
 #include "gntnotify.h"
 #include "debug.h"
--- a/finch/gntplugin.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/gntplugin.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include "notify.h"
+#include "request.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -34,8 +37,6 @@
 #include "finch.h"
 
 #include "debug.h"
-#include "notify.h"
-#include "request.h"
 
 #include "gntplugin.h"
 #include "gntrequest.h"
--- a/finch/gntpounce.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/gntpounce.c	Thu Nov 06 03:20:05 2008 +0000
@@ -24,6 +24,16 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
+#include "internal.h"
+#include "account.h"
+#include "conversation.h"
+#include "debug.h"
+#include "notify.h"
+#include "prpl.h"
+#include "request.h"
+#include "server.h"
+#include "util.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -37,15 +47,6 @@
 
 #include "finch.h"
 
-#include "account.h"
-#include "conversation.h"
-#include "debug.h"
-#include "notify.h"
-#include "prpl.h"
-#include "request.h"
-#include "server.h"
-#include "util.h"
-
 #include "gntpounce.h"
 
 
--- a/finch/gntrequest.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/gntrequest.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include "util.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -37,7 +40,6 @@
 #include "finch.h"
 #include "gntrequest.h"
 #include "debug.h"
-#include "util.h"
 
 typedef struct
 {
--- a/finch/gntstatus.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/gntstatus.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,10 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include <notify.h>
+#include <request.h>
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -35,9 +39,6 @@
 
 #include "finch.h"
 
-#include <notify.h>
-#include <request.h>
-
 #include "gntstatus.h"
 
 static struct
--- a/finch/gntui.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/gntui.c	Thu Nov 06 03:20:05 2008 +0000
@@ -19,9 +19,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include <prefs.h>
 #include "finch.h"
 
-#include "gntui.h"
 
 #include "gntaccount.h"
 #include "gntblist.h"
@@ -31,6 +31,7 @@
 #include "gntdebug.h"
 #include "gntft.h"
 #include "gntlog.h"
+#include "gntmedia.h"
 #include "gntnotify.h"
 #include "gntplugin.h"
 #include "gntpounce.h"
@@ -40,7 +41,7 @@
 #include "gntstatus.h"
 #include "gntsound.h"
 
-#include <prefs.h>
+#include "gntui.h"
 
 void gnt_ui_init()
 {
@@ -91,6 +92,11 @@
 	finch_roomlist_init();
 	purple_roomlist_set_ui_ops(finch_roomlist_get_ui_ops());
 
+#ifdef USE_VV
+	/* Media */
+	finch_media_manager_init();
+#endif
+
 	gnt_register_action(_("Accounts"), finch_accounts_show_all);
 	gnt_register_action(_("Buddy List"), finch_blist_show);
 	gnt_register_action(_("Buddy Pounces"), finch_pounces_manager_show);
@@ -136,6 +142,10 @@
 	finch_roomlist_uninit();
 	purple_roomlist_set_ui_ops(NULL);
 
+#ifdef USE_VV
+	finch_media_manager_uninit();
+#endif
+
 	gnt_quit();
 #endif
 }
--- a/finch/libgnt/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/libgnt/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -89,6 +89,7 @@
 
 AM_CPPFLAGS = \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GNT_CFLAGS) \
 	$(DEBUG_CFLAGS) \
 	$(LIBXML_CFLAGS) \
--- a/finch/libgnt/gntkeys.h	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/libgnt/gntkeys.h	Thu Nov 06 03:20:05 2008 +0000
@@ -165,5 +165,6 @@
 #undef lines
 #undef buttons
 #undef newline
+#undef set_clock
 
 #endif
--- a/finch/libgnt/wms/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/libgnt/wms/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -36,5 +36,6 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(GNT_CFLAGS) \
-	$(PLUGIN_CFLAGS)
+	$(PLUGIN_CFLAGS) \
+	$(FARSIGHT_CFLAGS) 
 
--- a/finch/libgnt/wms/s.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/libgnt/wms/s.c	Thu Nov 06 03:20:05 2008 +0000
@@ -2,6 +2,7 @@
 #include <sys/types.h>
 
 #include "internal.h"
+#include "blist.h"
 
 #include "gnt.h"
 #include "gntbox.h"
@@ -11,7 +12,6 @@
 #include "gntwindow.h"
 #include "gntlabel.h"
 
-#include "blist.h"
 
 #define TYPE_S				(s_get_gtype())
 
--- a/finch/plugins/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/finch/plugins/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -42,6 +42,7 @@
 	-I$(top_srcdir)/finch \
 	-I$(top_srcdir)/finch/libgnt \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(GNT_CFLAGS) \
 	$(PLUGIN_CFLAGS)
--- a/libpurple/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -1,6 +1,7 @@
 EXTRA_DIST = \
 		dbus-analyze-functions.py \
 		dbus-analyze-types.py \
+		marshallers.list \
 		purple-notifications-example \
 		purple-remote \
 		purple-send \
@@ -51,6 +52,9 @@
 	idle.c \
 	imgstore.c \
 	log.c \
+	marshallers.c \
+	media.c \
+	mediamanager.c \
 	mime.c \
 	nat-pmp.c \
 	network.c \
@@ -104,6 +108,9 @@
 	idle.h \
 	imgstore.h \
 	log.h \
+	marshallers.h \
+	media.h \
+	mediamanager.h \
 	mime.h \
 	nat-pmp.h \
 	network.h \
@@ -137,6 +144,15 @@
 
 purple_builtheaders = purple.h version.h
 
+marshallers.h: marshallers.list
+	@echo "Generating marshallers.h"
+	$(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --header > marshallers.h
+
+marshallers.c: marshallers.list
+	@echo "Generating marshallers.c"
+	echo "#include \"marshallers.h\"" > marshallers.c
+	$(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --body >> marshallers.c
+
 if ENABLE_DBUS
 
 CLEANFILES = \
@@ -145,6 +161,8 @@
 	dbus-client-binding.h \
 	dbus-types.c \
 	dbus-types.h \
+	marshallers.c \
+	marshallers.h \
 	purple-client-bindings.c \
 	purple-client-bindings.h \
 	purple.service
@@ -215,6 +233,8 @@
 	dbus-types.c \
 	dbus-types.h \
 	dbus-bindings.c \
+	marshallers.c \
+	marshallers.h \
 	purple-client-bindings.c \
 	purple-client-bindings.h
 
@@ -248,6 +268,9 @@
 	$(LIBXML_LIBS) \
 	$(NETWORKMANAGER_LIBS) \
 	$(INTLLIBS) \
+	$(FARSIGHT_LIBS) \
+	$(GSTREAMER_LIBS) \
+	$(GSTPROPS_LIBS) \
 	-lm
 
 AM_CPPFLAGS = \
@@ -260,6 +283,9 @@
 	$(DEBUG_CFLAGS) \
 	$(DBUS_CFLAGS) \
 	$(LIBXML_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(GSTPROPS_CFLAGS) \
 	$(NETWORKMANAGER_CFLAGS)
 
 # INSTALL_SSL_CERTIFICATES is true when SSL_CERTIFICATES_DIR is empty.
--- a/libpurple/example/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/example/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -8,6 +8,7 @@
 	$(INTLLIBS) \
 	$(GLIB_LIBS) \
 	$(LIBXML_LIBS) \
+	$(FARSIGHT_LIBS) \
 	$(top_builddir)/libpurple/libpurple.la
 
 AM_CPPFLAGS = \
@@ -23,4 +24,5 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(DBUS_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(LIBXML_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/marshallers.list	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,4 @@
+VOID:BOXED,BOXED
+VOID:POINTER,POINTER,OBJECT
+BOOLEAN:OBJECT
+VOID:STRING,STRING
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media.c	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,1382 @@
+/**
+ * @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 "debug.h"
+
+#ifdef USE_VV
+
+#include <gst/interfaces/propertyprobe.h>
+#include <gst/farsight/fs-conference-iface.h>
+
+struct _PurpleMediaSession
+{
+	gchar *id;
+	PurpleMedia *media;
+	GstElement *src;
+	GstElement *sink;
+	FsSession *session;
+	/* FsStream table. Mapped by participant's name */
+	GHashTable *streams;
+	PurpleMediaSessionType type;
+	/* GList of FsCandidates table. Mapped by participant's name */
+	GHashTable *local_candidates;
+
+	/*
+	 * These will need to be per stream when sessions with multiple
+	 * streams are supported.
+	 */
+	FsCandidate *local_candidate;
+	FsCandidate *remote_candidate;
+};
+
+struct _PurpleMediaPrivate
+{
+	FsConference *conference;
+
+	char *name;
+	PurpleConnection *connection;
+
+	GHashTable *sessions;	/* PurpleMediaSession table */
+	GHashTable *participants; /* FsParticipant table */
+
+	GstElement *pipeline;
+};
+
+#define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate))
+
+static void purple_media_class_init (PurpleMediaClass *klass);
+static void purple_media_init (PurpleMedia *media);
+static void purple_media_finalize (GObject *object);
+static void purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+
+static 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 {
+	READY,
+	WAIT,
+	ACCEPTED,
+	HANGUP,
+	REJECT,
+	GOT_REQUEST,
+	GOT_HANGUP,
+	GOT_ACCEPT,
+	NEW_CANDIDATE,
+	CANDIDATES_PREPARED,
+	CANDIDATE_PAIR,
+	CODECS_READY,
+	LAST_SIGNAL
+};
+static guint purple_media_signals[LAST_SIGNAL] = {0};
+
+enum {
+	PROP_0,
+	PROP_FS_CONFERENCE,
+	PROP_NAME,
+	PROP_CONNECTION,
+};
+
+GType
+purple_media_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PurpleMediaClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) purple_media_class_init,
+			NULL,
+			NULL,
+			sizeof(PurpleMedia),
+			0,
+			(GInstanceInitFunc) purple_media_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "PurpleMedia", &info, 0);
+	}
+	return type;
+}
+
+static void
+purple_media_class_init (PurpleMediaClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+	
+	gobject_class->finalize = purple_media_finalize;
+	gobject_class->set_property = purple_media_set_property;
+	gobject_class->get_property = purple_media_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_FS_CONFERENCE,
+			g_param_spec_object("farsight-conference",
+			"Farsight conference",
+			"The FsConference associated with this media.",
+			FS_TYPE_CONFERENCE,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_NAME,
+			g_param_spec_string("screenname",
+			"Screenname",
+			"The screenname of the remote user",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_CONNECTION,
+			g_param_spec_pointer("connection",
+			"Connection",
+			"The PurpleConnection associated with this session",
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	purple_media_signals[READY] = g_signal_new("ready", G_TYPE_FROM_CLASS(klass),
+				 	 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[WAIT] = g_signal_new("wait", G_TYPE_FROM_CLASS(klass),
+				 	 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[ACCEPTED] = g_signal_new("accepted", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[HANGUP] = g_signal_new("hangup", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[REJECT] = g_signal_new("reject", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[GOT_REQUEST] = g_signal_new("got-request", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[GOT_HANGUP] = g_signal_new("got-hangup", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[GOT_ACCEPT] = g_signal_new("got-accept", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[NEW_CANDIDATE] = g_signal_new("new-candidate", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__POINTER_POINTER_OBJECT,
+					 G_TYPE_NONE, 3, G_TYPE_POINTER,
+					 G_TYPE_POINTER, FS_TYPE_CANDIDATE);
+	purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__STRING_STRING,
+					 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+	purple_media_signals[CANDIDATE_PAIR] = g_signal_new("candidate-pair", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__BOXED_BOXED,
+					 G_TYPE_NONE, 2, FS_TYPE_CANDIDATE, FS_TYPE_CANDIDATE);
+	purple_media_signals[CODECS_READY] = g_signal_new("codecs-ready", 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(PurpleMediaPrivate));
+}
+
+
+static void
+purple_media_init (PurpleMedia *media)
+{
+	media->priv = PURPLE_MEDIA_GET_PRIVATE(media);
+	memset(media->priv, 0, sizeof(media->priv));
+}
+
+static void
+purple_media_finalize (GObject *media)
+{
+	PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media);
+	purple_debug_info("media","purple_media_finalize\n");
+
+	purple_media_manager_remove_media(purple_media_manager_get(),
+			PURPLE_MEDIA(media));
+
+	g_free(priv->name);
+
+	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;
+			g_free(session->id);
+
+			if (session->streams) {
+				GList *streams = g_hash_table_get_values(session->streams);
+				for (; streams; streams = g_list_delete_link(streams, streams))
+					g_object_unref(streams->data);
+				g_hash_table_destroy(session->streams);
+			}
+
+			if (session->local_candidates) {
+				GList *candidates = g_hash_table_get_values(session->local_candidates);
+				for (; candidates; candidates =
+						g_list_delete_link(candidates, candidates))
+					fs_candidate_list_destroy(candidates->data);
+				g_hash_table_destroy(session->local_candidates);
+			}
+
+			if (session->local_candidate)
+				fs_candidate_destroy(session->local_candidate);
+			if (session->remote_candidate)
+				fs_candidate_destroy(session->remote_candidate);
+
+			g_free(session);
+		}
+		g_hash_table_destroy(priv->sessions);
+	}
+
+	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_hash_table_destroy(priv->participants);
+	}
+
+	if (priv->pipeline) {
+		GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(priv->pipeline));
+		gst_bus_remove_signal_watch(bus);
+		gst_object_unref(bus);
+		gst_element_set_state(priv->pipeline, GST_STATE_NULL);
+		gst_object_unref(priv->pipeline);
+	}
+
+	gst_object_unref(priv->conference);
+
+	parent_class->finalize(media);
+}
+
+static void
+purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	PurpleMedia *media;
+	g_return_if_fail(PURPLE_IS_MEDIA(object));
+
+	media = PURPLE_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_FS_CONFERENCE:
+			if (media->priv->conference)
+				g_object_unref(media->priv->conference);
+			media->priv->conference = g_value_get_object(value);
+			g_object_ref(media->priv->conference);
+			break;
+		case PROP_NAME:
+			g_free(media->priv->name);
+			media->priv->name = g_value_dup_string(value);
+			break;
+		case PROP_CONNECTION:
+			media->priv->connection = g_value_get_pointer(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	PurpleMedia *media;
+	g_return_if_fail(PURPLE_IS_MEDIA(object));
+	
+	media = PURPLE_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_FS_CONFERENCE:
+			g_value_set_object(value, media->priv->conference);
+			break;
+		case PROP_NAME:
+			g_value_set_string(value, media->priv->name);
+			break;
+		case PROP_CONNECTION:
+			g_value_set_pointer(value, media->priv->connection);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+
+}
+
+FsMediaType
+purple_media_to_fs_media_type(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;
+}
+
+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;
+}
+
+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;
+}
+
+PurpleMediaSessionType
+purple_media_get_overall_type(PurpleMedia *media)
+{
+	GList *values = g_hash_table_get_values(media->priv->sessions);
+	PurpleMediaSessionType type = PURPLE_MEDIA_NONE;
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		PurpleMediaSession *session = values->data;
+		type |= session->type;
+	}
+
+	return type;
+}
+
+static PurpleMediaSession*
+purple_media_get_session(PurpleMedia *media, const gchar *sess_id)
+{
+	return (PurpleMediaSession*) (media->priv->sessions) ?
+			g_hash_table_lookup(media->priv->sessions, sess_id) : NULL;
+}
+
+static FsParticipant*
+purple_media_get_participant(PurpleMedia *media, const gchar *name)
+{
+	return (FsParticipant*) (media->priv->participants) ?
+			g_hash_table_lookup(media->priv->participants, name) : NULL;
+}
+
+static FsStream*
+purple_media_session_get_stream(PurpleMediaSession *session, const gchar *name)
+{
+	return (FsStream*) (session->streams) ?
+			g_hash_table_lookup(session->streams, name) : NULL;
+}
+
+static GList*
+purple_media_session_get_local_candidates(PurpleMediaSession *session, const gchar *name)
+{
+	return (GList*) (session->local_candidates) ?
+			g_hash_table_lookup(session->local_candidates, name) : NULL;
+}
+
+static void
+purple_media_add_session(PurpleMedia *media, PurpleMediaSession *session)
+{
+	if (!media->priv->sessions) {
+		purple_debug_info("media", "Creating hash table for sessions\n");
+		media->priv->sessions = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+	g_hash_table_insert(media->priv->sessions, g_strdup(session->id), session);
+}
+
+static gboolean
+purple_media_remove_session(PurpleMedia *media, PurpleMediaSession *session)
+{
+	return g_hash_table_remove(media->priv->sessions, session->id);
+}
+
+static FsParticipant *
+purple_media_add_participant(PurpleMedia *media, const gchar *name)
+{
+	FsParticipant *participant = purple_media_get_participant(media, name);
+	GError *err = NULL;
+
+	if (participant)
+		return participant;
+
+	participant = fs_conference_new_participant(media->priv->conference,
+						    (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 void
+purple_media_insert_stream(PurpleMediaSession *session, const gchar *name, FsStream *stream)
+{
+	if (!session->streams) {
+		purple_debug_info("media", "Creating hash table for streams\n");
+		session->streams = g_hash_table_new_full(g_str_hash,
+				g_str_equal, g_free, NULL);
+	}
+
+	g_hash_table_insert(session->streams, g_strdup(name), stream);
+}
+
+static void
+purple_media_insert_local_candidate(PurpleMediaSession *session, const gchar *name,
+				     FsCandidate *candidate)
+{
+	GList *candidates = purple_media_session_get_local_candidates(session, name);
+
+	candidates = g_list_append(candidates, candidate);
+
+	if (!session->local_candidates) {
+		purple_debug_info("media", "Creating hash table for local candidates\n");
+		session->local_candidates = g_hash_table_new_full(g_str_hash,
+				g_str_equal, g_free, NULL);
+	}
+
+	g_hash_table_insert(session->local_candidates, g_strdup(name), candidates);
+}
+
+GList *
+purple_media_get_session_names(PurpleMedia *media)
+{
+	return g_hash_table_get_keys(media->priv->sessions);
+}
+
+void 
+purple_media_get_elements(PurpleMedia *media, GstElement **audio_src, GstElement **audio_sink,
+                                                  GstElement **video_src, GstElement **video_sink)
+{
+	GList *values = g_hash_table_get_values(media->priv->sessions);
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		PurpleMediaSession *session = (PurpleMediaSession*)values->data;
+
+		if (session->type & PURPLE_MEDIA_SEND_AUDIO && audio_src)
+			*audio_src = session->src;
+		if (session->type & PURPLE_MEDIA_RECV_AUDIO && audio_sink)
+			*audio_sink = session->sink;
+		if (session->type & PURPLE_MEDIA_SEND_VIDEO && video_src)
+			*video_src = session->src;
+		if (session->type & PURPLE_MEDIA_RECV_VIDEO && video_sink)
+			*video_sink = session->sink;
+	}
+}
+
+void 
+purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	GstPad *sinkpad;
+	GstPad *srcpad;
+	
+	if (session->src)
+		gst_object_unref(session->src);
+	session->src = src;
+	gst_bin_add(GST_BIN(purple_media_get_pipeline(media)),
+		    session->src);
+
+	g_object_get(session->session, "sink-pad", &sinkpad, NULL);
+	srcpad = gst_element_get_static_pad(src, "ghostsrc");
+	purple_debug_info("media", "connecting pad: %s\n", 
+			  gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
+			  ? "success" : "failure");
+}
+
+void 
+purple_media_set_sink(PurpleMedia *media, const gchar *sess_id, GstElement *sink)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	if (session->sink)
+		gst_object_unref(session->sink);
+	session->sink = sink;
+	gst_bin_add(GST_BIN(purple_media_get_pipeline(media)),
+		    session->sink);
+}
+
+GstElement *
+purple_media_get_src(PurpleMedia *media, const gchar *sess_id)
+{
+	return purple_media_get_session(media, sess_id)->src;
+}
+
+GstElement *
+purple_media_get_sink(PurpleMedia *media, const gchar *sess_id)
+{
+	return purple_media_get_session(media, sess_id)->sink;
+}
+
+static PurpleMediaSession *
+purple_media_session_from_fs_stream(PurpleMedia *media, FsStream *stream)
+{
+	FsSession *fssession;
+	GList *values;
+
+	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;
+}
+
+static gboolean
+media_bus_call(GstBus *bus, GstMessage *msg, gpointer media)
+{
+	switch(GST_MESSAGE_TYPE(msg)) {
+		case GST_MESSAGE_EOS:
+			purple_debug_info("media", "End of Stream\n");
+			break;
+		case GST_MESSAGE_ERROR: {
+			gchar *debug = NULL;
+			GError *err = NULL;
+
+			gst_message_parse_error(msg, &err, &debug);
+
+			purple_debug_error("media", "gst pipeline error: %s\n", err->message);
+			g_error_free(err);
+
+			if (debug) {
+				purple_debug_error("media", "Debug details: %s\n", debug);
+				g_free (debug);
+			}
+			break;
+		}
+		case GST_MESSAGE_ELEMENT: {
+			if (gst_structure_has_name(msg->structure, "farsight-error")) {
+				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) {
+						g_signal_emit(session->media,
+								purple_media_signals[CODECS_READY],
+								0, session->id);
+						g_list_free(sessions);
+						break;
+					}
+				}
+			}
+			break;
+		}
+		default:
+			break;
+	}
+
+	return TRUE;
+}
+
+GstElement *
+purple_media_get_pipeline(PurpleMedia *media)
+{
+	if (!media->priv->pipeline) {
+		GstBus *bus;
+		media->priv->pipeline = gst_pipeline_new(media->priv->name);
+		bus = gst_pipeline_get_bus(GST_PIPELINE(media->priv->pipeline));
+		gst_bus_add_signal_watch(GST_BUS(bus));
+		g_signal_connect(G_OBJECT(bus), "message",
+				G_CALLBACK(media_bus_call), media);
+		gst_object_unref(bus);
+
+		gst_bin_add(GST_BIN(media->priv->pipeline), GST_ELEMENT(media->priv->conference));
+	}
+
+	return media->priv->pipeline;
+}
+
+PurpleConnection *
+purple_media_get_connection(PurpleMedia *media)
+{
+	PurpleConnection *gc;
+	g_object_get(G_OBJECT(media), "connection", &gc, NULL);
+	return gc;
+}
+
+char *
+purple_media_get_screenname(PurpleMedia *media)
+{
+	char *ret;
+	g_object_get(G_OBJECT(media), "screenname", &ret, NULL);
+	return ret;
+}
+
+void
+purple_media_ready(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[READY], 0);
+}
+
+void
+purple_media_wait(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[WAIT], 0);
+}
+
+void
+purple_media_accept(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[ACCEPTED], 0);
+}
+
+void
+purple_media_hangup(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[HANGUP], 0);
+}
+
+void
+purple_media_reject(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[REJECT], 0);
+}
+
+void
+purple_media_got_request(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[GOT_REQUEST], 0);
+}
+
+void
+purple_media_got_hangup(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[GOT_HANGUP], 0);
+}
+
+void
+purple_media_got_accept(PurpleMedia *media)
+{
+    g_signal_emit(media, purple_media_signals[GOT_ACCEPT], 0);
+}
+
+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;
+
+	purple_debug_info("media", "purple_media_audio_init_src\n");
+
+	*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, *local_sink;
+	GstPad *pad;
+	GstPad *ghost;
+	const gchar *video_plugin = purple_prefs_get_string(
+			"/purple/media/video/plugin");
+	const gchar *video_device = purple_prefs_get_string(
+			"/purple/media/video/device");
+
+	purple_debug_info("media", "purple_media_video_init_src\n");
+
+	*sendbin = gst_bin_new("purplesendvideobin");
+	src = gst_element_factory_make(video_plugin, "purplevideosource");
+	gst_bin_add(GST_BIN(*sendbin), src);
+
+	tee = gst_element_factory_make("tee", "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);
+
+	queue = gst_element_factory_make("queue", "purplelocalvideoqueue");
+	gst_bin_add(GST_BIN(*sendbin), queue);
+	gst_element_link(tee, queue);
+
+	local_sink = gst_element_factory_make("autovideosink", "purplelocalvideosink");
+	gst_bin_add(GST_BIN(*sendbin), local_sink);
+	gst_element_link(queue, local_sink);
+
+	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;
+
+	purple_debug_info("media", "purple_media_audio_init_recv\n");
+
+	*recvbin = gst_bin_new("pidginrecvaudiobin");
+	sink = gst_element_factory_make("alsasink", "asink");
+	g_object_set(G_OBJECT(sink), "sync", FALSE, NULL);
+	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);
+
+	purple_debug_info("media", "purple_media_audio_init_recv end\n");
+}
+
+void
+purple_media_video_init_recv(GstElement **recvbin)
+{
+	GstElement *sink;
+	GstPad *pad, *ghost;
+
+	purple_debug_info("media", "purple_media_video_init_recv\n");
+
+	*recvbin = gst_bin_new("pidginrecvvideobin");
+	sink = gst_element_factory_make("autovideosink", "purplevideosink");
+	gst_bin_add(GST_BIN(*recvbin), sink);
+	pad = gst_element_get_pad(sink, "sink");
+	ghost = gst_ghost_pad_new("ghostsink", pad);
+	gst_element_add_pad(*recvbin, ghost);
+
+	purple_debug_info("media", "purple_media_video_init_recv end\n");
+}
+
+static void
+purple_media_new_local_candidate_cb(FsStream *stream,
+				    FsCandidate *local_candidate,
+				    PurpleMediaSession *session)
+{
+	gchar *name;
+	FsParticipant *participant;
+	FsCandidate *candidate;
+	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 = fs_candidate_copy(local_candidate);
+	g_signal_emit(session->media, purple_media_signals[NEW_CANDIDATE],
+		      0, session->id, name, candidate);
+	fs_candidate_destroy(candidate);
+
+	g_free(name);
+}
+
+static void
+purple_media_candidates_prepared_cb(FsStream *stream, PurpleMediaSession *session)
+{
+	gchar *name;
+	FsParticipant *participant;
+	g_object_get(stream, "participant", &participant, NULL);
+	g_object_get(participant, "cname", &name, NULL);
+	g_object_unref(participant);
+	g_signal_emit(session->media, purple_media_signals[CANDIDATES_PREPARED],
+			0, session->id, 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 *stream,
+					   FsCandidate *native_candidate,
+					   FsCandidate *remote_candidate,
+					   PurpleMediaSession *session)
+{
+	FsCandidate *local = fs_candidate_copy(native_candidate);
+	FsCandidate *remote = fs_candidate_copy(remote_candidate);
+
+	session->local_candidate = fs_candidate_copy(native_candidate);
+	session->remote_candidate = fs_candidate_copy(remote_candidate);
+
+	purple_debug_info("media", "candidate pair established\n");
+	g_signal_emit(session->media, purple_media_signals[CANDIDATE_PAIR], 0,
+		      local, remote);
+
+	fs_candidate_destroy(local);
+	fs_candidate_destroy(remote);
+}
+
+static void
+purple_media_src_pad_added_cb(FsStream *stream, GstPad *srcpad,
+			      FsCodec *codec, PurpleMediaSession *session)
+{
+	PurpleMediaSessionType type = purple_media_from_fs(codec->media_type, FS_DIRECTION_RECV);
+	GstPad *sinkpad = NULL;
+	session->sink = purple_media_manager_get_element(purple_media_manager_get(), type);
+
+	gst_bin_add(GST_BIN(purple_media_get_pipeline(session->media)),
+		    session->sink);
+	sinkpad = gst_element_get_static_pad(session->sink, "ghostsink");
+	purple_debug_info("media", "connecting new src pad: %s\n", 
+			  gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK ? "success" : "failure");
+	gst_element_set_state(session->sink, GST_STATE_PLAYING);
+}
+
+static gchar *
+purple_media_get_stun_pref_ip()
+{
+	const gchar *stun_pref =
+			purple_prefs_get_string("/purple/network/stun_server");
+	struct hostent *host;
+
+	if ((host = gethostbyname(stun_pref)) && host->h_addr) {
+		gchar *stun_ip = g_strdup_printf("%hhu.%hhu.%hhu.%hhu",
+				host->h_addr[0], host->h_addr[1],
+				host->h_addr[2], host->h_addr[3]);
+		purple_debug_info("media", "IP address for %s found: %s\n",
+				stun_pref, stun_ip);
+		return stun_ip;
+	} else {
+		purple_debug_info("media", "Unable to resolve %s IP address\n",
+				stun_pref);
+		return 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 = purple_media_get_session(media, sess_id);
+	FsParticipant *participant = NULL;
+	FsStream *stream = NULL;
+	FsStreamDirection *direction = NULL;
+
+	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_debug_error("media", "Error creating session: %s\n", err->message);
+			g_error_free(err);
+			purple_conv_present_error(who,
+						  purple_connection_get_account(purple_media_get_connection(media)),
+						  _("Error creating session."));
+			g_free(session);
+			return FALSE;
+		}
+
+	/*
+	 * The MPV codec didn't work for me.
+	 * MPV may not work yet as of Farsight2 0.0.3
+	 */
+#if 0
+		codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_DISABLE,
+				"MPV", FS_MEDIA_TYPE_VIDEO, 90000));
+#endif
+
+	/* 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);
+
+	/*
+	 * Temporary fix to remove a 5-7 second delay before
+	 * receiving the src-pad-added signal.
+	 * Only works for one-to-one sessions.
+	 * Specific to FsRtpSession.
+	 */
+		g_object_set(G_OBJECT(session->session), "no-rtcp-timeout", 0, NULL);
+
+
+		fs_codec_list_destroy(codec_conf);
+
+		session->id = g_strdup(sess_id);
+		session->media = media;
+		session->type = purple_media_from_fs(type, type_direction);
+
+		purple_media_add_session(media, session);
+	}
+
+	if (!(participant = purple_media_add_participant(media, who))) {
+		purple_media_remove_session(media, session);
+		g_free(session);
+		return FALSE;
+	}
+
+	stream = purple_media_session_get_stream(session, who);
+
+	if (!stream) {
+		GError *err = NULL;
+		gchar *stun_ip = NULL;
+
+		if (!strcmp(transmitter, "rawudp") &&
+				(stun_ip = purple_media_get_stun_pref_ip())) {
+			GParameter *param = g_new0(GParameter, num_params+2);
+			memcpy(param, params, sizeof(GParameter) * num_params);
+
+			param[num_params].name = "stun-ip";
+			g_value_init(&param[num_params].value, G_TYPE_STRING);
+			g_value_take_string(&param[num_params].value, stun_ip);
+
+			param[num_params+1].name = "stun-timeout";
+			g_value_init(&param[num_params+1].value, G_TYPE_UINT);
+			g_value_set_uint(&param[num_params+1].value, 5);
+
+			stream = fs_session_new_stream(session->session,
+					participant, type_direction,
+					transmitter, num_params+2, param, &err);
+			g_free(param);
+			g_free(stun_ip);
+		} else {
+			stream = fs_session_new_stream(session->session,
+					participant, type_direction,
+					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;
+		}
+
+		purple_media_insert_stream(session, who, stream);
+
+		/* callback for source pad added (new stream source ready) */
+		g_signal_connect(G_OBJECT(stream),
+				 "src-pad-added", G_CALLBACK(purple_media_src_pad_added_cb), session);
+
+	} else if (*direction != type_direction) {	
+		/* change direction */
+		g_object_set(stream, "direction", type_direction, NULL);
+	}
+
+	return TRUE;
+}
+
+gboolean
+purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who,
+			PurpleMediaSessionType type,
+			const gchar *transmitter,
+			guint num_params, GParameter *params)
+{
+	FsStreamDirection type_direction;
+
+	if (type & PURPLE_MEDIA_AUDIO) {
+		type_direction = purple_media_to_fs_stream_direction(type & PURPLE_MEDIA_AUDIO);
+
+		if (!purple_media_add_stream_internal(media, sess_id, who,
+						      FS_MEDIA_TYPE_AUDIO, type_direction,
+						      transmitter, 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)
+{
+	
+}
+
+PurpleMediaSessionType
+purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id)
+{
+	PurpleMediaSession *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_local_codecs(PurpleMedia *media, const gchar *sess_id)
+{
+	GList *codecs;
+	g_object_get(G_OBJECT(purple_media_get_session(media, sess_id)->session),
+		     "codecs", &codecs, NULL);
+	return codecs;
+}
+
+GList *
+purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	return fs_candidate_list_copy(
+			purple_media_session_get_local_candidates(session, name));
+}
+/* XXX: Should wait until codecs-ready is TRUE before using this function */
+GList *
+purple_media_get_negotiated_codecs(PurpleMedia *media, const gchar *sess_id)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	GList *codec_intersection;
+	g_object_get(session->session, "codecs", &codec_intersection, NULL);
+	return codec_intersection;
+}
+
+void
+purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id,
+				   const gchar *name, GList *remote_candidates)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	FsStream *stream = purple_media_session_get_stream(session, name);
+	GError *err = NULL;
+
+	fs_stream_set_remote_candidates(stream, remote_candidates, &err);
+
+	if (err) {
+		purple_debug_error("media", "Error adding remote candidates: %s\n",
+				   err->message);
+		g_error_free(err);
+	}
+}
+
+FsCandidate *
+purple_media_get_local_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	return session->local_candidate;
+}
+
+FsCandidate *
+purple_media_get_remote_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	return session->remote_candidate;
+}
+
+gboolean
+purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id, const gchar *name, GList *codecs)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	FsStream *stream = purple_media_session_get_stream(session, name);
+	GError *err = NULL;
+
+	fs_stream_set_remote_codecs(stream, codecs, &err);
+
+	if (err) {
+		purple_debug_error("media", "Error setting remote codecs: %s\n",
+				   err->message);
+		g_error_free(err);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+gboolean
+purple_media_candidates_prepared(PurpleMedia *media, const gchar *name)
+{
+	GList *sessions = purple_media_get_session_names(media);
+
+	for (; sessions; sessions = sessions->next) {
+		const gchar *session = sessions->data;
+		if (!purple_media_get_local_candidate(media, session, name) ||
+				!purple_media_get_remote_candidate(media, session, name))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, FsCodec *codec)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	GError *err = NULL;
+
+	fs_session_set_send_codec(session->session, codec, &err);
+
+	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 = purple_media_get_session(media, sess_id);
+	gboolean ret;
+	g_object_get(session->session, "codecs-ready", &ret, NULL);
+	return ret;
+}
+
+void purple_media_mute(PurpleMedia *media, gboolean active)
+{
+	GList *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);
+		}
+	}
+}
+
+#endif  /* USE_VV */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media.h	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,503 @@
+/**
+ * @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_
+
+#include "internal.h"
+
+#ifdef USE_VV
+
+#include <gst/gst.h>
+#include <gst/farsight/fs-stream.h>
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_MEDIA            (purple_media_get_type())
+#define PURPLE_MEDIA(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA, PurpleMedia))
+#define PURPLE_MEDIA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA, PurpleMediaClass))
+#define PURPLE_IS_MEDIA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA))
+#define PURPLE_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA))
+#define PURPLE_MEDIA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA, PurpleMediaClass))
+
+/** @copydoc _PurpleMedia */
+typedef struct _PurpleMedia PurpleMedia;
+/** @copydoc _PurpleMediaClass */
+typedef struct _PurpleMediaClass PurpleMediaClass;
+/** @copydoc _PurpleMediaPrivate */
+typedef struct _PurpleMediaPrivate PurpleMediaPrivate;
+/** @copydoc _PurpleMediaSession */
+typedef struct _PurpleMediaSession PurpleMediaSession;
+
+#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;
+
+#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. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the media class's GType
+ *
+ * @return The media class's GType.
+ */
+GType purple_media_get_type(void);
+
+/**************************************************************************/
+/** @name Media Utility Functions                                         */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Converts a PurpleMediaSessionType to an FsMediaType.
+ *
+ * @param type The type to derive FsMediaType from
+ *
+ * @return The FsMediaType derived from type
+ */
+FsMediaType purple_media_to_fs_media_type(PurpleMediaSessionType type);
+
+/**
+ * Converts a PurpleMediaSessionType to an FsStreamDirection.
+ *
+ * @param type The type to derive FsMediaType from
+ *
+ * @return The FsMediaDirection derived from type
+ */
+FsStreamDirection purple_media_to_fs_stream_direction(PurpleMediaSessionType type);
+
+/**
+ * Converts an FsMediaType and FsStreamDirection into a PurpleMediaSessionType.
+ *
+ * @param type The type used to construct PurpleMediaSessionType
+ * @param direction The direction used to construct PurpleMediaSessionType
+ *
+ * @return The PurpleMediaSessionType constructed
+ */
+PurpleMediaSessionType purple_media_from_fs(FsMediaType type, FsStreamDirection direction);
+
+/*@}*/
+
+/**
+ * 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 session.
+ *
+ * @param media The media object the session is in.
+ * @param sess_id The session id of the session to set the sink on.
+ * @param sink The source to set the session sink to.
+ */
+void purple_media_set_sink(PurpleMedia *media, const gchar *sess_id, 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 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 sink retrieved.
+ */
+GstElement *purple_media_get_sink(PurpleMedia *media, const gchar *sess_id);
+
+/**
+ * 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);
+
+/**
+ * Gets the connection the media session is associated with.
+ *
+ * @param media The media object to retrieve the connection from.
+ *
+ * @return The retreived connection.
+ */
+PurpleConnection *purple_media_get_connection(PurpleMedia *media);
+
+/**
+ * Gets the screenname of the remote user.
+ *
+ * @param media The media object to retrieve the remote user from.
+ *
+ * @return The retrieved screenname.
+ */
+char *purple_media_get_screenname(PurpleMedia *media);
+
+/**
+ * Set the media session to the ready state.
+ *
+ * @param media The media object to set the state on.
+ */
+void purple_media_ready(PurpleMedia *media);
+
+/**
+ * Set the media session to the wait state.
+ *
+ * @param media The media object to set the state on.
+ */
+void purple_media_wait(PurpleMedia *media);
+
+/**
+ * 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);
+
+/**
+ * Set the media session to the got_request state.
+ *
+ * @param media The media object to set the state on.
+ */
+void purple_media_got_request(PurpleMedia *media);
+
+/**
+ * Set the media session to the got_hangup state.
+ *
+ * @param media The media object to set the state on.
+ */
+void purple_media_got_hangup(PurpleMedia *media);
+
+/**
+ * Set the media session to the got_accept state.
+ *
+ * @param media The media object to set the state on.
+ */
+void purple_media_got_accept(PurpleMedia *media);
+
+/**
+ * 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 negotiated 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 negotiated codecs from.
+ *
+ * @return The retreieved codecs.
+ */
+GList *purple_media_get_negotiated_codecs(PurpleMedia *media, const gchar *sess_id);
+
+/**
+ * Gets the local 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 local codecs from.
+ *
+ * @return The retreieved codecs.
+ */
+GList *purple_media_get_local_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.
+ */
+FsCandidate *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.
+ */
+FsCandidate *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, FsCodec *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);
+
+/**
+ * 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);
+
+#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 Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,227 @@
+/**
+ * @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;
+};
+
+#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,
+		G_TYPE_BOOLEAN, 1, PURPLE_TYPE_MEDIA);
+	g_type_class_add_private(klass, sizeof(PurpleMediaManagerPrivate));
+}
+
+static void
+purple_media_manager_init (PurpleMediaManager *media)
+{
+	media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media);
+	media->priv->medias = NULL;
+}
+
+static void
+purple_media_manager_finalize (GObject *media)
+{
+	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);
+	}
+	parent_class->finalize(media);
+}
+
+PurpleMediaManager *
+purple_media_manager_get()
+{
+	static PurpleMediaManager *manager = NULL;
+
+	if (manager == NULL)
+		manager = PURPLE_MEDIA_MANAGER(g_object_new(purple_media_manager_get_type(), NULL));
+	return manager;
+}
+
+PurpleMedia *
+purple_media_manager_create_media(PurpleMediaManager *manager,
+				  PurpleConnection *gc,
+				  const char *conference_type,
+				  const char *remote_user)
+{
+	PurpleMedia *media;
+	FsConference *conference = FS_CONFERENCE(gst_element_factory_make(conference_type, NULL));
+	GstStateChangeReturn ret;
+	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(),
+			     "screenname", remote_user,
+			     "connection", gc, 
+			     "farsight-conference", conference,
+			     NULL));
+
+	ret = gst_element_set_state(purple_media_get_pipeline(media), 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, &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;
+	GstElement *level = 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)
+		purple_media_audio_init_src(&ret, &level);
+	else if (type & PURPLE_MEDIA_RECV_AUDIO)
+		purple_media_audio_init_recv(&ret, &level);
+	else if (type & PURPLE_MEDIA_SEND_VIDEO)
+		purple_media_video_init_src(&ret);
+	else if (type & PURPLE_MEDIA_RECV_VIDEO)
+		purple_media_video_init_recv(&ret);
+
+	if (ret == NULL)
+		purple_debug_error("media", "Error creating source or sink\n");
+
+	return ret;
+}
+
+#endif  /* USE_VV */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/mediamanager.h	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,146 @@
+/**
+ * @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_
+
+#include "internal.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;
+
+/** 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. */
+};
+
+#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);
+
+/**
+ * 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);
+
+/*}@*/
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif  /* USE_VV */
+
+
+#endif  /* __MEDIA_MANAGER_H_ */
--- a/libpurple/plugins/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/plugins/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -140,6 +140,9 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(PLUGIN_CFLAGS) \
 	$(DBUS_CFLAGS)
--- a/libpurple/plugins/perl/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/plugins/perl/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -5,7 +5,7 @@
 plugin_LTLIBRARIES = perl.la
 
 perl_la_LDFLAGS = -module -avoid-version
-perl_la_LIBADD = $(GLIB_LIBS) $(PERL_LIBS)
+perl_la_LIBADD = $(GLIB_LIBS) $(PERL_LIBS) $(FARSIGHT_LIBS)
 perl_la_SOURCES = \
 	perl.c \
 	perl-common.c \
@@ -167,4 +167,5 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(PLUGIN_CFLAGS) \
-	$(PERL_CFLAGS)
+	$(PERL_CFLAGS) \
+	$(FARSIGHT_CFLAGS)
--- a/libpurple/plugins/ssl/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/plugins/ssl/Makefile.am	Thu Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/plugins/tcl/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -7,7 +7,7 @@
 tcl_la_SOURCES = tcl.c tcl_glib.c tcl_glib.h tcl_cmds.c tcl_signals.c tcl_purple.h \
                  tcl_ref.c tcl_cmd.c
 
-tcl_la_LIBADD = $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS)
+tcl_la_LIBADD = $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS) $(FARSIGHT_LIBS)
 
 EXTRA_DIST = signal-test.tcl Makefile.mingw
 
@@ -18,5 +18,6 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(PLUGIN_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(TK_CFLAGS) \
 	$(TCL_CFLAGS)
--- a/libpurple/protocols/bonjour/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -51,5 +51,12 @@
 	$(GLIB_CFLAGS) \
 	$(DEBUG_CFLAGS) \
 	$(LIBXML_CFLAGS) \
-	$(AVAHI_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
 
+#if MDNS_AVAHI
+#  AM_CPPFLAGS += $(AVAHI_CFLAGS)
+#else
+#endif
+
--- a/libpurple/protocols/bonjour/bonjour.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Thu Nov 06 03:20:05 2008 +0000
@@ -499,13 +499,13 @@
 	NULL,                                                    /* whiteboard_prpl_ops */
 	NULL,                                                    /* send_raw */
 	NULL,                                                    /* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,                                                    /* unregister_user */
+	NULL,                                                    /* send_attention */
+	NULL,                                                    /* get_attention_types */
+	sizeof(PurplePluginProtocolInfo),                        /* struct_size */
+	NULL,                                                    /* get_account_text_table */
+	NULL,                                                    /* initiate_media */
+	NULL                                                     /* can_do_media */
 };
 
 static PurplePluginInfo info =
@@ -725,3 +725,4 @@
 }
 
 PURPLE_INIT_PLUGIN(bonjour, init_plugin, info);
+
--- a/libpurple/protocols/gg/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/gg/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -75,5 +75,8 @@
 	-I$(top_builddir)/libpurple \
 	$(INTGG_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
 	$(DEBUG_CFLAGS)
 
--- a/libpurple/protocols/gg/gg.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/gg/gg.c	Thu Nov 06 03:20:05 2008 +0000
@@ -2165,13 +2165,13 @@
 	NULL,				/* whiteboard_prpl_ops */
 	NULL,				/* send_raw */
 	NULL,				/* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
+	NULL,				/* unregister_user */
+	NULL,				/* send_attention */
+	NULL,				/* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,                           /* get_account_text_table */
+	NULL,                           /* initiate_media */
+	NULL                            /* can_do_media */
 };
 /* }}} */
 
@@ -2258,3 +2258,4 @@
 PURPLE_INIT_PLUGIN(gg, init_plugin, info);
 
 /* vim: set ts=8 sts=0 sw=8 noet: */
+
--- a/libpurple/protocols/irc/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/irc/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -32,4 +32,6 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
 	$(DEBUG_CFLAGS)
--- a/libpurple/protocols/irc/irc.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/irc/irc.c	Thu Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -21,6 +21,18 @@
 			  iq.h \
 			  jabber.c \
 			  jabber.h \
+			  jingle/jingle.c \
+			  jingle/jingle.h \
+			  jingle/content.c \
+			  jingle/content.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 \
@@ -71,7 +83,7 @@
 pkg_LTLIBRARIES = libjabber.la libxmpp.la
 noinst_LIBRARIES =
 
-libjabber_la_SOURCES = $(JABBERSOURCES)
+libjabber_la_SOURCES = $(JABBERSOURCES)		
 libjabber_la_LIBADD = $(GLIB_LIBS) $(SASL_LIBS) $(LIBXML_LIBS)
 
 libxmpp_la_SOURCES = libxmpp.c
@@ -84,4 +96,6 @@
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
 	$(LIBXML_CFLAGS)
--- a/libpurple/protocols/jabber/Makefile.mingw	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/Makefile.mingw	Thu Nov 06 03:20:05 2008 +0000
@@ -53,6 +53,12 @@
 			google.c \
 			iq.c \
 			jabber.c \
+			jingle/jingle.c \
+			jingle/content.c \
+			jingle/rawudp.c \
+			jingle/rtp.c \
+			jingle/session.c \
+			jingle/transport.c \
 			jutil.c \
 			message.c \
 			oob.c \
--- a/libpurple/protocols/jabber/caps.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/caps.c	Thu Nov 06 03:20:05 2008 +0000
@@ -27,6 +27,7 @@
 #include "util.h"
 #include "iq.h"
 
+
 #define JABBER_CAPS_FILENAME "xmpp-caps.xml"
 
 static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */
--- a/libpurple/protocols/jabber/disco.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/disco.c	Thu Nov 06 03:20:05 2008 +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,17 @@
 						SUPPORT_FEATURE(feat->namespace);
 				}
 			}
+#ifdef USE_VV
+		} else if (node && !strcmp(node, CAPS0115_NODE "#voice-v1")) {
+			SUPPORT_FEATURE("http://www.google.com/session");
+			SUPPORT_FEATURE("http://www.google.com/transport/p2p");
+			SUPPORT_FEATURE("http://www.google.com/transport/raw-udp");
+			SUPPORT_FEATURE("http://www.google.com/session/phone");
+			SUPPORT_FEATURE(JINGLE);
+			SUPPORT_FEATURE(JINGLE_APP_RTP_SUPPORT_AUDIO);
+			SUPPORT_FEATURE(JINGLE_APP_RTP_SUPPORT_VIDEO);
+			SUPPORT_FEATURE(JINGLE_TRANSPORT_RAWUDP);
+#endif
 		} else {
 			const char *ext = NULL;
 			unsigned pos;
--- a/libpurple/protocols/jabber/google.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/google.c	Thu Nov 06 03:20:05 2008 +0000
@@ -20,6 +20,7 @@
 
 #include "internal.h"
 #include "debug.h"
+#include "mediamanager.h"
 #include "util.h"
 #include "privacy.h"
 
@@ -29,6 +30,358 @@
 #include "presence.h"
 #include "iq.h"
 
+#ifdef USE_VV
+#include <gst/farsight/fs-conference-iface.h>
+
+typedef struct {
+	char *id;
+	char *initiator;
+} GoogleSessionId;
+
+typedef enum {
+	UNINIT,
+	SENT_INITIATE,
+	RECEIVED_INITIATE,
+	IN_PRORESS,
+	TERMINATED
+} GoogleSessionState;
+
+typedef struct {
+	GoogleSessionId id;
+	GoogleSessionState state;
+	PurpleMedia *media;
+	JabberStream *js; 
+	char *remote_jid;
+} GoogleSession;
+
+GHashTable *sessions = NULL;
+
+static guint 
+google_session_id_hash(gconstpointer key) 
+{
+	GoogleSessionId *id = (GoogleSessionId*)key;
+	
+	guint id_hash = g_str_hash(id->id);
+	guint init_hash = g_str_hash(id->initiator);
+
+	return 23 * id_hash + init_hash;
+}
+
+static gboolean 
+google_session_id_equal(gconstpointer a, gconstpointer b)
+{
+	GoogleSessionId *c = (GoogleSessionId*)a;
+	GoogleSessionId *d = (GoogleSessionId*)b;
+	
+	return !strcmp(c->id, d->id) && !strcmp(c->initiator, d->initiator);
+}
+
+static void
+google_session_destroy(GoogleSession *session)
+{
+	g_hash_table_remove(sessions, &(session->id));
+	g_free(session->id.id);
+	g_free(session->id.initiator);
+	g_free(session->remote_jid);
+	g_object_unref(session->media);
+	g_free(session);
+}
+
+static xmlnode *
+google_session_create_xmlnode(GoogleSession *session, const char *type)
+{
+	xmlnode *node = xmlnode_new("session");
+	xmlnode_set_namespace(node, "http://www.google.com/session");
+	xmlnode_set_attrib(node, "id", session->id.id);
+	xmlnode_set_attrib(node, "initiator", session->id.initiator);
+	xmlnode_set_attrib(node, "type", type);
+	return node;
+}
+
+static void
+google_session_send_accept(GoogleSession *session)
+{
+	xmlnode *sess, *desc, *payload;
+	GList *codecs = purple_media_get_negotiated_codecs(session->media, "google-voice");
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	sess = google_session_create_xmlnode(session, "accept");
+	xmlnode_insert_child(iq->node, sess);
+	desc = xmlnode_new_child(sess, "description");
+	xmlnode_set_namespace(desc, "http://www.google.com/session/phone");
+
+	for (;codecs; codecs = codecs->next) {
+		FsCodec *codec = (FsCodec*)codecs->data;
+		char id[8], clockrate[10];
+		payload = xmlnode_new_child(desc, "payload-type");
+		g_snprintf(id, sizeof(id), "%d", codec->id);
+		g_snprintf(clockrate, sizeof(clockrate), "%d", codec->clock_rate);
+		xmlnode_set_attrib(payload, "name", codec->encoding_name);
+		xmlnode_set_attrib(payload, "id", id);
+		xmlnode_set_attrib(payload, "clockrate", clockrate);
+	}
+
+	fs_codec_list_destroy(codecs);
+	jabber_iq_send(iq);
+	gst_element_set_state(purple_media_get_pipeline(session->media), GST_STATE_PLAYING);
+}
+
+static void
+google_session_send_terminate(GoogleSession *session)
+{
+	xmlnode *sess;
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	sess = google_session_create_xmlnode(session, "terminate");
+	xmlnode_insert_child(iq->node, sess);
+	
+	jabber_iq_send(iq);
+	google_session_destroy(session);
+}
+
+static void
+google_session_send_reject(GoogleSession *session)
+{
+	xmlnode *sess;
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	sess = google_session_create_xmlnode(session, "reject");
+	xmlnode_insert_child(iq->node, sess);
+	
+	jabber_iq_send(iq);
+	google_session_destroy(session);
+}
+
+
+static void 
+google_session_candidates_prepared (PurpleMedia *media, GoogleSession *session)
+{
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+	GList *candidates = purple_media_get_local_candidates(session->media, "google-voice",
+							      session->remote_jid);
+	FsCandidate *transport;
+	xmlnode *sess;
+	xmlnode *candidate;
+	sess = google_session_create_xmlnode(session, "candidates");
+	xmlnode_insert_child(iq->node, sess);
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	
+	for (;candidates;candidates = candidates->next) {
+		char port[8];
+		char pref[8];
+		transport = (FsCandidate*)(candidates->data);
+
+		if (!strcmp(transport->ip, "127.0.0.1"))
+			continue;
+	
+		candidate = xmlnode_new("candidate");
+
+		g_snprintf(port, sizeof(port), "%d", transport->port);
+		g_snprintf(pref, sizeof(pref), "%d", transport->priority);
+
+		xmlnode_set_attrib(candidate, "address", transport->ip);
+		xmlnode_set_attrib(candidate, "port", port);
+		xmlnode_set_attrib(candidate, "name", "rtp");
+		xmlnode_set_attrib(candidate, "username", transport->username);
+		xmlnode_set_attrib(candidate, "password", transport->password);
+		xmlnode_set_attrib(candidate, "preference", pref);
+		xmlnode_set_attrib(candidate, "protocol", transport->proto == FS_NETWORK_PROTOCOL_UDP ? "udp" : "tcp");
+		xmlnode_set_attrib(candidate, "type", transport->type == FS_CANDIDATE_TYPE_HOST ? "local" :
+						      transport->type == FS_CANDIDATE_TYPE_PRFLX ? "stun" :
+					       	      transport->type == FS_CANDIDATE_TYPE_RELAY ? "relay" : NULL);
+		xmlnode_set_attrib(candidate, "generation", "0");
+		xmlnode_set_attrib(candidate, "network", "0");
+		xmlnode_insert_child(sess, candidate);
+		
+	}
+	jabber_iq_send(iq);
+}
+
+static void
+google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	JabberIq *result;
+	GList *codecs = NULL;
+	xmlnode *desc_element, *codec_element;
+	FsCodec *codec;
+	const char *id, *encoding_name,  *clock_rate;
+		
+	if (session->state != UNINIT) {
+		purple_debug_error("jabber", "Received initiate for active session.\n");
+		return;
+	}
+
+	session->media = purple_media_manager_create_media(purple_media_manager_get(), js->gc,
+							   "fsrtpconference", session->remote_jid);
+
+	/* "rawudp" will need to be changed to "nice" when libnice is finished */
+	/* GTalk will require the NICE_COMPATIBILITY_GOOGLE param */
+	purple_media_add_stream(session->media, "google-voice", session->remote_jid, 
+				PURPLE_MEDIA_AUDIO, "rawudp", 0, NULL);
+
+	desc_element = xmlnode_get_child(sess, "description");
+	
+	for (codec_element = xmlnode_get_child(desc_element, "payload-type"); 
+	     codec_element; 
+	     codec_element = xmlnode_get_next_twin(codec_element)) {
+		encoding_name = xmlnode_get_attrib(codec_element, "name");
+		id = xmlnode_get_attrib(codec_element, "id");
+		clock_rate = xmlnode_get_attrib(codec_element, "clockrate");
+
+		codec = fs_codec_new(atoi(id), encoding_name, FS_MEDIA_TYPE_AUDIO,
+				     clock_rate ? atoi(clock_rate) : 0);
+		codecs = g_list_append(codecs, codec);
+	}
+
+	purple_media_set_remote_codecs(session->media, "google-voice", session->remote_jid, codecs);
+
+	g_signal_connect_swapped(G_OBJECT(session->media), "accepted",
+				 G_CALLBACK(google_session_send_accept), session);
+	g_signal_connect_swapped(G_OBJECT(session->media), "reject",
+				 G_CALLBACK(google_session_send_reject), session);
+	g_signal_connect_swapped(G_OBJECT(session->media), "hangup",
+				 G_CALLBACK(google_session_send_terminate), session);
+	g_signal_connect(G_OBJECT(session->media), "candidates-prepared", 
+			 G_CALLBACK(google_session_candidates_prepared), session);
+	purple_media_ready(session->media);
+
+	fs_codec_list_destroy(codecs);
+	
+	result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "to", session->remote_jid);
+	jabber_iq_send(result);
+}
+
+static void 
+google_session_handle_candidates(JabberStream  *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	JabberIq *result;
+	GList *list = NULL;
+	xmlnode *cand;
+	static int name = 0;
+	char n[4];	
+		
+	for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) {
+		FsCandidate *info;
+		g_snprintf(n, sizeof(n), "S%d", name++);
+		info = fs_candidate_new(n, FS_COMPONENT_RTP, !strcmp(xmlnode_get_attrib(cand, "type"), "local") ?
+					FS_CANDIDATE_TYPE_HOST :
+			     		!strcmp(xmlnode_get_attrib(cand, "type"), "stun") ?
+						FS_CANDIDATE_TYPE_PRFLX :
+			     			!strcmp(xmlnode_get_attrib(cand, "type"), "relay") ?
+							FS_CANDIDATE_TYPE_RELAY : FS_CANDIDATE_TYPE_HOST,
+						!strcmp(xmlnode_get_attrib(cand, "protocol"),"udp") ?
+							FS_NETWORK_PROTOCOL_UDP : FS_NETWORK_PROTOCOL_TCP,
+					xmlnode_get_attrib(cand, "address"), atoi(xmlnode_get_attrib(cand, "port")));
+
+		info->username = g_strdup(xmlnode_get_attrib(cand, "username"));
+		info->password = g_strdup(xmlnode_get_attrib(cand, "password"));
+
+		list = g_list_append(list, info);
+	}
+
+	purple_media_add_remote_candidates(session->media, "google-voice", session->remote_jid, list);
+	fs_candidate_list_destroy(list);
+
+	result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "to", session->remote_jid);
+	jabber_iq_send(result);
+}
+
+static void
+google_session_handle_reject(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	purple_media_got_hangup(session->media);
+	
+	google_session_destroy(session);
+}
+
+static void
+google_session_handle_terminate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	purple_media_got_hangup(session->media);
+
+	google_session_destroy(session);
+}
+
+static void
+google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *packet)
+{
+	xmlnode *sess = xmlnode_get_child(packet, "session");	
+	const char *type = xmlnode_get_attrib(sess, "type");
+
+	if (!strcmp(type, "initiate")) {
+		google_session_handle_initiate(js, session, packet, sess);
+	} else if (!strcmp(type, "accept")) {
+	} else if (!strcmp(type, "reject")) {
+		google_session_handle_reject(js, session, packet, sess);
+	} else if (!strcmp(type, "terminate")) {
+		google_session_handle_terminate(js, session, packet, sess);
+	} else if (!strcmp(type, "candidates")) {
+		google_session_handle_candidates(js, session, packet, sess);
+	}
+}
+#endif /* USE_VV */
+
+void
+jabber_google_session_parse(JabberStream *js, xmlnode *packet)
+{
+#ifdef USE_VV
+	GoogleSession *session;
+	GoogleSessionId id;
+
+	xmlnode *session_node;
+	xmlnode *desc_node;
+
+	if (strcmp(xmlnode_get_attrib(packet, "type"), "set"))
+		return;
+
+	session_node = xmlnode_get_child(packet, "session");
+	if (!session_node)
+		return;
+
+	id.id = (gchar*)xmlnode_get_attrib(session_node, "id");
+	if (!id.id)
+		return;
+
+	id.initiator = (gchar*)xmlnode_get_attrib(session_node, "initiator");
+	if (!id.initiator)
+		return;
+
+	if (sessions == NULL)
+		sessions = g_hash_table_new(google_session_id_hash, google_session_id_equal);
+	session = (GoogleSession*)g_hash_table_lookup(sessions, &id);
+
+	if (session) {
+		google_session_parse_iq(js, session, packet);
+		return;
+	}
+
+	/* If the session doesn't exist, this has to be an initiate message */
+	if (strcmp(xmlnode_get_attrib(session_node, "type"), "initiate"))
+		return;
+	desc_node = xmlnode_get_child(session_node, "description");
+	if (!desc_node)
+		return;
+	session = g_new0(GoogleSession, 1);
+	session->id.id = g_strdup(id.id);
+	session->id.initiator = g_strdup(id.initiator);
+	session->state = UNINIT;
+	session->js = js;
+	session->remote_jid = g_strdup(session->id.initiator);
+	g_hash_table_insert(sessions, &(session->id), session);
+
+	google_session_parse_iq(js, session, packet);
+#else
+	/* TODO: send proper error response */
+#endif /* USE_VV */
+}
+
 static void
 jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul)
 {
--- a/libpurple/protocols/jabber/google.h	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/google.h	Thu Nov 06 03:20:05 2008 +0000
@@ -45,6 +45,7 @@
 
 char *jabber_google_format_to_html(const char *text);
 
+void jabber_google_session_parse(JabberStream *js, xmlnode *node);
 
 
 #endif   /* _PURPLE_GOOGLE_H_ */
--- a/libpurple/protocols/jabber/iq.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/iq.c	Thu Nov 06 03:20:05 2008 +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,9 @@
 	jabber_iq_register_handler("http://jabber.org/protocol/disco#items", jabber_disco_items_parse);
 	jabber_iq_register_handler("jabber:iq:register", jabber_register_parse);
 	jabber_iq_register_handler("urn:xmpp:ping", urn_xmpp_ping_parse);
+#ifdef USE_VV
+	jabber_iq_register_handler(JINGLE, jingle_parse);
+#endif
 }
 
 void jabber_iq_uninit(void)
--- a/libpurple/protocols/jabber/jabber.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Nov 06 03:20:05 2008 +0000
@@ -58,6 +58,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/session/phone"
+
+#endif
 
 #define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5)
 
@@ -664,6 +673,9 @@
 	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
 
 	if(!js->user) {
 		purple_connection_error_reason (gc,
@@ -1275,6 +1287,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.
@@ -1905,10 +1922,13 @@
 	JabberID *jid;
 	JabberBuddy *jb;
 	JabberBuddyResource *jbr;
-	
+
 	if(!(jid = jabber_id_new(who)))
 		return;
 
+#ifdef USE_VV
+	jingle_rtp_terminate_session(js, who);
+#endif
 	if((jb = jabber_buddy_find(js, who, TRUE)) &&
 			(jbr = jabber_buddy_find_resource(jb, jid->resource))) {
 		if(jbr->thread_id) {
@@ -2385,6 +2405,54 @@
 {
 	return TRUE;
 }
+#ifdef USE_VV
+
+PurpleMedia *
+jabber_initiate_media(PurpleConnection *gc, const char *who, 
+		      PurpleMediaSessionType type)
+{
+	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;
+	}
+	/* 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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Thu Nov 06 03:20:05 2008 +0000
@@ -53,6 +53,8 @@
 #include "circbuffer.h"
 #include "connection.h"
 #include "dnssrv.h"
+#include "media.h"
+#include "mediamanager.h"
 #include "roomlist.h"
 #include "sslconn.h"
 
@@ -241,6 +243,12 @@
 	 * for when we lookup buddy icons from a url
 	 */
 	GSList *url_datas;
+
+#ifdef USE_VV
+	/* keep a hash table of JingleSessions */
+	GHashTable *sessions;
+	GHashTable *medias;
+#endif
 };
 
 typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
@@ -308,4 +316,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 Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,455 @@
+/**
+ * @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 "config.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 Nov 06 03:20:05 2008 +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/jingle.c	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,434 @@
+/*
+ * @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 "config.h"
+#include "content.h"
+#include "debug.h"
+#include "jingle.h"
+#include <string.h>
+#include "session.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_APP_RTP))
+		return JINGLE_TYPE_RTP;
+#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 if (!strcmp(type, JINGLE_TRANSPORT_RAWUDP))
+		return JINGLE_TYPE_RAWUDP;
+#if 0
+	else if (!strcmp(type, JINGLE_TRANSPORT_ICEUDP))
+		return JINGLE_TYPE_ICEUDP;
+	else if (!strcmp(type, JINGLE_TRANSPORT_SOCKS))
+		return JINGLE_TYPE_SOCKS;
+	else if (!strcmp(type, JINGLE_TRANSPORT_IBB))
+		return JINGLE_TYPE_IBB;
+#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);
+	}
+}
+
+void
+jingle_terminate_sessions(JabberStream *js)
+{
+	GList *values = js->sessions ?
+			g_hash_table_get_values(js->sessions) : NULL;
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		JingleSession *session = (JingleSession *)values->data;
+		g_object_unref(session);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.h	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,81 @@
+/*
+ * @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);
+
+#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 Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,239 @@
+/**
+ * @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 "rawudp.h"
+#include "jingle.h"
+#include "debug.h"
+
+#include <string.h>
+
+struct _JingleRawUdpPrivate
+{
+	guint generation;
+	gchar *id;
+	gchar *ip;
+	guint port;
+};
+
+#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_GENERATION,
+	PROP_ID,
+	PROP_IP,
+	PROP_PORT,
+};
+
+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_GENERATION,
+			g_param_spec_uint("generation",
+			"Generation",
+			"The generation for this transport.",
+			0,
+			G_MAXUINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_ID,
+			g_param_spec_string("id",
+			"Id",
+			"The id for this transport.",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_IP,
+			g_param_spec_string("ip",
+			"IP Address",
+			"The IP address for this transport.",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_PORT,
+			g_param_spec_uint("port",
+			"Port",
+			"The port for this transport.",
+			0,
+			65535,
+			0,
+			G_PARAM_READWRITE));
+
+	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");
+
+	g_free(priv->id);
+	g_free(priv->ip);
+}
+
+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_GENERATION:
+			rawudp->priv->generation = g_value_get_uint(value);
+			break;
+		case PROP_ID:
+			g_free(rawudp->priv->id);
+			rawudp->priv->id = g_value_dup_string(value);
+			break;
+		case PROP_IP:
+			g_free(rawudp->priv->ip);
+			rawudp->priv->ip = g_value_dup_string(value);
+			break;
+		case PROP_PORT:
+			rawudp->priv->port = g_value_get_uint(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_GENERATION:
+			g_value_set_uint(value, rawudp->priv->generation);
+			break;
+		case PROP_ID:
+			g_value_set_string(value, rawudp->priv->id);
+			break;
+		case PROP_IP:
+			g_value_set_string(value, rawudp->priv->ip);
+			break;
+		case PROP_PORT:
+			g_value_set_uint(value, rawudp->priv->port);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+JingleRawUdp *
+jingle_rawudp_create(guint generation, const gchar *id, const gchar *ip, guint port)
+{
+	return g_object_new(jingle_rawudp_get_type(),
+			"generation", generation,
+			"id", id,
+			"ip", ip,
+			"port", port, NULL);
+}
+
+static JingleTransport *
+jingle_rawudp_parse_internal(xmlnode *rawudp)
+{
+	JingleTransport *transport = parent_class->parse(rawudp);
+	
+	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) {
+		xmlnode *xmltransport = xmlnode_new_child(node, "candidate");
+		JingleRawUdpPrivate *priv = JINGLE_RAWUDP_GET_PRIVATE(transport);
+		gchar *generation = g_strdup_printf("%d", priv->generation);
+		gchar *port = g_strdup_printf("%d", priv->port);
+
+		xmlnode_set_attrib(xmltransport, "generation", generation);
+		xmlnode_set_attrib(xmltransport, "id", priv->id);
+		xmlnode_set_attrib(xmltransport, "ip", priv->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 Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,81 @@
+/**
+ * @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_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;
+
+/** 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. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the rawudp class's GType
+ *
+ * @return The rawudp class's GType.
+ */
+GType jingle_rawudp_get_type(void);
+
+JingleRawUdp *jingle_rawudp_create(guint generation, const gchar *id, const gchar *ip, guint port);
+
+#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 Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,684 @@
+/**
+ * @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"
+
+#include "jabber.h"
+#include "jingle.h"
+#include "media.h"
+#include "mediamanager.h"
+#include "rawudp.h"
+#include "rtp.h"
+#include "session.h"
+#include "debug.h"
+
+#include <string.h>
+
+struct _JingleRtpPrivate
+{
+	gchar *media_type;
+	gboolean candidates_ready;
+	gboolean codecs_ready;
+};
+
+#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;
+	}
+}
+
+static gboolean
+jingle_rtp_ready_to_initiate(JingleSession *session, PurpleMedia *media)
+{
+	if (jingle_session_is_initiator(session)) {
+		GList *iter = jingle_session_get_contents(session);
+		for (; iter; iter = g_list_next(iter)) {
+			JingleContent *content = iter->data;
+			gchar *name = jingle_content_get_name(content);
+			if (!JINGLE_IS_RTP(content)
+					|| JINGLE_RTP_GET_PRIVATE(content)->codecs_ready == FALSE
+					|| JINGLE_RTP_GET_PRIVATE(content)->candidates_ready == FALSE) {
+				g_free(name);
+				return FALSE;
+			}
+			g_free(name);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+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_candidate_to_transport(GType type, guint generation, FsCandidate *candidate)
+{
+	if (type == JINGLE_TYPE_RAWUDP) {
+		return JINGLE_TRANSPORT(jingle_rawudp_create(generation,
+				candidate->foundation, candidate->ip, candidate->port));
+#if 0
+	} else if (type == JINGLE_TYPE_ICEUDP) {
+		return NULL;
+#endif
+	} else {
+		return NULL;
+	}
+}
+
+static FsCandidate *
+jingle_rtp_transport_to_candidate(xmlnode *transport)
+{
+	xmlnode *candidate = xmlnode_get_child(transport, "candidate");
+	const gchar *type = xmlnode_get_namespace(transport);
+	if (!strcmp(type, JINGLE_TRANSPORT_RAWUDP)) {
+		return fs_candidate_new("", FS_COMPONENT_RTP,
+				FS_CANDIDATE_TYPE_SRFLX, FS_NETWORK_PROTOCOL_UDP,
+				xmlnode_get_attrib(candidate, "ip"),
+				atoi(xmlnode_get_attrib(candidate, "port")));
+#if 0
+	} else if (type == JINGLE_TRANSPORT_ICEUDP) {
+		return NULL;
+#endif
+	} else {
+		return NULL;
+	}
+}
+
+static void
+jingle_rtp_accept_cb(PurpleMedia *media, JingleSession *session)
+{
+	jabber_iq_send(jingle_session_to_packet(session, JINGLE_TRANSPORT_INFO));
+	jabber_iq_send(jingle_session_to_packet(session, JINGLE_SESSION_ACCEPT));
+}
+
+static void
+jingle_rtp_reject_cb(PurpleMedia *media, JingleSession *session)
+{
+	jabber_iq_send(jingle_session_to_packet(session, JINGLE_SESSION_TERMINATE));
+	g_object_unref(session);
+}
+
+static void
+jingle_rtp_hangup_cb(PurpleMedia *media, JingleSession *session)
+{
+	jabber_iq_send(jingle_session_to_packet(session, JINGLE_SESSION_TERMINATE));
+	g_object_unref(session);
+}
+
+static void
+jingle_rtp_new_candidate_cb(PurpleMedia *media, gchar *sid, gchar *name, FsCandidate *candidate, JingleSession *session)
+{
+	purple_debug_info("jingle-rtp", "jingle_rtp_new_candidate_cb\n");
+
+	if (candidate->component_id == 1) {
+		JingleContent *content = jingle_session_find_content(session, sid, "initiator");
+		JingleTransport *transport =
+				JINGLE_TRANSPORT(jingle_rtp_candidate_to_transport(
+				JINGLE_TYPE_RAWUDP, 0, candidate));
+		jingle_content_set_pending_transport(content, transport);
+		jingle_content_accept_transport(content);
+	}
+}
+
+static void
+jingle_rtp_candidates_prepared_cb(PurpleMedia *media, gchar *sid, gchar *name, JingleSession *session)
+{
+	JingleContent *content = jingle_session_find_content(session, sid, "initiator");
+	JINGLE_RTP_GET_PRIVATE(content)->candidates_ready = TRUE;
+
+	if (jingle_rtp_ready_to_initiate(session, media))
+		jabber_iq_send(jingle_session_to_packet(session, JINGLE_SESSION_INITIATE));
+}
+
+static void
+jingle_rtp_candidate_pair_established_cb(PurpleMedia *media, FsCandidate *local_candidate, FsCandidate *remote_candidate, JingleSession *session)
+{
+
+}
+
+static void
+jingle_rtp_codecs_ready_cb(PurpleMedia *media, gchar *sid, JingleSession *session)
+{
+	JingleContent *content = jingle_session_find_content(session, sid, "initiator");
+
+	if (content == NULL)
+		content = jingle_session_find_content(session, sid, "responder");
+
+	if (JINGLE_RTP_GET_PRIVATE(content)->codecs_ready == FALSE) {
+		JINGLE_RTP_GET_PRIVATE(content)->codecs_ready =
+				purple_media_codecs_ready(media, sid);
+
+		if (jingle_rtp_ready_to_initiate(session, media))
+			jabber_iq_send(jingle_session_to_packet(session, JINGLE_SESSION_INITIATE));
+	}
+}
+
+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);
+	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_accept_cb), session);
+	g_signal_connect(G_OBJECT(media), "reject",
+				 G_CALLBACK(jingle_rtp_reject_cb), session);
+	g_signal_connect(G_OBJECT(media), "hangup",
+				 G_CALLBACK(jingle_rtp_hangup_cb), session);
+	g_signal_connect(G_OBJECT(media), "new-candidate",
+				 G_CALLBACK(jingle_rtp_new_candidate_cb), session);
+	g_signal_connect(G_OBJECT(media), "candidates-prepared",
+				 G_CALLBACK(jingle_rtp_candidates_prepared_cb), session);
+	g_signal_connect(G_OBJECT(media), "candidate-pair",
+				 G_CALLBACK(jingle_rtp_candidate_pair_established_cb), session);
+	g_signal_connect(G_OBJECT(media), "codecs-ready",
+				 G_CALLBACK(jingle_rtp_codecs_ready_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;
+	FsMediaType type;
+	FsStreamDirection direction;
+	JingleTransport *transport;
+
+	/* 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
+		transmitter = "notransmitter";
+
+	if (!strcmp(media_type, "audio"))
+		type = FS_MEDIA_TYPE_AUDIO;
+	else
+		type = FS_MEDIA_TYPE_VIDEO;
+
+	if (!strcmp(senders, "both"))
+		direction = FS_DIRECTION_BOTH;
+	else if (!strcmp(senders, "initiator")
+			&& jingle_session_is_initiator(session))
+		direction = FS_DIRECTION_SEND;
+	else
+		direction = FS_DIRECTION_RECV;
+
+	purple_media_add_stream(media, name, remote_jid,
+			purple_media_from_fs(type, direction),
+			transmitter, 0, NULL);
+
+
+	g_free(name);
+	g_free(media_type);
+	g_free(remote_jid);
+	g_free(senders);
+	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;
+	FsCodec *codec;
+	const gchar *media = xmlnode_get_attrib(description, "media");
+	FsMediaType type = !strcmp(media, "video") ? FS_MEDIA_TYPE_VIDEO :
+			!strcmp(media, "audio") ? FS_MEDIA_TYPE_AUDIO : 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 = fs_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)) {
+			fs_codec_add_optional_parameter(codec,
+					xmlnode_get_attrib(param, "name"),
+					xmlnode_get_attrib(param, "value"));
+		}
+
+		codec_str = fs_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) {
+		FsCodec *codec = (FsCodec*)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)) {
+			FsCodecParameter *fsparam = iter->data;
+			xmlnode *param = xmlnode_new_child(payload, "parameter");
+			xmlnode_set_attrib(param, "name", fsparam->name);
+			xmlnode_set_attrib(param, "value", fsparam->value);
+		}
+
+		codec_str = fs_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_local_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_got_accept(jingle_rtp_get_media(session));
+
+			g_object_unref(session);
+			break;
+		}
+		case JINGLE_SESSION_INITIATE: {
+			JingleSession *session = jingle_content_get_session(content);
+			xmlnode *description = xmlnode_get_child(xmlcontent, "description");
+			xmlnode *transport = xmlnode_get_child(xmlcontent, "transport");
+			FsCandidate *candidate = jingle_rtp_transport_to_candidate(transport);
+			GList *candidates = g_list_append(NULL, candidate);
+			GList *codecs = jingle_rtp_parse_codecs(description);
+
+			jingle_rtp_init_media(content);
+
+			purple_media_set_remote_codecs(jingle_rtp_get_media(session),
+					jingle_content_get_name(content),
+					jingle_session_get_remote_jid(session), codecs);
+
+			/* manufacture rtcp candidate */
+			candidate = fs_candidate_copy(candidate);
+			candidate->component_id = 2;
+			candidate->port = candidate->port + 1;
+			candidates = g_list_append(candidates, candidate);
+
+			purple_media_add_remote_candidates(jingle_rtp_get_media(session),
+					jingle_content_get_name(content),
+					jingle_session_get_remote_jid(session),
+					candidates);
+
+			/* very hacky */
+			if (xmlnode_get_next_twin(xmlcontent) == NULL)
+				purple_media_ready(jingle_rtp_get_media(session));
+
+			g_object_unref(session);
+			break;
+		}
+		case JINGLE_SESSION_TERMINATE: {
+			JingleSession *session = jingle_content_get_session(content);
+			purple_media_got_hangup(jingle_rtp_get_media(session));
+			g_object_unref(session);
+			break;
+		}
+		case JINGLE_TRANSPORT_INFO: {
+			JingleSession *session = jingle_content_get_session(content);
+			xmlnode *transport = xmlnode_get_child(xmlcontent, "transport");
+			FsCandidate *candidate = jingle_rtp_transport_to_candidate(transport);
+			GList *candidates = g_list_append(NULL, candidate);
+
+			/* manufacture rtcp candidate */
+			candidate = fs_candidate_copy(candidate);
+			candidate->component_id = 2;
+			candidate->port = candidate->port + 1;
+			candidates = g_list_append(candidates, candidate);
+
+			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;
+	
+	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 ((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(JINGLE_TRANSPORT_RAWUDP);
+		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(JINGLE_TRANSPORT_RAWUDP);
+		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);
+	}
+
+	purple_media_ready(jingle_rtp_get_media(session));
+	purple_media_wait(jingle_rtp_get_media(session));
+
+	g_free(jid);
+	g_free(me);
+
+	return NULL;
+}
+
+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);
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/rtp.h	Thu Nov 06 03:20:05 2008 +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 Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,588 @@
+/**
+ * @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 "config.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;
+}
+
+JingleSession *
+jingle_session_find_by_jid(JabberStream *js, const gchar *jid)
+{
+	GList *values = (js->sessions) ? 
+			g_hash_table_get_values(js->sessions) : NULL;
+	gboolean use_bare = strchr(jid, '/') == NULL;
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		JingleSession *session = (JingleSession *)values->data;
+		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);
+			g_list_free(values);
+			return session;
+		}
+		g_free(cmp_jid);
+	}
+
+	return 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 Nov 06 03:20:05 2008 +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 Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,172 @@
+/**
+ * @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 "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 Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Thu Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/jabber/presence.c	Thu Nov 06 03:20:05 2008 +0000
@@ -265,6 +265,10 @@
 	xmlnode_set_namespace(c, "http://jabber.org/protocol/caps");
 	xmlnode_set_attrib(c, "node", CAPS0115_NODE);
 	xmlnode_set_attrib(c, "ver", VERSION);
+#ifdef USE_VV
+	/* Make sure this is 'voice-v1', or you won't be able to talk to Google Talk */
+	xmlnode_set_attrib(c, "ext", "voice-v1");
+#endif
 	
 	if(js != NULL) {
 		/* add the extensions */
--- a/libpurple/protocols/msn/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/msn/Makefile.am	Thu Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/msn/msn.c	Thu Nov 06 03:20:05 2008 +0000
@@ -2546,9 +2546,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 =
@@ -2630,3 +2631,4 @@
 }
 
 PURPLE_INIT_PLUGIN(msn, init_plugin, info);
+
--- a/libpurple/protocols/msnp9/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/msnp9/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -87,4 +87,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/msnp9/msn.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/msnp9/msn.c	Thu Nov 06 03:20:05 2008 +0000
@@ -2276,9 +2276,10 @@
 	NULL,					/* unregister_user */
 	msn_send_attention,                     /* send_attention */
 	msn_attention_types,                    /* attention_types */
-
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	msn_get_account_text_table,             /* get_account_text_table */
+	NULL,                                   /* initiate_media */
+	NULL                                    /* can_do_media */
 };
 
 static PurplePluginInfo info =
@@ -2359,3 +2360,4 @@
 }
 
 PURPLE_INIT_PLUGIN(msnp9, init_plugin, info);
+
--- a/libpurple/protocols/myspace/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/myspace/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -40,4 +40,7 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
--- a/libpurple/protocols/myspace/myspace.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Thu Nov 06 03:20:05 2008 +0000
@@ -3146,9 +3146,10 @@
 	NULL,                  /* unregister_user */
 	msim_send_attention,   /* send_attention */
 	msim_attention_types,  /* attention_types */
-
-	sizeof(PurplePluginProtocolInfo),  /* struct_size */
+	sizeof(PurplePluginProtocolInfo), /* struct_size */
 	msim_get_account_text_table,              /* get_account_text_table */
+	NULL,                   /* initiate_media */
+	NULL                    /* can_do_media */
 };
 
 
--- a/libpurple/protocols/novell/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/novell/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -54,4 +54,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
-	$(GLIB_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/novell/novell.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/novell/novell.c	Thu Nov 06 03:20:05 2008 +0000
@@ -3512,13 +3512,13 @@
 	NULL,						/* whiteboard_prpl_ops */
 	NULL,						/* send_raw */
 	NULL,						/* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
+	NULL,						/* unregister_user */
+	NULL,						/* send_attention */
+	NULL,						/* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,						/* get_account_text_table */
+	NULL,						/* initiate_media */
+	NULL						/* can_do_media */
 };
 
 static PurplePluginInfo info = {
@@ -3573,3 +3573,4 @@
 }
 
 PURPLE_INIT_PLUGIN(novell, init_plugin, info);
+
--- a/libpurple/protocols/null/nullprpl.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/null/nullprpl.c	Thu Nov 06 03:20:05 2008 +0000
@@ -1126,11 +1126,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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/oscar/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -76,4 +76,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/oscar/libaim.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Thu Nov 06 03:20:05 2008 +0000
@@ -95,9 +95,10 @@
 	NULL,					/* unregister_user */
 	NULL,					/* send_attention */
 	NULL,					/* get_attention_types */
-
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,					/* get_account_text_table */
+	NULL,					/* initiate_media */
+	NULL					/* can_do_media */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/oscar/libicq.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Thu Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/qq/Makefile.am	Thu Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/qq/qq.c	Thu Nov 06 03:20:05 2008 +0000
@@ -271,9 +271,9 @@
 	case QQ_BUDDY_ONLINE_INVISIBLE:
 		g_string_append(status, _("Invisible"));
 		break;
-	case QQ_BUDDY_ONLINE_BUSY:
-		g_string_append(status, _("Busy"));
-		break;
+	case QQ_BUDDY_ONLINE_BUSY:
+		g_string_append(status, _("Busy"));
+		break;
 	default:
 		g_string_printf(status, _("Unknown-%d"), bd->status);
 	}
@@ -419,9 +419,9 @@
 			"invisible", _("Invisible"), FALSE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
-	status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE,
-			"busy", _("Busy"), TRUE, TRUE, FALSE);
-	types = g_list_append(types, status);
+	status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE,
+			"busy", _("Busy"), TRUE, TRUE, FALSE);
+	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
 			"offline", _("Offline"), FALSE, TRUE, FALSE);
@@ -1096,7 +1096,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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/sametime/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -32,7 +32,7 @@
 
 
 AM_CFLAGS = \
-	$(GLIB_CFLAGS) $(MEANWHILE_CFLAGS) \
+	$(GLIB_CFLAGS) $(MEANWHILE_CFLAGS) $(FARSIGHT_CFLAGS) \
 	$(DEBUG_CFLAGS) \
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple
--- a/libpurple/protocols/sametime/sametime.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Thu Nov 06 03:20:05 2008 +0000
@@ -5188,7 +5188,8 @@
   .new_xfer                  = mw_prpl_new_xfer,
   .offline_message           = NULL,
   .whiteboard_prpl_ops       = NULL,
-  .send_raw                  = NULL
+  .send_raw                  = NULL,
+  .struct_size               = sizeof(PurplePluginProtocolInfo)		
 };
 
 
--- a/libpurple/protocols/silc/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/silc/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -25,7 +25,7 @@
 noinst_LIBRARIES =
 
 libsilcpurple_la_SOURCES = $(SILCSOURCES)
-libsilcpurple_la_LIBADD  = $(GLIB_LIBS) $(SILC_LIBS)
+libsilcpurple_la_LIBADD  = $(GLIB_LIBS) $(SILC_LIBS) $(FARSIGHT_LIBS)
 
 endif
 
@@ -33,4 +33,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/silc/silc.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/silc/silc.c	Thu Nov 06 03:20:05 2008 +0000
@@ -2103,13 +2103,13 @@
 	&silcpurple_wb_ops,			/* whiteboard_prpl_ops */
 	NULL,					/* send_raw */
 	NULL,				        /* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
+	NULL,				        /* unregister_user */
+	NULL,				        /* send_attention */
+	NULL,				        /* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,				        /* get_account_text_table */
+	NULL,				        /* initiate_media */
+	NULL                        /* can_do_media */
 };
 
 static PurplePluginInfo info =
@@ -2243,3 +2243,4 @@
 }
 
 PURPLE_INIT_PLUGIN(silc, init_plugin, info);
+
--- a/libpurple/protocols/silc10/silc.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/silc10/silc.c	Thu Nov 06 03:20:05 2008 +0000
@@ -1836,12 +1836,13 @@
 	&silcpurple_wb_ops,			/* whiteboard_prpl_ops */
 	NULL,                       /* send_raw */
 	NULL,                       /* roomlist_room_serialize */
-
-	NULL,
-	NULL,
-	NULL,
+	NULL,                       /* unregister_user */
+	NULL,                       /* send_attention */
+	NULL,                       /* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,                       /* get_account_text_table */
+	NULL,                       /* initiate_media */
+	NULL                       /* can_do_media */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/simple/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/simple/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -33,4 +33,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/simple/simple.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/simple/simple.c	Thu Nov 06 03:20:05 2008 +0000
@@ -2069,13 +2069,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 */
 };
 
 
@@ -2141,3 +2141,4 @@
 }
 
 PURPLE_INIT_PLUGIN(simple, _init_plugin, info);
+
--- a/libpurple/protocols/yahoo/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/yahoo/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -53,4 +53,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/yahoo/yahoo.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Thu Nov 06 03:20:05 2008 +0000
@@ -4421,6 +4421,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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/zephyr/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -106,5 +106,9 @@
 	-I$(top_srcdir)/libpurple/protocols \
 	-DCONFDIR=\"$(sysconfdir)\" \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(KRB4_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/zephyr/zephyr.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/protocols/zephyr/zephyr.c	Thu Nov 06 03:20:05 2008 +0000
@@ -2915,7 +2915,9 @@
 	NULL,
 	NULL,
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,					/* get_account_text_table */
+	NULL,					/* initate_media */
+	NULL					/* can_do_media */
 };
 
 static PurplePluginInfo info = {
--- a/libpurple/prpl.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/prpl.c	Thu Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/prpl.h	Thu Nov 06 03:20:05 2008 +0000
@@ -65,6 +65,7 @@
 #include "conversation.h"
 #include "ft.h"
 #include "imgstore.h"
+#include "media.h"
 #include "notify.h"
 #include "proxy.h"
 #include "plugin.h"
@@ -72,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
@@ -404,7 +404,7 @@
 	 * reasons.
 	 */
 	void (*unregister_user)(PurpleAccount *, PurpleAccountUnregistrationCb cb, void *user_data);
-	
+
 	/* Attention API for sending & receiving zaps/nudges/buzzes etc. */
 	gboolean (*send_attention)(PurpleConnection *gc, const char *username, guint type);
 	GList *(*get_attention_types)(PurpleAccount *acct);
@@ -440,6 +440,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) \
@@ -728,6 +750,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/server.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/server.c	Thu Nov 06 03:20:05 2008 +0000
@@ -37,6 +37,9 @@
 #include "server.h"
 #include "status.h"
 #include "util.h"
+#ifdef USE_VV
+#include "media.h"
+#endif
 
 #define SECS_BEFORE_RESENDING_AUTORESPONSE 600
 #define SEX_BEFORE_RESENDING_AUTORESPONSE "Only after you're married"
@@ -979,3 +982,4 @@
 		}
 	}
 }
+
--- a/libpurple/xmlnode.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/xmlnode.c	Thu Nov 06 03:20:05 2008 +0000
@@ -307,6 +307,12 @@
 	return node->prefix;
 }
 
+xmlnode *xmlnode_get_parent(const xmlnode *child)
+{
+	g_return_val_if_fail(child != NULL, NULL);
+	return child->parent;
+}
+
 void
 xmlnode_free(xmlnode *node)
 {
--- a/libpurple/xmlnode.h	Mon Nov 03 20:36:38 2008 +0000
+++ b/libpurple/xmlnode.h	Thu Nov 06 03:20:05 2008 +0000
@@ -246,6 +246,15 @@
 const char *xmlnode_get_prefix(xmlnode *node);
 
 /**
+ * Gets the parent node.
+ *
+ * @param child The child node.
+ *
+ * @return The parent or NULL.
+ */
+xmlnode *xmlnode_get_parent(const xmlnode *child);
+
+/**
  * Returns the node in a string of xml.
  *
  * @param node The starting node to output.
--- a/pidgin/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -100,6 +100,7 @@
 	gtkimhtmltoolbar.c \
 	gtklog.c \
 	gtkmain.c \
+	gtkmedia.c \
 	gtkmenutray.c \
 	gtknotify.c \
 	gtkplugin.c \
@@ -152,6 +153,7 @@
 	gtkimhtml.h \
 	gtkimhtmltoolbar.h \
 	gtklog.h \
+	gtkmedia.c \
 	gtkmenutray.h \
 	gtknickcolors.h \
 	gtknotify.h \
@@ -198,6 +200,8 @@
 	$(STARTUP_NOTIFICATION_LIBS) \
 	$(LIBXML_LIBS) \
 	$(GTK_LIBS) \
+	$(FARSIGHT_LIBS) \
+	$(GSTPROPS_LIBS) \
 	$(top_builddir)/libpurple/libpurple.la
 
 if USE_INTERNAL_LIBGADU
@@ -222,5 +226,7 @@
 	$(GTKSPELL_CFLAGS) \
 	$(STARTUP_NOTIFICATION_CFLAGS) \
 	$(LIBXML_CFLAGS) \
-	$(INTGG_CFLAGS)
+	$(INTGG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTPROPS_CFLAGS)
 endif  # ENABLE_GTK
--- a/pidgin/gtkconv.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/gtkconv.c	Thu Nov 06 03:20:05 2008 +0000
@@ -48,6 +48,7 @@
 #include "idle.h"
 #include "imgstore.h"
 #include "log.h"
+#include "mediamanager.h"
 #include "notify.h"
 #include "prpl.h"
 #include "request.h"
@@ -62,6 +63,7 @@
 #include "gtkimhtml.h"
 #include "gtkimhtmltoolbar.h"
 #include "gtklog.h"
+#include "gtkmedia.h"
 #include "gtkmenutray.h"
 #include "gtkpounce.h"
 #include "gtkprefs.h"
@@ -1197,6 +1199,18 @@
 	gtk_widget_grab_focus(s->entry);
 }
 
+#ifdef USE_VV
+/* Forward declare this here, because I want to keep VV-related stuff together
+for now */
+static void 
+menu_initiate_audio_call_cb(gpointer data, guint action, GtkWidget *widget);
+static void 
+menu_initiate_video_call_cb(gpointer data, guint action, GtkWidget *widget);
+static void 
+menu_initiate_audio_video_call_cb(gpointer data, guint action, GtkWidget *widget);
+
+#endif
+
 static void
 menu_send_file_cb(gpointer data, guint action, GtkWidget *widget)
 {
@@ -3117,6 +3131,17 @@
 
 	{ "/Conversation/sep1", NULL, NULL, 0, "<Separator>", NULL },
 
+#ifdef USE_VV
+	{ N_("/Conversation/M_edia"), NULL, NULL, 0, "<Branch>", NULL },
+
+	{ N_("/Conversation/Media/_Audio Call"), NULL, menu_initiate_audio_call_cb, 0,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL },
+	{ N_("/Conversation/Media/_Video Call"), NULL, menu_initiate_video_call_cb, 0,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL },
+	{ N_("/Conversation/Media/Audio\\/Video _Call"), NULL, menu_initiate_audio_video_call_cb, 0,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL },
+#endif
+
 	{ N_("/Conversation/Se_nd File..."), NULL, menu_send_file_cb, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_FILE },
 	{ N_("/Conversation/Add Buddy _Pounce..."), NULL, menu_add_pounce_cb,
 			0, "<Item>", NULL },
@@ -3427,6 +3452,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 =
@@ -4774,7 +4811,7 @@
 static GtkWidget *
 setup_common_pane(PidginConversation *gtkconv)
 {
-	GtkWidget *vbox, *frame, *imhtml_sw, *event_box;
+	GtkWidget *vbox, *hpaned, *frame, *imhtml_sw, *event_box;
 	GtkCellRenderer *rend;
 	GtkTreePath *path;
 	PurpleConversation *conv = gtkconv->active_conv;
@@ -4784,7 +4821,7 @@
 	int buddyicon_size = 0;
 
 	/* Setup the top part of the pane */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtkconv->topvbox = vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtk_widget_show(vbox);
 
 	/* Setup the info pane */
@@ -4854,23 +4891,21 @@
 	/* Setup the gtkimhtml widget */
 	frame = pidgin_create_imhtml(FALSE, &gtkconv->imhtml, NULL, &imhtml_sw);
 	gtk_widget_set_size_request(gtkconv->imhtml, -1, 0);
+
+	/* Add the gtkimhtml frame */
+	gtkconv->middle_hpaned = hpaned = gtk_hpaned_new();
+	gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
+	gtk_widget_show(hpaned);
+	gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE);
+
 	if (chat) {
-		GtkWidget *hpaned;
-
 		/* Add the topic */
 		setup_chat_topic(gtkconv, vbox);
 
-		/* Add the gtkimhtml frame */
-		hpaned = gtk_hpaned_new();
-		gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
-		gtk_widget_show(hpaned);
-		gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE);
-
 		/* Now add the userlist */
 		setup_chat_userlist(gtkconv, hpaned);
-	} else {
-		gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
-	}
+	}
+
 	gtk_widget_show(frame);
 
 	gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml");
@@ -6409,6 +6444,37 @@
 		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
+					&& gtkconv->gtkmedia == NULL) {
+			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));
@@ -6946,7 +7012,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);
 
@@ -7689,6 +7755,110 @@
 	return TRUE;
 }
 
+
+#ifdef USE_VV
+
+static void
+pidgin_gtkmedia_message_cb(PidginMedia *media, const char *msg, PurpleConversation *conv)
+{
+	purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL));
+}
+
+static void
+menu_initiate_audio_call_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = (PidginWindow *)data;
+	PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win);
+	PurpleAccount *account = purple_conversation_get_account(conv);
+
+	PurpleMedia *media =
+		purple_prpl_initiate_media(account,
+					   purple_conversation_get_name(conv),
+					   PURPLE_MEDIA_AUDIO);
+
+	if (media)
+		purple_media_wait(media);
+}
+
+static void
+menu_initiate_video_call_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = (PidginWindow *)data;
+	PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win);
+	PurpleAccount *account = purple_conversation_get_account(conv);
+
+	PurpleMedia *media =
+		purple_prpl_initiate_media(account,
+					   purple_conversation_get_name(conv),
+					   PURPLE_MEDIA_VIDEO);
+
+	if (media)
+		purple_media_wait(media);
+}
+
+static void
+menu_initiate_audio_video_call_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = (PidginWindow *)data;
+	PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win);
+	PurpleAccount *account = purple_conversation_get_account(conv);
+
+	PurpleMedia *media =
+		purple_prpl_initiate_media(account,
+					   purple_conversation_get_name(conv),
+					   PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO);
+
+	if (media)
+		purple_media_wait(media);
+}
+
+static void
+pidgin_conv_gtkmedia_destroyed(GtkWidget *widget, PidginConversation *gtkconv)
+{
+	gtk_widget_destroyed(widget, &(gtkconv->gtkmedia));
+	gray_stuff_out(gtkconv);
+}
+
+static gboolean
+pidgin_conv_new_media_cb(PurpleMediaManager *manager, PurpleMedia *media, gpointer nul)
+{
+	GtkWidget *gtkmedia;
+	PurpleConversation *conv;
+	PidginConversation *gtkconv;
+	gchar *name = purple_media_get_screenname(media);
+
+	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
+			purple_connection_get_account(
+			purple_media_get_connection(media)), name);
+	g_free(name);
+
+	gtkconv = PIDGIN_CONVERSATION(conv);
+
+	if (gtkconv->gtkmedia) {
+		purple_debug_info("gtkconv", "Media session exists for this conversation.\n");
+		return FALSE;
+	}
+
+	gtkmedia = pidgin_media_new(media);
+	g_object_unref(media);
+
+	gtk_box_pack_start(GTK_BOX(gtkconv->topvbox), gtkmedia, FALSE, FALSE, 0);
+	gtk_widget_show(gtkmedia);
+	g_signal_connect(G_OBJECT(gtkmedia), "message", G_CALLBACK(pidgin_gtkmedia_message_cb), conv);
+
+	gtkconv->gtkmedia = gtkmedia;
+	g_signal_connect(G_OBJECT(gtkmedia), "destroy", G_CALLBACK(
+			pidgin_conv_gtkmedia_destroyed), gtkconv);
+
+	gtk_paned_pack2(GTK_PANED(gtkconv->middle_hpaned),
+			pidgin_media_get_display_widget(gtkmedia), FALSE, TRUE);
+
+	gray_stuff_out(gtkconv);
+	return TRUE;
+}
+
+#endif
+
 void *
 pidgin_conversations_get_handle(void)
 {
@@ -7789,7 +7959,10 @@
 								show_protocol_icons_pref_cb, NULL);
 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/hide_new",
                                 hide_new_pref_cb, NULL);
-
+#ifdef USE_VV
+	g_signal_connect(G_OBJECT(purple_media_manager_get()), "init-media",
+			 G_CALLBACK(pidgin_conv_new_media_cb), NULL);
+#endif
 
 
 	/**********************************************************************
--- a/pidgin/gtkconv.h	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/gtkconv.h	Thu Nov 06 03:20:05 2008 +0000
@@ -150,6 +150,7 @@
 	gpointer depr1;
 #endif
 
+	GtkWidget *middle_hpaned;
 	GtkWidget *lower_hbox;
 
 	GtkWidget *toolbar;
@@ -169,6 +170,8 @@
 	GtkWidget *infopane;
 	GtkListStore *infopane_model;
 	GtkTreeIter infopane_iter;
+	GtkWidget *topvbox;
+	GtkWidget *gtkmedia;
 
 	/* Used when attaching a PidginConversation to a PurpleConversation
 	 * with message history */
--- a/pidgin/gtkconvwin.h	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/gtkconvwin.h	Thu Nov 06 03:20:05 2008 +0000
@@ -49,7 +49,11 @@
 		GtkWidget *menubar;
 
 		GtkWidget *view_log;
-
+#ifdef USE_VV
+		GtkWidget *audio_call;
+		GtkWidget *video_call;
+		GtkWidget *audio_video_call;
+#endif
 		GtkWidget *send_file;
 		GtkWidget *add_pounce;
 		GtkWidget *get_info;
--- a/pidgin/gtkdebug.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/gtkdebug.c	Thu Nov 06 03:20:05 2008 +0000
@@ -985,6 +985,13 @@
 #ifdef USE_GSTREAMER
 	REGISTER_G_LOG_HANDLER("GStreamer");
 #endif
+#ifdef USE_VV
+#ifdef USE_FARSIGHT
+	REGISTER_G_LOG_HANDLER("farsight");
+	REGISTER_G_LOG_HANDLER("farsight-transmitter");
+	REGISTER_G_LOG_HANDLER("farsight-rtp");
+#endif
+#endif
 
 #ifdef _WIN32
 	if (!purple_debug_is_enabled())
--- a/pidgin/gtkdialogs.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/gtkdialogs.c	Thu Nov 06 03:20:05 2008 +0000
@@ -632,6 +632,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	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Thu Nov 06 03:20:05 2008 +0000
@@ -40,6 +40,8 @@
 #include "gtkthemes.h"
 #include "gtkutils.h"
 
+#include "debug.h"
+
 #include <gdk/gdkkeysyms.h>
 
 static GtkHBoxClass *parent_class = NULL;
@@ -449,12 +451,12 @@
 
 static void insert_hr_cb(GtkWidget *widget, GtkIMHtmlToolbar *toolbar)
 {
-        GtkTextIter iter;
-        GtkTextMark *ins;
+	GtkTextIter iter;
+	GtkTextMark *ins;
 	GtkIMHtmlScalable *hr;
 
-        ins = gtk_text_buffer_get_insert(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)));
-        gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)), &iter, ins);
+	ins = gtk_text_buffer_get_insert(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)));
+	gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)), &iter, ins);
 	hr = gtk_imhtml_hr_new();
 	gtk_imhtml_hr_add_to(hr, GTK_IMHTML(toolbar->imhtml), &iter);
 }
@@ -1297,6 +1299,7 @@
 	GtkWidget *insert_button;
 	GtkWidget *font_button;
 	GtkWidget *smiley_button;
+
 	GtkWidget *font_menu;
 	GtkWidget *insert_menu;
 	GtkWidget *menuitem;
--- a/pidgin/gtkimhtmltoolbar.h	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/gtkimhtmltoolbar.h	Thu Nov 06 03:20:05 2008 +0000
@@ -76,6 +76,7 @@
 	char *sml;
 	GtkWidget *strikethrough;
 	GtkWidget *insert_hr;
+	GtkWidget *call;
 };
 
 struct _GtkIMHtmlToolbarClass {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkmedia.c	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,606 @@
+/**
+ * @file media.c Account API
+ * @ingroup core
+ *
+ * Pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <string.h>
+#include "debug.h"
+#include "internal.h"
+#include "connection.h"
+#include "media.h"
+#include "pidgin.h"
+
+#include "gtkmedia.h"
+
+#ifdef USE_VV
+
+#include <gst/interfaces/xoverlay.h>
+
+typedef enum
+{
+	/* Waiting for response */
+	PIDGIN_MEDIA_WAITING = 1,
+	/* Got request */
+	PIDGIN_MEDIA_REQUESTED,
+	/* Accepted call */
+	PIDGIN_MEDIA_ACCEPTED,
+	/* Rejected call */
+	PIDGIN_MEDIA_REJECTED,
+} PidginMediaState;
+
+struct _PidginMediaPrivate
+{
+	PurpleMedia *media;
+	GstElement *send_level;
+	GstElement *recv_level;
+
+	GtkWidget *calling;
+	GtkWidget *accept;
+	GtkWidget *reject;
+	GtkWidget *hangup;
+	GtkWidget *mute;
+
+	GtkWidget *send_progress;
+	GtkWidget *recv_progress;
+
+	PidginMediaState state;
+
+	GtkWidget *display;
+	GtkWidget *local_video;
+	GtkWidget *remote_video;
+};
+
+#define PIDGIN_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PIDGIN_TYPE_MEDIA, PidginMediaPrivate))
+
+static void pidgin_media_class_init (PidginMediaClass *klass);
+static void pidgin_media_init (PidginMedia *media);
+static void pidgin_media_finalize (GObject *object);
+static void pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static void pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state);
+
+static GtkHBoxClass *parent_class = NULL;
+
+
+
+enum {
+	MESSAGE,
+	LAST_SIGNAL
+};
+static guint pidgin_media_signals[LAST_SIGNAL] = {0};
+
+enum {
+	PROP_0,
+	PROP_MEDIA,
+	PROP_SEND_LEVEL,
+	PROP_RECV_LEVEL
+};
+
+GType
+pidgin_media_get_type(void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PidginMediaClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) pidgin_media_class_init,
+			NULL,
+			NULL,
+			sizeof(PidginMedia),
+			0,
+			(GInstanceInitFunc) pidgin_media_init,
+			NULL
+		};
+		type = g_type_register_static(GTK_TYPE_HBOX, "PidginMedia", &info, 0);
+	}
+	return type;
+}
+
+
+static void
+pidgin_media_class_init (PidginMediaClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+/*	GtkContainerClass *container_class = (GtkContainerClass*)klass; */
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = pidgin_media_finalize;
+	gobject_class->set_property = pidgin_media_set_property;
+	gobject_class->get_property = pidgin_media_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_MEDIA,
+			g_param_spec_object("media",
+			"PurpleMedia",
+			"The PurpleMedia associated with this media.",
+			PURPLE_TYPE_MEDIA,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+	g_object_class_install_property(gobject_class, PROP_SEND_LEVEL,
+			g_param_spec_object("send-level",
+			"Send level",
+			"The GstElement of this media's send 'level'",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+	g_object_class_install_property(gobject_class, PROP_RECV_LEVEL,
+			g_param_spec_object("recv-level",
+			"Receive level",
+			"The GstElement of this media's recv 'level'",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+
+	pidgin_media_signals[MESSAGE] = g_signal_new("message", G_TYPE_FROM_CLASS(klass),
+					G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					g_cclosure_marshal_VOID__STRING,
+					G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	g_type_class_add_private(klass, sizeof(PidginMediaPrivate));
+}
+
+static void
+pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media)
+{
+	purple_media_mute(media->priv->media,
+			gtk_toggle_button_get_active(toggle));
+}
+
+static void
+pidgin_media_init (PidginMedia *media)
+{
+	media->priv = PIDGIN_MEDIA_GET_PRIVATE(media);
+	media->priv->calling = gtk_label_new_with_mnemonic("Calling...");
+	media->priv->hangup = gtk_button_new_with_label("Hangup");
+	media->priv->accept = gtk_button_new_with_label("Accept");
+	media->priv->reject = gtk_button_new_with_label("Reject");
+	media->priv->mute = gtk_toggle_button_new_with_label("Mute");
+
+	g_signal_connect(media->priv->mute, "toggled",
+			G_CALLBACK(pidgin_media_mute_toggled), media);
+
+	gtk_box_pack_start(GTK_BOX(media), media->priv->calling, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(media), media->priv->hangup, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(media), media->priv->accept, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(media), media->priv->reject, FALSE, FALSE, 0);
+	gtk_box_pack_end(GTK_BOX(media), media->priv->mute, FALSE, FALSE, 0);
+
+	gtk_widget_show_all(media->priv->accept);
+	gtk_widget_show_all(media->priv->reject);
+
+	media->priv->display = gtk_vbox_new(TRUE, PIDGIN_HIG_BOX_SPACE);
+}
+
+static gboolean
+level_message_cb(GstBus *bus, GstMessage *message, PidginMedia *gtkmedia)
+{
+	const GstStructure *s;
+	gchar *name;
+
+	gdouble rms_db;
+	gdouble percent;
+	const GValue *list;
+	const GValue *value;
+
+	GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(message));
+
+	if (message->type != GST_MESSAGE_ELEMENT)
+		return TRUE;
+
+	s = gst_message_get_structure(message);
+
+	if (strcmp(gst_structure_get_name(s), "level"))
+		return TRUE;
+
+	list = gst_structure_get_value(s, "rms");
+
+	/* Only bother with the first channel. */
+	value = gst_value_list_get_value(list, 0);
+	rms_db = g_value_get_double(value);
+
+	percent = pow(10, rms_db / 20) * 5;
+
+	if(percent > 1.0)
+		percent = 1.0;
+
+	name = gst_element_get_name(src);
+	if (!strcmp(name, "sendlevel"))	
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gtkmedia->priv->send_progress), percent);
+	else
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gtkmedia->priv->recv_progress), percent);
+
+	g_free(name);
+	return TRUE;
+}
+
+
+static void
+pidgin_media_disconnect_levels(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	GstElement *element = purple_media_get_pipeline(media);
+	gulong handler_id = g_signal_handler_find(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))),
+						  G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, 
+						  NULL, G_CALLBACK(level_message_cb), gtkmedia);
+	if (handler_id)
+		g_signal_handler_disconnect(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))),
+					    handler_id);
+}
+
+static void
+pidgin_media_finalize (GObject *media)
+{
+	PidginMedia *gtkmedia = PIDGIN_MEDIA(media);
+	purple_debug_info("gtkmedia", "pidgin_media_finalize\n");
+	if (gtkmedia->priv->media) {
+		pidgin_media_disconnect_levels(gtkmedia->priv->media, gtkmedia);
+		g_object_unref(gtkmedia->priv->media);
+	}
+	if (gtkmedia->priv->send_level)
+		gst_object_unref(gtkmedia->priv->send_level);
+	if (gtkmedia->priv->recv_level)
+		gst_object_unref(gtkmedia->priv->recv_level);
+	if (gtkmedia->priv->display)
+		gtk_widget_destroy(gtkmedia->priv->display);
+}
+
+static void
+pidgin_media_emit_message(PidginMedia *gtkmedia, const char *msg)
+{
+	g_signal_emit(gtkmedia, pidgin_media_signals[MESSAGE], 0, msg);
+}
+
+GtkWidget *
+pidgin_media_get_display_widget(GtkWidget *gtkmedia)
+{
+	return PIDGIN_MEDIA_GET_PRIVATE(gtkmedia)->display;
+}
+
+static gboolean
+create_window (GstBus *bus, GstMessage *message, PidginMedia *gtkmedia)
+{
+	char *name;
+
+	if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT)
+		return TRUE;
+
+	if (!gst_structure_has_name(message->structure, "prepare-xwindow-id"))
+		return TRUE;
+
+	name = gst_object_get_name(GST_MESSAGE_SRC (message));
+	purple_debug_info("gtkmedia", "prepare-xwindow-id object name: %s\n", name);
+
+	/* The XOverlay's name is the sink's name with a suffix */
+	if (!strncmp(name, "purplevideosink", strlen("purplevideosink")))
+		gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(message)),
+					     GDK_WINDOW_XWINDOW(gtkmedia->priv->remote_video->window));
+	else if (!strncmp(name, "purplelocalvideosink", strlen("purplelocalvideosink")))
+		gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(message)),
+					     GDK_WINDOW_XWINDOW(gtkmedia->priv->local_video->window));
+	g_free(name);
+	return TRUE;
+}
+
+static void
+pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	GstElement *pipeline = purple_media_get_pipeline(media);
+	GtkWidget *send_widget = NULL, *recv_widget = NULL;
+	GstElement *audiosendbin = NULL, *audiosendlevel = NULL;
+	GstElement *videosendbin = NULL;
+	gboolean audiorecvbool = FALSE;
+	gboolean videorecvbool = FALSE;
+	GstBus *bus;
+
+	GList *sessions = purple_media_get_session_names(media);
+
+	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+		PurpleMediaSessionType type = purple_media_get_session_type(media, sessions->data);
+		if (type & PURPLE_MEDIA_AUDIO) {
+			if (!audiosendbin && (type & PURPLE_MEDIA_SEND_AUDIO)) {
+				purple_media_audio_init_src(&audiosendbin, &audiosendlevel);
+				purple_media_set_src(media, sessions->data, audiosendbin);
+				gst_element_set_state(audiosendbin, GST_STATE_PLAYING);
+			}
+			if (!audiorecvbool && (type & PURPLE_MEDIA_RECV_AUDIO)) {
+				audiorecvbool = TRUE;
+			}
+		} else if (type & PURPLE_MEDIA_VIDEO) {
+			if (!videosendbin && (type & PURPLE_MEDIA_SEND_VIDEO)) {
+				purple_media_video_init_src(&videosendbin);
+				purple_media_set_src(media, sessions->data, videosendbin);
+				gst_element_set_state(videosendbin, GST_STATE_PLAYING);
+			}
+			if (!videorecvbool && (type & PURPLE_MEDIA_RECV_VIDEO)) {
+				videorecvbool = TRUE;
+			}
+		}
+	}
+
+	if (videorecvbool || audiorecvbool) {
+		recv_widget = gtk_hbox_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);
+	}
+	if (videosendbin || audiosendbin) {
+		send_widget = gtk_hbox_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);
+	}
+
+	if (videorecvbool) {
+		GtkWidget *aspect;
+		GtkWidget *remote_video;
+
+		aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE);
+		gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
+		gtk_box_pack_start(GTK_BOX(recv_widget), aspect, TRUE, TRUE, 0);
+
+		remote_video = gtk_drawing_area_new();
+		gtk_container_add(GTK_CONTAINER(aspect), remote_video);
+		gtk_widget_set_size_request (GTK_WIDGET(remote_video), 100, -1);
+		gtk_widget_show(remote_video);
+		gtk_widget_show(aspect);
+
+		gtkmedia->priv->remote_video = remote_video;
+	}
+	if (videosendbin) {
+		GtkWidget *aspect;
+		GtkWidget *local_video;
+
+		aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE);
+		gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
+		gtk_box_pack_start(GTK_BOX(send_widget), aspect, TRUE, TRUE, 0);
+
+		local_video = gtk_drawing_area_new();
+		gtk_container_add(GTK_CONTAINER(aspect), local_video);
+		gtk_widget_set_size_request (GTK_WIDGET(local_video), 100, -1);
+		gtk_widget_show(local_video);
+		gtk_widget_show(aspect);
+
+		gtkmedia->priv->local_video = local_video;
+	}
+
+	if (audiorecvbool) {
+		gtkmedia->priv->recv_progress = gtk_progress_bar_new();
+		gtk_widget_set_size_request(gtkmedia->priv->recv_progress, 10, 70);
+		gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(gtkmedia->priv->recv_progress),
+						 GTK_PROGRESS_BOTTOM_TO_TOP);
+		gtk_box_pack_end(GTK_BOX(recv_widget),
+				   gtkmedia->priv->recv_progress, FALSE, FALSE, 0);
+		gtk_widget_show(gtkmedia->priv->recv_progress);
+	}
+	if (audiosendbin) {
+		gtkmedia->priv->send_progress = gtk_progress_bar_new();
+		gtk_widget_set_size_request(gtkmedia->priv->send_progress, 10, 70);
+		gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(gtkmedia->priv->send_progress),
+						 GTK_PROGRESS_BOTTOM_TO_TOP);
+		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);
+	}
+
+	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
+
+	if (videorecvbool || videosendbin)
+		gst_bus_set_sync_handler(bus,
+				(GstBusSyncHandler)create_window, gtkmedia);
+
+	if (audiorecvbool || audiosendbin)
+		g_signal_connect(G_OBJECT(bus), "message::element",
+				G_CALLBACK(level_message_cb), gtkmedia);
+
+	gst_object_unref(bus);
+}
+
+static void
+pidgin_media_wait_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_WAITING);
+}
+
+/* maybe we should have different callbacks for when we received the accept
+    and we accepted ourselves */
+static void
+pidgin_media_accept_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	GstElement *audiosendbin = NULL;
+	GstElement *audiorecvbin = NULL;
+	GstElement *videosendbin = NULL;
+	GstElement *videorecvbin = NULL;
+
+	pidgin_media_emit_message(gtkmedia, _("Call in progress."));
+	pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED);
+
+	purple_media_get_elements(media, &audiosendbin, &audiorecvbin,
+				  &videosendbin, &videorecvbin);
+
+	if (audiorecvbin || audiosendbin || videorecvbin || videosendbin)
+		gtk_widget_show(gtkmedia->priv->display);
+}
+
+static void
+pidgin_media_hangup_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	pidgin_media_emit_message(gtkmedia, _("You have ended the call."));
+	gtk_widget_destroy(GTK_WIDGET(gtkmedia));
+}
+
+static void
+pidgin_media_got_request_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	PurpleMediaSessionType type = purple_media_get_overall_type(media);
+	gchar *message;
+	gchar *name = purple_media_get_screenname(media);
+
+	if (type & PURPLE_MEDIA_AUDIO && type & PURPLE_MEDIA_VIDEO) {
+		message = g_strdup_printf(_("%s wishes to start an audio/video session with you."),
+					  name);
+	} else if (type & PURPLE_MEDIA_AUDIO) {
+		message = g_strdup_printf(_("%s wishes to start an audio session with you."),
+					  name);
+	} else if (type & PURPLE_MEDIA_VIDEO) {
+		message = g_strdup_printf(_("%s wishes to start a video session with you."),
+					  name);
+	} else {
+		g_free(name);
+		return;
+	}
+
+	g_free(name);
+	pidgin_media_emit_message(gtkmedia, message);
+	g_free(message);
+}
+
+static void
+pidgin_media_got_hangup_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	pidgin_media_emit_message(gtkmedia, _("The call has been terminated."));
+	gtk_widget_destroy(GTK_WIDGET(gtkmedia));
+}
+
+static void
+pidgin_media_reject_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	pidgin_media_emit_message(gtkmedia, _("You have rejected the call."));
+	gtk_widget_destroy(GTK_WIDGET(gtkmedia));
+}
+
+static void
+pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	PidginMedia *media;
+	g_return_if_fail(PIDGIN_IS_MEDIA(object));
+
+	media = PIDGIN_MEDIA(object);
+	switch (prop_id) {
+		case PROP_MEDIA:
+			if (media->priv->media)
+				g_object_unref(media->priv->media);
+			media->priv->media = g_value_get_object(value);
+			g_object_ref(media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->accept), "clicked", 
+				 G_CALLBACK(purple_media_accept), media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->reject), "clicked",
+				 G_CALLBACK(purple_media_reject), media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->hangup), "clicked",
+				 G_CALLBACK(purple_media_hangup), media->priv->media);
+
+			g_signal_connect(G_OBJECT(media->priv->media), "accepted",
+				G_CALLBACK(pidgin_media_accept_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media) ,"ready",
+				G_CALLBACK(pidgin_media_ready_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media) ,"wait",
+				G_CALLBACK(pidgin_media_wait_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "hangup",
+				G_CALLBACK(pidgin_media_hangup_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "reject",
+				G_CALLBACK(pidgin_media_reject_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-request",
+				G_CALLBACK(pidgin_media_got_request_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-hangup",
+				G_CALLBACK(pidgin_media_got_hangup_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-accept",
+				G_CALLBACK(pidgin_media_accept_cb), media);
+			break;
+		case PROP_SEND_LEVEL:
+			if (media->priv->send_level)
+				gst_object_unref(media->priv->send_level);
+			media->priv->send_level = g_value_get_object(value);
+			g_object_ref(media->priv->send_level);
+			break;
+		case PROP_RECV_LEVEL:
+			if (media->priv->recv_level)
+				gst_object_unref(media->priv->recv_level);
+			media->priv->recv_level = g_value_get_object(value);
+			g_object_ref(media->priv->recv_level);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	PidginMedia *media;
+	g_return_if_fail(PIDGIN_IS_MEDIA(object));
+
+	media = PIDGIN_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_MEDIA:
+			g_value_set_object(value, media->priv->media);
+			break;
+		case PROP_SEND_LEVEL:
+			g_value_set_object(value, media->priv->send_level);
+			break;
+		case PROP_RECV_LEVEL:
+			g_value_set_object(value, media->priv->recv_level);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+GtkWidget *
+pidgin_media_new(PurpleMedia *media)
+{
+	PidginMedia *gtkmedia = g_object_new(pidgin_media_get_type(),
+					     "media", media, NULL);
+	return GTK_WIDGET(gtkmedia);
+}
+
+static void
+pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state)
+{
+	gtkmedia->priv->state = state;
+	switch (state) {
+		case PIDGIN_MEDIA_WAITING:
+			gtk_widget_show(gtkmedia->priv->calling);
+			gtk_widget_hide(gtkmedia->priv->accept);
+			gtk_widget_hide(gtkmedia->priv->reject);
+			gtk_widget_show(gtkmedia->priv->hangup);
+			break;
+		case PIDGIN_MEDIA_REQUESTED:
+			gtk_widget_hide(gtkmedia->priv->calling);
+			gtk_widget_show(gtkmedia->priv->accept);
+			gtk_widget_show(gtkmedia->priv->reject);
+			gtk_widget_hide(gtkmedia->priv->hangup);
+			break;
+		case PIDGIN_MEDIA_ACCEPTED:
+			gtk_widget_show(gtkmedia->priv->hangup);
+			gtk_widget_hide(gtkmedia->priv->calling);
+			gtk_widget_hide(gtkmedia->priv->accept);
+			gtk_widget_hide(gtkmedia->priv->reject);
+			break;
+		default:
+			break;
+	}
+}
+
+#endif  /* USE_VV */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkmedia.h	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,70 @@
+/**
+ * @file media.h Account API
+ * @ingroup core
+ *
+ * Pidgin 
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __GTKMEDIA_H_
+#define __GTKMEDIA_H_
+
+#ifdef USE_VV
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+
+#include "connection.h"
+
+G_BEGIN_DECLS
+
+#define PIDGIN_TYPE_MEDIA            (pidgin_media_get_type())
+#define PIDGIN_MEDIA(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PIDGIN_TYPE_MEDIA, PidginMedia))
+#define PIDGIN_MEDIA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PIDGIN_TYPE_MEDIA, PidginMediaClass))
+#define PIDGIN_IS_MEDIA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PIDGIN_TYPE_MEDIA))
+#define PIDGIN_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PIDGIN_TYPE_MEDIA))
+#define PIDGIN_MEDIA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PIDGIN_TYPE_MEDIA, PidginMediaClass))
+
+typedef struct _PidginMedia PidginMedia;
+typedef struct _PidginMediaClass PidginMediaClass;
+typedef struct _PidginMediaPrivate PidginMediaPrivate;
+
+struct _PidginMediaClass
+{
+	GtkHBoxClass parent_class;
+};
+
+struct _PidginMedia
+{
+	GtkHBox parent;
+	PidginMediaPrivate *priv;
+};
+
+GType pidgin_media_get_type(void);
+
+GtkWidget *pidgin_media_new(PurpleMedia *media);
+GtkWidget *pidgin_media_get_display_widget(GtkWidget *gtkmedia);
+
+G_END_DECLS
+
+#endif  /* USE_VV */
+
+
+#endif  /* __GTKMEDIA_H_ */
--- a/pidgin/gtkprefs.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/gtkprefs.c	Thu Nov 06 03:20:05 2008 +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"
@@ -145,12 +148,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 +946,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 +956,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 +991,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:"),
@@ -2042,6 +2043,313 @@
 	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);
+		GList *sessions = purple_media_get_session_names(media);
+		for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+			const gchar *session = sessions->data;
+			if (purple_media_get_session_type(media, session)
+					& PURPLE_MEDIA_SEND_AUDIO) {
+				GstElement *volume = gst_bin_get_by_name(
+						GST_BIN(purple_media_get_src(media, session)),
+						"purpleaudioinputvolume");
+				g_object_set(volume, "volume", val, NULL);
+			}
+		}
+	}
+}
+
+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);
+		GList *sessions = purple_media_get_session_names(media);
+		for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+			const gchar *session = sessions->data;
+			if (purple_media_get_session_type(media, session)
+					& PURPLE_MEDIA_RECV_AUDIO) {
+				GstElement *volume = gst_bin_get_by_name(
+						GST_BIN(purple_media_get_sink(media, session)),
+						"purpleaudiooutputvolume");
+				g_object_set(volume, "volume", val, NULL);
+			}
+		}
+	}
+}
+
+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)
@@ -2167,6 +2475,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 */
@@ -2292,6 +2604,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	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/gtkprefs.h	Thu Nov 06 03:20:05 2008 +0000
@@ -29,6 +29,7 @@
 
 #include "prefs.h"
 
+
 /**
  * Initializes all UI-specific preferences.
  */
--- a/pidgin/pidginstock.c	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/pidginstock.c	Thu Nov 06 03:20:05 2008 +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	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/pidginstock.h	Thu Nov 06 03:20:05 2008 +0000
@@ -152,6 +152,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	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/pixmaps/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -421,6 +421,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 \
@@ -434,7 +435,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	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/plugins/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -123,6 +123,7 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GTK_CFLAGS) \
 	$(PLUGIN_CFLAGS)
 
--- a/pidgin/plugins/cap/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/plugins/cap/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -15,7 +15,7 @@
 
 endif
 
-cap_la_LIBADD = $(GTK_LIBS) $(SQLITE3_LIBS)
+cap_la_LIBADD = $(GTK_LIBS) $(SQLITE3_LIBS) $(FARSIGHT_LIBS) $(GSTPROPS_LIBS)
 
 AM_CPPFLAGS = \
 	-DDATADIR=\"$(datadir)\" \
@@ -24,6 +24,8 @@
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
 	$(GTK_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTPROPS_CFLAGS) \
 	$(SQLITE3_CFLAGS)
 
 EXTRA_DIST = Makefile.mingw
--- a/pidgin/plugins/gestures/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/plugins/gestures/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -23,4 +23,5 @@
 	-I$(top_builddir)/libpurple \
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GTK_CFLAGS)
--- a/pidgin/plugins/gevolution/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/plugins/gevolution/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -26,4 +26,5 @@
 	-I$(top_srcdir)/pidgin \
 	$(EVOLUTION_ADDRESSBOOK_CFLAGS) \
 	$(DEBUG_CFLAGS) \
-	$(GTK_CFLAGS)
+	$(GTK_CFLAGS) \
+	$(FARSIGHT_CFLAGS)
--- a/pidgin/plugins/musicmessaging/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/plugins/musicmessaging/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -40,5 +40,6 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GTK_CFLAGS) \
 	$(DBUS_CFLAGS)
--- a/pidgin/plugins/ticker/Makefile.am	Mon Nov 03 20:36:38 2008 +0000
+++ b/pidgin/plugins/ticker/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -24,4 +24,5 @@
 	-I$(top_builddir)/libpurple \
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GTK_CFLAGS)
--- a/po/POTFILES.in	Mon Nov 03 20:36:38 2008 +0000
+++ b/po/POTFILES.in	Thu Nov 06 03:20:05 2008 +0000
@@ -8,6 +8,7 @@
 finch/gntdebug.c
 finch/gntft.c
 finch/gntlog.c
+finch/gntmedia.c
 finch/gntnotify.c
 finch/gntplugin.c
 finch/gntpounce.c
@@ -86,6 +87,7 @@
 libpurple/protocols/jabber/buddy.c
 libpurple/protocols/jabber/chat.c
 libpurple/protocols/jabber/jabber.c
+libpurple/protocols/jabber/jingle.c
 libpurple/protocols/jabber/libxmpp.c
 libpurple/protocols/jabber/message.c
 libpurple/protocols/jabber/parser.c

mercurial