summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Ruprecht <maiku@pidgin.im>2008-10-19 04:59:51 +0000
committerMichael Ruprecht <maiku@pidgin.im>2008-10-19 04:59:51 +0000
commit148fece14e2a48c28b49e43e48fc769021d592c1 (patch)
tree97809b0f20399e3bb98f20146ccf6da46950eb9a
parenta2940a98da26bafdcefd7d558d85b1fb45415ee2 (diff)
parent863654bdb08f0f21fcd6d81022efbdff42a3c5f2 (diff)
downloadpidgin-148fece14e2a48c28b49e43e48fc769021d592c1.tar.gz
propagate from branch 'im.pidgin.pidgin' (head d19f6dd77700cab8427d85fbeeb1b8a995d3a237)
to branch 'im.pidgin.maiku.vv' (head 74ddb0beeb44b2c0ba7a4b2308bea5417729c09f)
-rw-r--r--ChangeLog.API6
-rw-r--r--Doxyfile.in2
-rw-r--r--configure.ac58
-rw-r--r--finch/Makefile.am4
-rw-r--r--finch/gntaccount.c1
-rw-r--r--finch/gntdebug.c10
-rw-r--r--finch/gntft.c12
-rw-r--r--finch/gntmedia.c428
-rw-r--r--finch/gntmedia.h77
-rw-r--r--finch/gntnotify.c3
-rw-r--r--finch/gntplugin.c5
-rw-r--r--finch/gntpounce.c19
-rw-r--r--finch/gntrequest.c4
-rw-r--r--finch/gntstatus.c7
-rw-r--r--finch/gntui.c14
-rw-r--r--finch/libgnt/Makefile.am1
-rw-r--r--finch/libgnt/gntkeys.h1
-rw-r--r--finch/libgnt/wms/Makefile.am3
-rw-r--r--finch/libgnt/wms/s.c2
-rw-r--r--finch/plugins/Makefile.am1
-rw-r--r--libpurple/Makefile.am26
-rw-r--r--libpurple/example/Makefile.am2
-rw-r--r--libpurple/marshallers.list4
-rw-r--r--libpurple/media.c1363
-rw-r--r--libpurple/media.h503
-rw-r--r--libpurple/mediamanager.c202
-rw-r--r--libpurple/mediamanager.h137
-rw-r--r--libpurple/plugins/Makefile.am3
-rw-r--r--libpurple/plugins/perl/Makefile.am5
-rw-r--r--libpurple/plugins/ssl/Makefile.am3
-rw-r--r--libpurple/plugins/tcl/Makefile.am3
-rw-r--r--libpurple/protocols/bonjour/Makefile.am9
-rw-r--r--libpurple/protocols/bonjour/bonjour.c15
-rw-r--r--libpurple/protocols/gg/Makefile.am3
-rw-r--r--libpurple/protocols/gg/gg.c13
-rw-r--r--libpurple/protocols/irc/Makefile.am2
-rw-r--r--libpurple/protocols/irc/irc.c14
-rw-r--r--libpurple/protocols/jabber/Makefile.am16
-rw-r--r--libpurple/protocols/jabber/Makefile.mingw6
-rw-r--r--libpurple/protocols/jabber/caps.c1
-rw-r--r--libpurple/protocols/jabber/disco.c16
-rw-r--r--libpurple/protocols/jabber/google.c353
-rw-r--r--libpurple/protocols/jabber/google.h1
-rw-r--r--libpurple/protocols/jabber/iq.c16
-rw-r--r--libpurple/protocols/jabber/jabber.c70
-rw-r--r--libpurple/protocols/jabber/jabber.h13
-rw-r--r--libpurple/protocols/jabber/jingle/content.c455
-rw-r--r--libpurple/protocols/jabber/jingle/content.h117
-rw-r--r--libpurple/protocols/jabber/jingle/jingle.c432
-rw-r--r--libpurple/protocols/jabber/jingle/jingle.h81
-rw-r--r--libpurple/protocols/jabber/jingle/session.c588
-rw-r--r--libpurple/protocols/jabber/jingle/session.h122
-rw-r--r--libpurple/protocols/jabber/jingle/transport.c172
-rw-r--r--libpurple/protocols/jabber/jingle/transport.h95
-rw-r--r--libpurple/protocols/jabber/libxmpp.c13
-rw-r--r--libpurple/protocols/jabber/presence.c4
-rw-r--r--libpurple/protocols/msn/Makefile.am5
-rw-r--r--libpurple/protocols/msn/msn.c4
-rw-r--r--libpurple/protocols/msnp9/Makefile.am6
-rw-r--r--libpurple/protocols/msnp9/msn.c4
-rw-r--r--libpurple/protocols/myspace/Makefile.am5
-rw-r--r--libpurple/protocols/myspace/myspace.c5
-rw-r--r--libpurple/protocols/novell/Makefile.am6
-rw-r--r--libpurple/protocols/novell/novell.c13
-rw-r--r--libpurple/protocols/null/nullprpl.c10
-rw-r--r--libpurple/protocols/oscar/Makefile.am6
-rw-r--r--libpurple/protocols/oscar/libaim.c5
-rw-r--r--libpurple/protocols/oscar/libicq.c3
-rw-r--r--libpurple/protocols/qq/Makefile.am6
-rw-r--r--libpurple/protocols/qq/qq.c4
-rw-r--r--libpurple/protocols/sametime/Makefile.am2
-rw-r--r--libpurple/protocols/sametime/sametime.c3
-rw-r--r--libpurple/protocols/silc/Makefile.am8
-rw-r--r--libpurple/protocols/silc/silc.c13
-rw-r--r--libpurple/protocols/silc10/silc.c11
-rw-r--r--libpurple/protocols/simple/Makefile.am6
-rw-r--r--libpurple/protocols/simple/simple.c13
-rw-r--r--libpurple/protocols/yahoo/Makefile.am6
-rw-r--r--libpurple/protocols/yahoo/yahoo.c2
-rw-r--r--libpurple/protocols/zephyr/Makefile.am6
-rw-r--r--libpurple/protocols/zephyr/zephyr.c4
-rw-r--r--libpurple/prpl.c56
-rw-r--r--libpurple/prpl.h52
-rw-r--r--libpurple/server.c4
-rw-r--r--libpurple/xmlnode.c6
-rw-r--r--libpurple/xmlnode.h9
-rw-r--r--pidgin/Makefile.am8
-rw-r--r--pidgin/gtkconv.c201
-rw-r--r--pidgin/gtkconv.h3
-rw-r--r--pidgin/gtkconvwin.h6
-rw-r--r--pidgin/gtkdebug.c7
-rw-r--r--pidgin/gtkdialogs.c6
-rw-r--r--pidgin/gtkimhtmltoolbar.c11
-rw-r--r--pidgin/gtkimhtmltoolbar.h1
-rw-r--r--pidgin/gtkmedia.c622
-rw-r--r--pidgin/gtkmedia.h70
-rw-r--r--pidgin/gtkprefs.c334
-rw-r--r--pidgin/gtkprefs.h1
-rw-r--r--pidgin/pidginstock.c6
-rw-r--r--pidgin/pidginstock.h5
-rw-r--r--pidgin/pixmaps/Makefile.am4
-rw-r--r--pidgin/pixmaps/toolbar/16/audio-call.pngbin0 -> 848 bytes
-rw-r--r--pidgin/pixmaps/toolbar/16/video-call.pngbin0 -> 947 bytes
-rw-r--r--pidgin/plugins/Makefile.am1
-rw-r--r--pidgin/plugins/cap/Makefile.am4
-rw-r--r--pidgin/plugins/gestures/Makefile.am1
-rw-r--r--pidgin/plugins/gevolution/Makefile.am3
-rw-r--r--pidgin/plugins/musicmessaging/Makefile.am1
-rw-r--r--pidgin/plugins/ticker/Makefile.am1
-rw-r--r--po/POTFILES.in2
110 files changed, 6978 insertions, 139 deletions
diff --git a/ChangeLog.API b/ChangeLog.API
index fb73a46024..adac0740e4 100644
--- a/ChangeLog.API
+++ b/ChangeLog.API
@@ -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:
diff --git a/Doxyfile.in b/Doxyfile.in
index 5e2c469b5c..02b959637d 100644
--- a/Doxyfile.in
+++ b/Doxyfile.in
@@ -1070,7 +1070,7 @@ INCLUDE_FILE_PATTERNS =
# 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.
diff --git a/configure.ac b/configure.ac
index 180cc20a42..08cbc3053c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -47,7 +47,7 @@ m4_define([purple_lt_current], [5])
m4_define([purple_major_version], [2])
m4_define([purple_minor_version], [5])
m4_define([purple_micro_version], [2])
-m4_define([purple_version_suffix], [])
+m4_define([purple_version_suffix], [vv-devel])
m4_define([purple_version],
[purple_major_version.purple_minor_version.purple_micro_version])
m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
@@ -56,7 +56,7 @@ m4_define([gnt_lt_current], [5])
m4_define([gnt_major_version], [2])
m4_define([gnt_minor_version], [5])
m4_define([gnt_micro_version], [2])
-m4_define([gnt_version_suffix], [])
+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 @@ I can find them.
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 @@ Use --disable-gstreamer if you do not need GStreamer (sound) support.
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,
@@ -2452,6 +2505,7 @@ echo Protocols to link statically.. : $STATIC_PRPLS
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
diff --git a/finch/Makefile.am b/finch/Makefile.am
index 2f08d120b1..41f55fb77f 100644
--- a/finch/Makefile.am
+++ b/finch/Makefile.am
@@ -26,6 +26,7 @@ finch_SOURCES = \
finch.c \
gntidle.c \
gntlog.c \
+ gntmedia.c \
gntnotify.c \
gntplugin.c \
gntpounce.c \
@@ -47,6 +48,7 @@ finch_headers = \
finch.h \
gntidle.h \
gntlog.h \
+ gntmedia.h \
gntnotify.h \
gntplugin.h \
gntpounce.h \
@@ -69,6 +71,7 @@ finch_LDADD = \
$(INTLLIBS) \
$(GLIB_LIBS) \
$(LIBXML_LIBS) \
+ $(FARSIGHT_LIBS) \
$(GNT_LIBS) \
$(GSTREAMER_LIBS) \
./libgnt/libgnt.la \
@@ -88,5 +91,6 @@ AM_CPPFLAGS = \
$(GLIB_CFLAGS) \
$(DBUS_CFLAGS) \
$(LIBXML_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(GSTREAMER_CFLAGS) \
$(GNT_CFLAGS)
diff --git a/finch/gntaccount.c b/finch/gntaccount.c
index cbe8918d84..e6b27efb4f 100644
--- a/finch/gntaccount.c
+++ b/finch/gntaccount.c
@@ -1097,3 +1097,4 @@ PurpleAccountUiOps *finch_accounts_get_ui_ops()
return &ui_ops;
}
+
diff --git a/finch/gntdebug.c b/finch/gntdebug.c
index 78044abdb6..bcbf20e455 100644
--- a/finch/gntdebug.c
+++ b/finch/gntdebug.c
@@ -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 @@ void finch_debug_init()
#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())
diff --git a/finch/gntft.c b/finch/gntft.c
index 43cd76c52e..0374d1b5a4 100644
--- a/finch/gntft.c
+++ b/finch/gntft.c
@@ -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"
diff --git a/finch/gntmedia.c b/finch/gntmedia.c
new file mode 100644
index 0000000000..f5db59b247
--- /dev/null
+++ b/finch/gntmedia.c
@@ -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 */
+
diff --git a/finch/gntmedia.h b/finch/gntmedia.h
new file mode 100644
index 0000000000..74a6ae8f3d
--- /dev/null
+++ b/finch/gntmedia.h
@@ -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 */
+
diff --git a/finch/gntnotify.c b/finch/gntnotify.c
index 16896d6de1..e00f470cab 100644
--- a/finch/gntnotify.c
+++ b/finch/gntnotify.c
@@ -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"
diff --git a/finch/gntplugin.c b/finch/gntplugin.c
index 4c1127cbc2..0c1a6e5c97 100644
--- a/finch/gntplugin.c
+++ b/finch/gntplugin.c
@@ -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"
diff --git a/finch/gntpounce.c b/finch/gntpounce.c
index e17e1616ac..799829eacb 100644
--- a/finch/gntpounce.c
+++ b/finch/gntpounce.c
@@ -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"
diff --git a/finch/gntrequest.c b/finch/gntrequest.c
index b613d8f811..16b9f75c3f 100644
--- a/finch/gntrequest.c
+++ b/finch/gntrequest.c
@@ -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
{
diff --git a/finch/gntstatus.c b/finch/gntstatus.c
index 7bb115edbc..129e29f03a 100644
--- a/finch/gntstatus.c
+++ b/finch/gntstatus.c
@@ -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
diff --git a/finch/gntui.c b/finch/gntui.c
index 7ebdf1a59d..1c2dbf0e24 100644
--- a/finch/gntui.c
+++ b/finch/gntui.c
@@ -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 @@ void gnt_ui_init()
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 @@ void gnt_ui_uninit()
finch_roomlist_uninit();
purple_roomlist_set_ui_ops(NULL);
+#ifdef USE_VV
+ finch_media_manager_uninit();
+#endif
+
gnt_quit();
#endif
}
diff --git a/finch/libgnt/Makefile.am b/finch/libgnt/Makefile.am
index af061dcc01..e692e4c121 100644
--- a/finch/libgnt/Makefile.am
+++ b/finch/libgnt/Makefile.am
@@ -89,6 +89,7 @@ libgnt_la_LIBADD = \
AM_CPPFLAGS = \
$(GLIB_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(GNT_CFLAGS) \
$(DEBUG_CFLAGS) \
$(LIBXML_CFLAGS) \
diff --git a/finch/libgnt/gntkeys.h b/finch/libgnt/gntkeys.h
index 0f5a6cc5a1..6f218aa4d6 100644
--- a/finch/libgnt/gntkeys.h
+++ b/finch/libgnt/gntkeys.h
@@ -165,5 +165,6 @@ int gnt_keys_find_combination(const char *key);
#undef lines
#undef buttons
#undef newline
+#undef set_clock
#endif
diff --git a/finch/libgnt/wms/Makefile.am b/finch/libgnt/wms/Makefile.am
index b72069a3d8..17b8352ddc 100644
--- a/finch/libgnt/wms/Makefile.am
+++ b/finch/libgnt/wms/Makefile.am
@@ -36,5 +36,6 @@ AM_CPPFLAGS = \
$(DEBUG_CFLAGS) \
$(GLIB_CFLAGS) \
$(GNT_CFLAGS) \
- $(PLUGIN_CFLAGS)
+ $(PLUGIN_CFLAGS) \
+ $(FARSIGHT_CFLAGS)
diff --git a/finch/libgnt/wms/s.c b/finch/libgnt/wms/s.c
index 3813fc4346..f514110c4b 100644
--- a/finch/libgnt/wms/s.c
+++ b/finch/libgnt/wms/s.c
@@ -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())
diff --git a/finch/plugins/Makefile.am b/finch/plugins/Makefile.am
index d733357c76..902a333ef7 100644
--- a/finch/plugins/Makefile.am
+++ b/finch/plugins/Makefile.am
@@ -42,6 +42,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/finch \
-I$(top_srcdir)/finch/libgnt \
$(DEBUG_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(GLIB_CFLAGS) \
$(GNT_CFLAGS) \
$(PLUGIN_CFLAGS)
diff --git a/libpurple/Makefile.am b/libpurple/Makefile.am
index 8a11b0098d..099da946f3 100644
--- a/libpurple/Makefile.am
+++ b/libpurple/Makefile.am
@@ -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 @@ purple_coresources = \
idle.c \
imgstore.c \
log.c \
+ marshallers.c \
+ media.c \
+ mediamanager.c \
mime.c \
nat-pmp.c \
network.c \
@@ -104,6 +108,9 @@ purple_coreheaders = \
idle.h \
imgstore.h \
log.h \
+ marshallers.h \
+ media.h \
+ mediamanager.h \
mime.h \
nat-pmp.h \
network.h \
@@ -137,6 +144,15 @@ purple_coreheaders = \
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 @@ CLEANFILES = \
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 @@ BUILT_SOURCES = $(purple_builtheaders) \
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 @@ libpurple_la_LIBADD = \
$(LIBXML_LIBS) \
$(NETWORKMANAGER_LIBS) \
$(INTLLIBS) \
+ $(FARSIGHT_LIBS) \
+ $(GSTREAMER_LIBS) \
+ $(GSTPROPS_LIBS) \
-lm
AM_CPPFLAGS = \
@@ -260,6 +283,9 @@ AM_CPPFLAGS = \
$(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.
diff --git a/libpurple/example/Makefile.am b/libpurple/example/Makefile.am
index e44edd3721..34cbe717ef 100644
--- a/libpurple/example/Makefile.am
+++ b/libpurple/example/Makefile.am
@@ -8,6 +8,7 @@ nullclient_LDADD = \
$(INTLLIBS) \
$(GLIB_LIBS) \
$(LIBXML_LIBS) \
+ $(FARSIGHT_LIBS) \
$(top_builddir)/libpurple/libpurple.la
AM_CPPFLAGS = \
@@ -23,4 +24,5 @@ AM_CPPFLAGS = \
$(DEBUG_CFLAGS) \
$(GLIB_CFLAGS) \
$(DBUS_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(LIBXML_CFLAGS)
diff --git a/libpurple/marshallers.list b/libpurple/marshallers.list
new file mode 100644
index 0000000000..2cfcaff953
--- /dev/null
+++ b/libpurple/marshallers.list
@@ -0,0 +1,4 @@
+VOID:BOXED,BOXED
+VOID:POINTER,POINTER,OBJECT
+BOOLEAN:OBJECT
+VOID:STRING,STRING
diff --git a/libpurple/media.c b/libpurple/media.c
new file mode 100644
index 0000000000..63fad00a3f
--- /dev/null
+++ b/libpurple/media.c
@@ -0,0 +1,1363 @@
+/**
+ * @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);
+ /* The queue is linked later, when the local video is ready to be shown */
+
+ 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)
+{
+ GstPad *sinkpad = gst_element_get_static_pad(session->sink, "ghostsink");
+ purple_debug_info("media", "connecting new src pad: %s\n",
+ gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK ? "success" : "failure");
+}
+
+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;
+
+ 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
+ */
+ codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_DISABLE,
+ "MPV", FS_MEDIA_TYPE_VIDEO, 90000));
+
+ /* XXX: SPEEX has a latency of 5 or 6 seconds for me */
+#if 0
+ /* SPEEX is added through the configuration */
+ codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY,
+ "SPEEX", FS_MEDIA_TYPE_AUDIO, 8000));
+ codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY,
+ "SPEEX", FS_MEDIA_TYPE_AUDIO, 16000));
+#endif
+
+ 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 */
diff --git a/libpurple/media.h b/libpurple/media.h
new file mode 100644
index 0000000000..813f3498c7
--- /dev/null
+++ b/libpurple/media.h
@@ -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_ */
diff --git a/libpurple/mediamanager.c b/libpurple/mediamanager.c
new file mode 100644
index 0000000000..323ea80afb
--- /dev/null
+++ b/libpurple/mediamanager.c
@@ -0,0 +1,202 @@
+/**
+ * @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);
+}
+
+#endif /* USE_VV */
diff --git a/libpurple/mediamanager.h b/libpurple/mediamanager.h
new file mode 100644
index 0000000000..8d93f502f1
--- /dev/null
+++ b/libpurple/mediamanager.h
@@ -0,0 +1,137 @@
+/**
+ * @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);
+
+/*}@*/
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* USE_VV */
+
+
+#endif /* __MEDIA_MANAGER_H_ */
diff --git a/libpurple/plugins/Makefile.am b/libpurple/plugins/Makefile.am
index 6cb2c8f45e..5d84aa8506 100644
--- a/libpurple/plugins/Makefile.am
+++ b/libpurple/plugins/Makefile.am
@@ -140,6 +140,9 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(DEBUG_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS) \
$(GLIB_CFLAGS) \
$(PLUGIN_CFLAGS) \
$(DBUS_CFLAGS)
diff --git a/libpurple/plugins/perl/Makefile.am b/libpurple/plugins/perl/Makefile.am
index 62c652a73e..3f3ffa0ad1 100644
--- a/libpurple/plugins/perl/Makefile.am
+++ b/libpurple/plugins/perl/Makefile.am
@@ -5,7 +5,7 @@ perl_dirs = common
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 @@ AM_CPPFLAGS = \
$(DEBUG_CFLAGS) \
$(GLIB_CFLAGS) \
$(PLUGIN_CFLAGS) \
- $(PERL_CFLAGS)
+ $(PERL_CFLAGS) \
+ $(FARSIGHT_CFLAGS)
diff --git a/libpurple/plugins/ssl/Makefile.am b/libpurple/plugins/ssl/Makefile.am
index 8959dd1bbd..930781d2d0 100644
--- a/libpurple/plugins/ssl/Makefile.am
+++ b/libpurple/plugins/ssl/Makefile.am
@@ -31,6 +31,9 @@ AM_CPPFLAGS = \
-I$(top_builddir)/libpurple \
$(DEBUG_CFLAGS) \
$(GLIB_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS) \
$(PLUGIN_CFLAGS)
ssl_gnutls_la_CFLAGS = $(AM_CPPFLAGS) $(GNUTLS_CFLAGS)
diff --git a/libpurple/plugins/tcl/Makefile.am b/libpurple/plugins/tcl/Makefile.am
index a29ac91334..69f57b0d88 100644
--- a/libpurple/plugins/tcl/Makefile.am
+++ b/libpurple/plugins/tcl/Makefile.am
@@ -7,7 +7,7 @@ plugin_LTLIBRARIES = tcl.la
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 @@ AM_CPPFLAGS = \
$(DEBUG_CFLAGS) \
$(GLIB_CFLAGS) \
$(PLUGIN_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(TK_CFLAGS) \
$(TCL_CFLAGS)
diff --git a/libpurple/protocols/bonjour/Makefile.am b/libpurple/protocols/bonjour/Makefile.am
index 1e481fa77c..a58d010dcc 100644
--- a/libpurple/protocols/bonjour/Makefile.am
+++ b/libpurple/protocols/bonjour/Makefile.am
@@ -51,5 +51,12 @@ AM_CPPFLAGS = \
$(GLIB_CFLAGS) \
$(DEBUG_CFLAGS) \
$(LIBXML_CFLAGS) \
- $(AVAHI_CFLAGS)
+ $(FARSIGHT_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
+
+#if MDNS_AVAHI
+# AM_CPPFLAGS += $(AVAHI_CFLAGS)
+#else
+#endif
diff --git a/libpurple/protocols/bonjour/bonjour.c b/libpurple/protocols/bonjour/bonjour.c
index 8b3f567165..e180eb5a82 100644
--- a/libpurple/protocols/bonjour/bonjour.c
+++ b/libpurple/protocols/bonjour/bonjour.c
@@ -499,13 +499,13 @@ static PurplePluginProtocolInfo prpl_info =
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 @@ init_plugin(PurplePlugin *plugin)
}
PURPLE_INIT_PLUGIN(bonjour, init_plugin, info);
+
diff --git a/libpurple/protocols/gg/Makefile.am b/libpurple/protocols/gg/Makefile.am
index e012307264..d26e780117 100644
--- a/libpurple/protocols/gg/Makefile.am
+++ b/libpurple/protocols/gg/Makefile.am
@@ -75,5 +75,8 @@ AM_CPPFLAGS = \
-I$(top_builddir)/libpurple \
$(INTGG_CFLAGS) \
$(GLIB_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS) \
$(DEBUG_CFLAGS)
diff --git a/libpurple/protocols/gg/gg.c b/libpurple/protocols/gg/gg.c
index 7aec897325..4b8de2ca3a 100644
--- a/libpurple/protocols/gg/gg.c
+++ b/libpurple/protocols/gg/gg.c
@@ -2165,13 +2165,13 @@ static PurplePluginProtocolInfo prpl_info =
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 @@ static void init_plugin(PurplePlugin *plugin)
PURPLE_INIT_PLUGIN(gg, init_plugin, info);
/* vim: set ts=8 sts=0 sw=8 noet: */
+
diff --git a/libpurple/protocols/irc/Makefile.am b/libpurple/protocols/irc/Makefile.am
index f56869a0bb..66c7ee0e23 100644
--- a/libpurple/protocols/irc/Makefile.am
+++ b/libpurple/protocols/irc/Makefile.am
@@ -32,4 +32,6 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(GLIB_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
$(DEBUG_CFLAGS)
diff --git a/libpurple/protocols/irc/irc.c b/libpurple/protocols/irc/irc.c
index 707a7389f8..049e76318e 100644
--- a/libpurple/protocols/irc/irc.c
+++ b/libpurple/protocols/irc/irc.c
@@ -917,13 +917,13 @@ static PurplePluginProtocolInfo prpl_info =
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) {
diff --git a/libpurple/protocols/jabber/Makefile.am b/libpurple/protocols/jabber/Makefile.am
index 4c5187e55e..dbf7603a64 100644
--- a/libpurple/protocols/jabber/Makefile.am
+++ b/libpurple/protocols/jabber/Makefile.am
@@ -21,6 +21,18 @@ JABBERSOURCES = auth.c \
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 @@ st =
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 @@ AM_CPPFLAGS = \
-I$(top_builddir)/libpurple \
$(DEBUG_CFLAGS) \
$(GLIB_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
$(LIBXML_CFLAGS)
diff --git a/libpurple/protocols/jabber/Makefile.mingw b/libpurple/protocols/jabber/Makefile.mingw
index 4c49619ed4..72878e0177 100644
--- a/libpurple/protocols/jabber/Makefile.mingw
+++ b/libpurple/protocols/jabber/Makefile.mingw
@@ -53,6 +53,12 @@ C_SRC = \
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 \
diff --git a/libpurple/protocols/jabber/caps.c b/libpurple/protocols/jabber/caps.c
index ad33a47884..33b27eca00 100644
--- a/libpurple/protocols/jabber/caps.c
+++ b/libpurple/protocols/jabber/caps.c
@@ -27,6 +27,7 @@
#include "util.h"
#include "iq.h"
+
#define JABBER_CAPS_FILENAME "xmpp-caps.xml"
static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */
diff --git a/libpurple/protocols/jabber/disco.c b/libpurple/protocols/jabber/disco.c
index e0fb8e347e..e77ec8cd33 100644
--- a/libpurple/protocols/jabber/disco.c
+++ b/libpurple/protocols/jabber/disco.c
@@ -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 @@ jabber_disco_bytestream_server_cb(JabberStream *js, xmlnode *packet, gpointer da
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 @@ void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) {
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 @@ void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) {
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;
diff --git a/libpurple/protocols/jabber/google.c b/libpurple/protocols/jabber/google.c
index b509096e5f..30acbeb1b6 100644
--- a/libpurple/protocols/jabber/google.c
+++ b/libpurple/protocols/jabber/google.c
@@ -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)
{
diff --git a/libpurple/protocols/jabber/google.h b/libpurple/protocols/jabber/google.h
index c1b6e0d0c2..f84ac82505 100644
--- a/libpurple/protocols/jabber/google.h
+++ b/libpurple/protocols/jabber/google.h
@@ -45,6 +45,7 @@ void jabber_google_roster_rem_deny(PurpleConnection *gc, const char *who);
char *jabber_google_format_to_html(const char *text);
+void jabber_google_session_parse(JabberStream *js, xmlnode *node);
#endif /* _PURPLE_GOOGLE_H_ */
diff --git a/libpurple/protocols/jabber/iq.c b/libpurple/protocols/jabber/iq.c
index f9d6801882..ee5808a7ae 100644
--- a/libpurple/protocols/jabber/iq.c
+++ b/libpurple/protocols/jabber/iq.c
@@ -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"
@@ -338,6 +339,11 @@ void jabber_iq_parse(JabberStream *js, xmlnode *packet)
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);
@@ -360,6 +366,13 @@ void jabber_iq_parse(JabberStream *js, xmlnode *packet)
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(type && (!strcmp(type, "set") || !strcmp(type, "get"))) {
@@ -400,6 +413,9 @@ void jabber_iq_init(void)
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)
diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
index 49fa5299a6..b01ac62632 100644
--- a/libpurple/protocols/jabber/jabber.c
+++ b/libpurple/protocols/jabber/jabber.c
@@ -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 @@ jabber_login(PurpleAccount *account)
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 @@ void jabber_close(PurpleConnection *gc)
{
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 @@ void jabber_convo_closed(PurpleConnection *gc, const char *who)
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 @@ gboolean jabber_offline_message(const PurpleBuddy *buddy)
{
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)
{
diff --git a/libpurple/protocols/jabber/jabber.h b/libpurple/protocols/jabber/jabber.h
index 72e945c3f8..44e1c2f90d 100644
--- a/libpurple/protocols/jabber/jabber.h
+++ b/libpurple/protocols/jabber/jabber.h
@@ -53,6 +53,8 @@ typedef struct _JabberStream JabberStream;
#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 @@ struct _JabberStream
* 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 @@ GList *jabber_actions(PurplePlugin *plugin, gpointer context);
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_ */
diff --git a/libpurple/protocols/jabber/jingle/content.c b/libpurple/protocols/jabber/jingle/content.c
new file mode 100644
index 0000000000..d7e57c0479
--- /dev/null
+++ b/libpurple/protocols/jabber/jingle/content.c
@@ -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 *jingle, JingleActionType action)
+{
+ g_return_if_fail(JINGLE_IS_CONTENT(content));
+ JINGLE_CONTENT_GET_CLASS(content)->handle_action(content, jingle, action);
+}
+
diff --git a/libpurple/protocols/jabber/jingle/content.h b/libpurple/protocols/jabber/jingle/content.h
new file mode 100644
index 0000000000..777b6b7488
--- /dev/null
+++ b/libpurple/protocols/jabber/jingle/content.h
@@ -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 *jingle, 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 *jingle, JingleActionType action);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_CONTENT_H */
+
diff --git a/libpurple/protocols/jabber/jingle/jingle.c b/libpurple/protocols/jabber/jingle/jingle.c
new file mode 100644
index 0000000000..0caee1cab9
--- /dev/null
+++ b/libpurple/protocols/jabber/jingle/jingle.c
@@ -0,0 +1,432 @@
+/*
+ * @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 *content = jingle_session_find_content(session, name, creator);
+ if (content == NULL) {
+ purple_debug_error("jingle", "Error parsing content\n");
+ /* XXX: send error */
+ } else {
+ jingle_content_handle_action(content, jingle,
+ 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, jingle,
+ 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 *content = jingle_session_find_content(session, name, creator);
+ if (content == NULL) {
+ purple_debug_error("jingle", "Error parsing content\n");
+ /* XXX: send error */
+ } else {
+ jingle_content_handle_action(content, jingle,
+ 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);
+ }
+}
+
diff --git a/libpurple/protocols/jabber/jingle/jingle.h b/libpurple/protocols/jabber/jingle/jingle.h
new file mode 100644
index 0000000000..c6b30373fe
--- /dev/null
+++ b/libpurple/protocols/jabber/jingle/jingle.h
@@ -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 */
diff --git a/libpurple/protocols/jabber/jingle/session.c b/libpurple/protocols/jabber/jingle/session.c
new file mode 100644
index 0000000000..1b374a720a
--- /dev/null
+++ b/libpurple/protocols/jabber/jingle/session.c
@@ -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;
+}
+
diff --git a/libpurple/protocols/jabber/jingle/session.h b/libpurple/protocols/jabber/jingle/session.h
new file mode 100644
index 0000000000..6ae459b79d
--- /dev/null
+++ b/libpurple/protocols/jabber/jingle/session.h
@@ -0,0 +1,122 @@
+/**
+ * @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);
+
+#define jingle_session_create_session_accept(session) \
+ jingle_session_to_packet(session, JINGLE_SESSION_ACCEPT)
+#define jingle_session_create_session_info(session) \
+ jingle_session_to_packet(session, JINGLE_SESSION_INFO)
+#define jingle_session_create_session_initiate(session) \
+ jingle_session_to_packet(session, JINGLE_SESSION_INITIATE)
+#define jingle_session_create_session_terminate(session) \
+ jingle_session_to_packet(session, JINGLE_SESSION_TERMINATE)
+
+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 */
+
diff --git a/libpurple/protocols/jabber/jingle/transport.c b/libpurple/protocols/jabber/jingle/transport.c
new file mode 100644
index 0000000000..79f1d8001e
--- /dev/null
+++ b/libpurple/protocols/jabber/jingle/transport.c
@@ -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);
+}
+
diff --git a/libpurple/protocols/jabber/jingle/transport.h b/libpurple/protocols/jabber/jingle/transport.h
new file mode 100644
index 0000000000..0c646ea37a
--- /dev/null
+++ b/libpurple/protocols/jabber/jingle/transport.h
@@ -0,0 +1,95 @@
+/**
+ * @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();
+
+#define jingle_transport_create_transport_accept(session) \
+ jingle_session_to_packet(session, JINGLE_TRANSPORT_ACCEPT)
+#define jingle_transport_create_transport_info(session) \
+ jingle_session_to_packet(session, JINGLE_TRANSPORT_INFO)
+#define jingle_transport_create_transport_replace(session) \
+ jingle_session_to_packet(session, JINGLE_TRANSPORT_REPLACE)
+
+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 */
+
diff --git a/libpurple/protocols/jabber/libxmpp.c b/libpurple/protocols/jabber/libxmpp.c
index 5b3ffd3810..c7ac36f9f3 100644
--- a/libpurple/protocols/jabber/libxmpp.c
+++ b/libpurple/protocols/jabber/libxmpp.c
@@ -116,9 +116,15 @@ static PurplePluginProtocolInfo prpl_info =
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 @@ init_plugin(PurplePlugin *plugin)
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
}
diff --git a/libpurple/protocols/jabber/presence.c b/libpurple/protocols/jabber/presence.c
index d5af230b4e..474b9816a1 100644
--- a/libpurple/protocols/jabber/presence.c
+++ b/libpurple/protocols/jabber/presence.c
@@ -265,6 +265,10 @@ xmlnode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, con
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 */
diff --git a/libpurple/protocols/msn/Makefile.am b/libpurple/protocols/msn/Makefile.am
index 39ecdb3200..bfabfb7e86 100644
--- a/libpurple/protocols/msn/Makefile.am
+++ b/libpurple/protocols/msn/Makefile.am
@@ -91,4 +91,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(GLIB_CFLAGS) \
- $(DEBUG_CFLAGS)
+ $(DEBUG_CFLAGS) \
+ $(FARSIGHT_CFLAGS)
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
diff --git a/libpurple/protocols/msn/msn.c b/libpurple/protocols/msn/msn.c
index 7294519b2c..a1bca57b29 100644
--- a/libpurple/protocols/msn/msn.c
+++ b/libpurple/protocols/msn/msn.c
@@ -2546,9 +2546,10 @@ static PurplePluginProtocolInfo prpl_info =
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 @@ init_plugin(PurplePlugin *plugin)
}
PURPLE_INIT_PLUGIN(msn, init_plugin, info);
+
diff --git a/libpurple/protocols/msnp9/Makefile.am b/libpurple/protocols/msnp9/Makefile.am
index ae1264b41c..1bb0c053fc 100644
--- a/libpurple/protocols/msnp9/Makefile.am
+++ b/libpurple/protocols/msnp9/Makefile.am
@@ -87,4 +87,8 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(GLIB_CFLAGS) \
- $(DEBUG_CFLAGS)
+ $(DEBUG_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
+
diff --git a/libpurple/protocols/msnp9/msn.c b/libpurple/protocols/msnp9/msn.c
index cf8527864a..29cdd1728c 100644
--- a/libpurple/protocols/msnp9/msn.c
+++ b/libpurple/protocols/msnp9/msn.c
@@ -2276,9 +2276,10 @@ static PurplePluginProtocolInfo prpl_info =
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 @@ init_plugin(PurplePlugin *plugin)
}
PURPLE_INIT_PLUGIN(msnp9, init_plugin, info);
+
diff --git a/libpurple/protocols/myspace/Makefile.am b/libpurple/protocols/myspace/Makefile.am
index 4309591d58..e475b489dd 100644
--- a/libpurple/protocols/myspace/Makefile.am
+++ b/libpurple/protocols/myspace/Makefile.am
@@ -40,4 +40,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(GLIB_CFLAGS) \
- $(DEBUG_CFLAGS)
+ $(FARSIGHT_CFLAGS) \
+ $(DEBUG_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
diff --git a/libpurple/protocols/myspace/myspace.c b/libpurple/protocols/myspace/myspace.c
index 0130e3b215..10fae7a276 100644
--- a/libpurple/protocols/myspace/myspace.c
+++ b/libpurple/protocols/myspace/myspace.c
@@ -3146,9 +3146,10 @@ static PurplePluginProtocolInfo prpl_info = {
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 */
};
diff --git a/libpurple/protocols/novell/Makefile.am b/libpurple/protocols/novell/Makefile.am
index df573c3fe2..2df0b5ee28 100644
--- a/libpurple/protocols/novell/Makefile.am
+++ b/libpurple/protocols/novell/Makefile.am
@@ -54,4 +54,8 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(DEBUG_CFLAGS) \
- $(GLIB_CFLAGS)
+ $(FARSIGHT_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
+
diff --git a/libpurple/protocols/novell/novell.c b/libpurple/protocols/novell/novell.c
index d137964a85..bff9e44502 100644
--- a/libpurple/protocols/novell/novell.c
+++ b/libpurple/protocols/novell/novell.c
@@ -3512,13 +3512,13 @@ static PurplePluginProtocolInfo prpl_info = {
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 @@ init_plugin(PurplePlugin * plugin)
}
PURPLE_INIT_PLUGIN(novell, init_plugin, info);
+
diff --git a/libpurple/protocols/null/nullprpl.c b/libpurple/protocols/null/nullprpl.c
index d3e1df87a5..03f4c4a558 100644
--- a/libpurple/protocols/null/nullprpl.c
+++ b/libpurple/protocols/null/nullprpl.c
@@ -1126,11 +1126,13 @@ static PurplePluginProtocolInfo prpl_info =
NULL, /* whiteboard_prpl_ops */
NULL, /* send_raw */
NULL, /* roomlist_room_serialize */
- NULL, /* padding... */
+ NULL, /* unregister_user */
+ NULL, /* send_attention */
+ NULL, /* get_attention_types */
+ sizeof(PurplePluginProtocolInfo), /* struct_size */
NULL,
- NULL,
- sizeof(PurplePluginProtocolInfo), /* struct_size */
- NULL
+ NULL, /* initiate_media */
+ NULL /* can_do_media */
};
static void nullprpl_init(PurplePlugin *plugin)
diff --git a/libpurple/protocols/oscar/Makefile.am b/libpurple/protocols/oscar/Makefile.am
index f27907d7a3..433ccceb3d 100644
--- a/libpurple/protocols/oscar/Makefile.am
+++ b/libpurple/protocols/oscar/Makefile.am
@@ -76,4 +76,8 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(GLIB_CFLAGS) \
- $(DEBUG_CFLAGS)
+ $(FARSIGHT_CFLAGS) \
+ $(DEBUG_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
+
diff --git a/libpurple/protocols/oscar/libaim.c b/libpurple/protocols/oscar/libaim.c
index ead36b3538..5e8579a0de 100644
--- a/libpurple/protocols/oscar/libaim.c
+++ b/libpurple/protocols/oscar/libaim.c
@@ -95,9 +95,10 @@ static PurplePluginProtocolInfo prpl_info =
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 =
diff --git a/libpurple/protocols/oscar/libicq.c b/libpurple/protocols/oscar/libicq.c
index fae6fd2b64..ed69d86aa9 100644
--- a/libpurple/protocols/oscar/libicq.c
+++ b/libpurple/protocols/oscar/libicq.c
@@ -26,7 +26,6 @@
#include "oscarcommon.h"
-
static GHashTable *
icq_get_account_text_table(PurpleAccount *account)
{
@@ -107,6 +106,8 @@ static PurplePluginProtocolInfo prpl_info =
sizeof(PurplePluginProtocolInfo), /* struct_size */
icq_get_account_text_table, /* get_account_text_table */
+ NULL, /* initiate_media */
+ NULL /* can_do_media */
};
static PurplePluginInfo info =
diff --git a/libpurple/protocols/qq/Makefile.am b/libpurple/protocols/qq/Makefile.am
index 6bc8fa3314..2b6f34a934 100644
--- a/libpurple/protocols/qq/Makefile.am
+++ b/libpurple/protocols/qq/Makefile.am
@@ -84,4 +84,8 @@ AM_CPPFLAGS = \
-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)
+
diff --git a/libpurple/protocols/qq/qq.c b/libpurple/protocols/qq/qq.c
index 58a02c8bbb..8ea5e26272 100644
--- a/libpurple/protocols/qq/qq.c
+++ b/libpurple/protocols/qq/qq.c
@@ -785,7 +785,9 @@ static PurplePluginProtocolInfo prpl_info =
NULL, /* get attention_types */
sizeof(PurplePluginProtocolInfo), /* struct_size */
- NULL
+ NULL, /* get_account_text_table */
+ NULL, /* initiate_media */
+ NULL /* can_do_media */
};
static PurplePluginInfo info = {
diff --git a/libpurple/protocols/sametime/Makefile.am b/libpurple/protocols/sametime/Makefile.am
index 1cba6a748b..4e28f46f77 100644
--- a/libpurple/protocols/sametime/Makefile.am
+++ b/libpurple/protocols/sametime/Makefile.am
@@ -32,7 +32,7 @@ libsametime_la_LIBADD = $(GLIB_LIBS) $(MEANWHILE_LIBS)
AM_CFLAGS = \
- $(GLIB_CFLAGS) $(MEANWHILE_CFLAGS) \
+ $(GLIB_CFLAGS) $(MEANWHILE_CFLAGS) $(FARSIGHT_CFLAGS) \
$(DEBUG_CFLAGS) \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple
diff --git a/libpurple/protocols/sametime/sametime.c b/libpurple/protocols/sametime/sametime.c
index 91e1894fb2..edb190d1b0 100644
--- a/libpurple/protocols/sametime/sametime.c
+++ b/libpurple/protocols/sametime/sametime.c
@@ -5188,7 +5188,8 @@ static PurplePluginProtocolInfo mw_prpl_info = {
.new_xfer = mw_prpl_new_xfer,
.offline_message = NULL,
.whiteboard_prpl_ops = NULL,
- .send_raw = NULL
+ .send_raw = NULL,
+ .struct_size = sizeof(PurplePluginProtocolInfo)
};
diff --git a/libpurple/protocols/silc/Makefile.am b/libpurple/protocols/silc/Makefile.am
index df78045d30..05fb3dc868 100644
--- a/libpurple/protocols/silc/Makefile.am
+++ b/libpurple/protocols/silc/Makefile.am
@@ -25,7 +25,7 @@ pkg_LTLIBRARIES = libsilcpurple.la
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 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(GLIB_CFLAGS) \
- $(DEBUG_CFLAGS)
+ $(FARSIGHT_CFLAGS) \
+ $(DEBUG_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
+
diff --git a/libpurple/protocols/silc/silc.c b/libpurple/protocols/silc/silc.c
index df71164a54..1200faea45 100644
--- a/libpurple/protocols/silc/silc.c
+++ b/libpurple/protocols/silc/silc.c
@@ -2103,13 +2103,13 @@ static PurplePluginProtocolInfo prpl_info =
&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 @@ silc_log_set_debug_callbacks(silcpurple_debug_cb, NULL, NULL, NULL);
}
PURPLE_INIT_PLUGIN(silc, init_plugin, info);
+
diff --git a/libpurple/protocols/silc10/silc.c b/libpurple/protocols/silc10/silc.c
index fe68af53da..9cc7a2c332 100644
--- a/libpurple/protocols/silc10/silc.c
+++ b/libpurple/protocols/silc10/silc.c
@@ -1836,12 +1836,13 @@ static PurplePluginProtocolInfo prpl_info =
&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 =
diff --git a/libpurple/protocols/simple/Makefile.am b/libpurple/protocols/simple/Makefile.am
index 029ea2a4a3..8fa1a43718 100644
--- a/libpurple/protocols/simple/Makefile.am
+++ b/libpurple/protocols/simple/Makefile.am
@@ -33,4 +33,8 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(GLIB_CFLAGS) \
- $(DEBUG_CFLAGS)
+ $(FARSIGHT_CFLAGS) \
+ $(DEBUG_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
+
diff --git a/libpurple/protocols/simple/simple.c b/libpurple/protocols/simple/simple.c
index 4cf336a0ac..057fd7ab7d 100644
--- a/libpurple/protocols/simple/simple.c
+++ b/libpurple/protocols/simple/simple.c
@@ -2069,13 +2069,13 @@ static PurplePluginProtocolInfo prpl_info =
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 @@ static void _init_plugin(PurplePlugin *plugin)
}
PURPLE_INIT_PLUGIN(simple, _init_plugin, info);
+
diff --git a/libpurple/protocols/yahoo/Makefile.am b/libpurple/protocols/yahoo/Makefile.am
index 3cd26b4812..d560693b9b 100644
--- a/libpurple/protocols/yahoo/Makefile.am
+++ b/libpurple/protocols/yahoo/Makefile.am
@@ -53,4 +53,8 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_builddir)/libpurple \
$(GLIB_CFLAGS) \
- $(DEBUG_CFLAGS)
+ $(FARSIGHT_CFLAGS) \
+ $(DEBUG_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
+
diff --git a/libpurple/protocols/yahoo/yahoo.c b/libpurple/protocols/yahoo/yahoo.c
index fc3d9ad36b..0960c327e0 100644
--- a/libpurple/protocols/yahoo/yahoo.c
+++ b/libpurple/protocols/yahoo/yahoo.c
@@ -4421,6 +4421,8 @@ static PurplePluginProtocolInfo prpl_info =
sizeof(PurplePluginProtocolInfo), /* struct_size */
yahoo_get_account_text_table, /* get_account_text_table */
+ NULL, /* initiate_media */
+ NULL /* can_do_media */
};
static PurplePluginInfo info =
diff --git a/libpurple/protocols/zephyr/Makefile.am b/libpurple/protocols/zephyr/Makefile.am
index b293b081bf..83d0add71d 100644
--- a/libpurple/protocols/zephyr/Makefile.am
+++ b/libpurple/protocols/zephyr/Makefile.am
@@ -106,5 +106,9 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple/protocols \
-DCONFDIR=\"$(sysconfdir)\" \
$(GLIB_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(KRB4_CFLAGS) \
- $(DEBUG_CFLAGS)
+ $(DEBUG_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
+ $(LIBXML_CFLAGS)
+
diff --git a/libpurple/protocols/zephyr/zephyr.c b/libpurple/protocols/zephyr/zephyr.c
index 2298933691..f3f37e1cde 100644
--- a/libpurple/protocols/zephyr/zephyr.c
+++ b/libpurple/protocols/zephyr/zephyr.c
@@ -2915,7 +2915,9 @@ static PurplePluginProtocolInfo prpl_info = {
NULL,
NULL,
sizeof(PurplePluginProtocolInfo), /* struct_size */
- NULL
+ NULL, /* get_account_text_table */
+ NULL, /* initate_media */
+ NULL /* can_do_media */
};
static PurplePluginInfo info = {
diff --git a/libpurple/prpl.c b/libpurple/prpl.c
index 0f9ab49b91..6bc10b28c6 100644
--- a/libpurple/prpl.c
+++ b/libpurple/prpl.c
@@ -494,6 +494,61 @@ purple_prpl_got_attention_in_chat(PurpleConnection *gc, int id, const char *who,
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
**************************************************************************/
@@ -515,3 +570,4 @@ purple_find_prpl(const char *id)
return NULL;
}
+
diff --git a/libpurple/prpl.h b/libpurple/prpl.h
index 43bb6e3cad..5d928337d4 100644
--- a/libpurple/prpl.h
+++ b/libpurple/prpl.h
@@ -65,6 +65,7 @@ typedef struct _PurpleBuddyIconSpec PurpleBuddyIconSpec;
#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 @@ typedef struct _PurpleBuddyIconSpec PurpleBuddyIconSpec;
#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 @@ struct _PurplePluginProtocolInfo
* 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 @@ struct _PurplePluginProtocolInfo
* 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(PurpleConnection *gc, const char *who, guint type
*/
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);
+
/*@}*/
/**************************************************************************/
diff --git a/libpurple/server.c b/libpurple/server.c
index 39a188a35b..5827db8e5b 100644
--- a/libpurple/server.c
+++ b/libpurple/server.c
@@ -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"
@@ -975,3 +978,4 @@ void serv_send_file(PurpleConnection *gc, const char *who, const char *file)
}
}
}
+
diff --git a/libpurple/xmlnode.c b/libpurple/xmlnode.c
index f5f2f9e48c..ad77e6c459 100644
--- a/libpurple/xmlnode.c
+++ b/libpurple/xmlnode.c
@@ -307,6 +307,12 @@ const char *xmlnode_get_prefix(xmlnode *node)
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)
{
diff --git a/libpurple/xmlnode.h b/libpurple/xmlnode.h
index f7422b232c..d6074dabee 100644
--- a/libpurple/xmlnode.h
+++ b/libpurple/xmlnode.h
@@ -246,6 +246,15 @@ void xmlnode_set_prefix(xmlnode *node, const char *prefix);
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.
diff --git a/pidgin/Makefile.am b/pidgin/Makefile.am
index 0ee908f4a0..f4138b9646 100644
--- a/pidgin/Makefile.am
+++ b/pidgin/Makefile.am
@@ -100,6 +100,7 @@ pidgin_SOURCES = \
gtkimhtmltoolbar.c \
gtklog.c \
gtkmain.c \
+ gtkmedia.c \
gtkmenutray.c \
gtknotify.c \
gtkplugin.c \
@@ -152,6 +153,7 @@ pidgin_headers = \
gtkimhtml.h \
gtkimhtmltoolbar.h \
gtklog.h \
+ gtkmedia.c \
gtkmenutray.h \
gtknickcolors.h \
gtknotify.h \
@@ -197,6 +199,8 @@ pidgin_LDADD = \
$(STARTUP_NOTIFICATION_LIBS) \
$(LIBXML_LIBS) \
$(GTK_LIBS) \
+ $(FARSIGHT_LIBS) \
+ $(GSTPROPS_LIBS) \
$(top_builddir)/libpurple/libpurple.la
if USE_INTERNAL_LIBGADU
@@ -221,5 +225,7 @@ AM_CPPFLAGS = \
$(GTKSPELL_CFLAGS) \
$(STARTUP_NOTIFICATION_CFLAGS) \
$(LIBXML_CFLAGS) \
- $(INTGG_CFLAGS)
+ $(INTGG_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
+ $(GSTPROPS_CFLAGS)
endif # ENABLE_GTK
diff --git a/pidgin/gtkconv.c b/pidgin/gtkconv.c
index 610433118a..ee1b0f9bcd 100644
--- a/pidgin/gtkconv.c
+++ b/pidgin/gtkconv.c
@@ -46,6 +46,7 @@
#include "idle.h"
#include "imgstore.h"
#include "log.h"
+#include "mediamanager.h"
#include "notify.h"
#include "prpl.h"
#include "request.h"
@@ -60,6 +61,7 @@
#include "gtkimhtml.h"
#include "gtkimhtmltoolbar.h"
#include "gtklog.h"
+#include "gtkmedia.h"
#include "gtkmenutray.h"
#include "gtkpounce.h"
#include "gtkprefs.h"
@@ -1194,6 +1196,18 @@ menu_find_cb(gpointer data, guint action, GtkWidget *widget)
gtk_widget_grab_focus(s->entry);
}
+#ifdef USE_VV
+/* Forward declare this here, because I want to keep VV-related stuff together
+for now */
+static void
+menu_initiate_audio_call_cb(gpointer data, guint action, GtkWidget *widget);
+static void
+menu_initiate_video_call_cb(gpointer data, guint action, GtkWidget *widget);
+static void
+menu_initiate_audio_video_call_cb(gpointer data, guint action, GtkWidget *widget);
+
+#endif
+
static void
menu_send_file_cb(gpointer data, guint action, GtkWidget *widget)
{
@@ -3073,6 +3087,17 @@ static GtkItemFactoryEntry menu_items[] =
{ "/Conversation/sep1", NULL, NULL, 0, "<Separator>", NULL },
+#ifdef USE_VV
+ { N_("/Conversation/M_edia"), NULL, NULL, 0, "<Branch>", NULL },
+
+ { N_("/Conversation/Media/_Audio Call"), NULL, menu_initiate_audio_call_cb, 0,
+ "<StockItem>", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL },
+ { N_("/Conversation/Media/_Video Call"), NULL, menu_initiate_video_call_cb, 0,
+ "<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL },
+ { N_("/Conversation/Media/Audio\\/Video _Call"), NULL, menu_initiate_audio_video_call_cb, 0,
+ "<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL },
+#endif
+
{ N_("/Conversation/Se_nd File..."), NULL, menu_send_file_cb, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_FILE },
{ N_("/Conversation/Add Buddy _Pounce..."), NULL, menu_add_pounce_cb,
0, "<Item>", NULL },
@@ -3383,6 +3408,18 @@ setup_menubar(PidginWindow *win)
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 =
@@ -4730,7 +4767,7 @@ pidgin_conv_create_tooltip(GtkWidget *tipwindow, gpointer userdata, int *w, int
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;
@@ -4740,7 +4777,7 @@ setup_common_pane(PidginConversation *gtkconv)
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 */
@@ -4810,23 +4847,21 @@ setup_common_pane(PidginConversation *gtkconv)
/* Setup the gtkimhtml widget */
frame = pidgin_create_imhtml(FALSE, &gtkconv->imhtml, NULL, &imhtml_sw);
gtk_widget_set_size_request(gtkconv->imhtml, -1, 0);
- if (chat) {
- GtkWidget *hpaned;
+ /* 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) {
/* 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");
@@ -6344,6 +6379,37 @@ gray_stuff_out(PidginConversation *gtkconv)
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));
@@ -6881,7 +6947,7 @@ pidgin_conv_update_buddy_icon(PurpleConversation *conv)
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);
@@ -7624,6 +7690,110 @@ gboolean pidgin_conv_attach_to_conversation(PurpleConversation *conv)
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)
{
@@ -7724,7 +7894,10 @@ pidgin_conversations_init(void)
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
/**********************************************************************
diff --git a/pidgin/gtkconv.h b/pidgin/gtkconv.h
index df34aa0017..9a0d557e11 100644
--- a/pidgin/gtkconv.h
+++ b/pidgin/gtkconv.h
@@ -150,6 +150,7 @@ struct _PidginConversation
gpointer depr1;
#endif
+ GtkWidget *middle_hpaned;
GtkWidget *lower_hbox;
GtkWidget *toolbar;
@@ -169,6 +170,8 @@ struct _PidginConversation
GtkWidget *infopane;
GtkListStore *infopane_model;
GtkTreeIter infopane_iter;
+ GtkWidget *topvbox;
+ GtkWidget *gtkmedia;
/* Used when attaching a PidginConversation to a PurpleConversation
* with message history */
diff --git a/pidgin/gtkconvwin.h b/pidgin/gtkconvwin.h
index 73107d2971..0076d16d50 100644
--- a/pidgin/gtkconvwin.h
+++ b/pidgin/gtkconvwin.h
@@ -49,7 +49,11 @@ struct _PidginWindow
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;
diff --git a/pidgin/gtkdebug.c b/pidgin/gtkdebug.c
index b6edea6233..b46d66d179 100644
--- a/pidgin/gtkdebug.c
+++ b/pidgin/gtkdebug.c
@@ -985,6 +985,13 @@ pidgin_debug_init(void)
#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())
diff --git a/pidgin/gtkdialogs.c b/pidgin/gtkdialogs.c
index d40eed480a..f7c66bbf5d 100644
--- a/pidgin/gtkdialogs.c
+++ b/pidgin/gtkdialogs.c
@@ -675,6 +675,12 @@ if (purple_plugins_find_with_id("core-tcl") != NULL) {
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/>");
diff --git a/pidgin/gtkimhtmltoolbar.c b/pidgin/gtkimhtmltoolbar.c
index a09208b490..2e1bceb342 100644
--- a/pidgin/gtkimhtmltoolbar.c
+++ b/pidgin/gtkimhtmltoolbar.c
@@ -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 @@ insert_link_cb(GtkWidget *w, GtkIMHtmlToolbar *toolbar)
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 @@ static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar)
GtkWidget *insert_button;
GtkWidget *font_button;
GtkWidget *smiley_button;
+
GtkWidget *font_menu;
GtkWidget *insert_menu;
GtkWidget *menuitem;
diff --git a/pidgin/gtkimhtmltoolbar.h b/pidgin/gtkimhtmltoolbar.h
index f7de106c49..82a54346a6 100644
--- a/pidgin/gtkimhtmltoolbar.h
+++ b/pidgin/gtkimhtmltoolbar.h
@@ -76,6 +76,7 @@ struct _GtkIMHtmlToolbar {
char *sml;
GtkWidget *strikethrough;
GtkWidget *insert_hr;
+ GtkWidget *call;
};
struct _GtkIMHtmlToolbarClass {
diff --git a/pidgin/gtkmedia.c b/pidgin/gtkmedia.c
new file mode 100644
index 0000000000..ed062fac21
--- /dev/null
+++ b/pidgin/gtkmedia.c
@@ -0,0 +1,622 @@
+/**
+ * @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 *audiosendbin = NULL, *audiosendlevel = NULL;
+ GstElement *audiorecvbin = NULL, *audiorecvlevel = NULL;
+ GstElement *videosendbin = NULL;
+ GstElement *videorecvbin = NULL;
+
+ 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 (!audiorecvbin && (type & PURPLE_MEDIA_RECV_AUDIO)) {
+ purple_media_audio_init_recv(&audiorecvbin, &audiorecvlevel);
+ purple_media_set_sink(media, sessions->data, audiorecvbin);
+ gst_element_set_state(audiorecvbin, GST_STATE_READY);
+ }
+ } 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 (!videorecvbin && (type & PURPLE_MEDIA_RECV_VIDEO)) {
+ purple_media_video_init_recv(&videorecvbin);
+ purple_media_set_sink(media, sessions->data, videorecvbin);
+ gst_element_set_state(videorecvbin, GST_STATE_READY);
+ }
+ }
+ }
+
+ if (audiosendlevel || audiorecvlevel) {
+ g_object_set(gtkmedia, "send-level", audiosendlevel,
+ "recv-level", audiorecvlevel,
+ NULL);
+ }
+}
+
+static void
+pidgin_media_wait_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+ pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_WAITING);
+}
+
+/* maybe we should have different callbacks for when we received the accept
+ and we accepted ourselves */
+static void
+pidgin_media_accept_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+ GtkWidget *send_widget = NULL, *recv_widget = NULL;
+
+ GstElement *pipeline = purple_media_get_pipeline(media);
+ GstElement *audiosendbin = NULL;
+ GstElement *audiorecvbin = NULL;
+ GstElement *videosendbin = NULL;
+ GstElement *videorecvbin = NULL;
+ GstBus *bus;
+
+ 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 (videorecvbin || audiorecvbin) {
+ 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 (videorecvbin) {
+ 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;
+ gst_element_set_state(videorecvbin, GST_STATE_PLAYING);
+ }
+ if (videosendbin) {
+ GtkWidget *aspect;
+ GtkWidget *local_video;
+ GstElement *tee, *queue;
+
+ 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;
+
+ tee = gst_bin_get_by_name(GST_BIN(videosendbin), "purplevideosrctee");
+ queue = gst_bin_get_by_name(GST_BIN(videosendbin), "purplelocalvideoqueue");
+ gst_element_link(tee, queue);
+ gst_object_unref(tee);
+ gst_object_unref(queue);
+ }
+
+ if (audiorecvbin) {
+ 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);
+ gst_element_set_state(audiorecvbin, GST_STATE_PLAYING);
+ }
+ 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);
+ }
+
+ if (audiorecvbin || audiosendbin || videorecvbin || videosendbin)
+ gtk_widget_show(gtkmedia->priv->display);
+
+ bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
+ if (audiorecvbin || audiosendbin)
+ g_signal_connect(G_OBJECT(bus), "message::element",
+ G_CALLBACK(level_message_cb), gtkmedia);
+ if (videorecvbin || videosendbin)
+ gst_bus_set_sync_handler(bus, (GstBusSyncHandler)create_window, gtkmedia);
+ gst_object_unref(bus);
+}
+
+static void
+pidgin_media_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 */
diff --git a/pidgin/gtkmedia.h b/pidgin/gtkmedia.h
new file mode 100644
index 0000000000..1c44e71267
--- /dev/null
+++ b/pidgin/gtkmedia.h
@@ -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_ */
diff --git a/pidgin/gtkprefs.c b/pidgin/gtkprefs.c
index 8c91e6b7a7..e5cc449ca8 100644
--- a/pidgin/gtkprefs.c
+++ b/pidgin/gtkprefs.c
@@ -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 @@ dropdown_set(GObject *w, const char *key)
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 @@ interface_page(void)
_("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 @@ interface_page(void)
_("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 @@ interface_page(void)
#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 @@ sound_page(void)
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 @@ static void prefs_notebook_init(void) {
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 @@ pidgin_prefs_init(void)
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();
}
diff --git a/pidgin/gtkprefs.h b/pidgin/gtkprefs.h
index e2baefc397..4ef67bd1cf 100644
--- a/pidgin/gtkprefs.h
+++ b/pidgin/gtkprefs.h
@@ -29,6 +29,7 @@
#include "prefs.h"
+
/**
* Initializes all UI-specific preferences.
*/
diff --git a/pidgin/pidginstock.c b/pidgin/pidginstock.c
index 932cd5eaeb..be535dc5ac 100644
--- a/pidgin/pidginstock.c
+++ b/pidgin/pidginstock.c
@@ -193,6 +193,12 @@ static struct SizedStockIcon {
{ 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 },
diff --git a/pidgin/pidginstock.h b/pidgin/pidginstock.h
index 65ebf4aa22..41fd5a481f 100644
--- a/pidgin/pidginstock.h
+++ b/pidgin/pidginstock.h
@@ -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"
diff --git a/pidgin/pixmaps/Makefile.am b/pidgin/pixmaps/Makefile.am
index ff2e2080c7..2acfe0ba07 100644
--- a/pidgin/pixmaps/Makefile.am
+++ b/pidgin/pixmaps/Makefile.am
@@ -421,6 +421,7 @@ TOOLBAR_16_SCALABLE = \
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 = \
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
diff --git a/pidgin/pixmaps/toolbar/16/audio-call.png b/pidgin/pixmaps/toolbar/16/audio-call.png
new file mode 100644
index 0000000000..7f71ff234a
--- /dev/null
+++ b/pidgin/pixmaps/toolbar/16/audio-call.png
Binary files differ
diff --git a/pidgin/pixmaps/toolbar/16/video-call.png b/pidgin/pixmaps/toolbar/16/video-call.png
new file mode 100644
index 0000000000..514cbc9568
--- /dev/null
+++ b/pidgin/pixmaps/toolbar/16/video-call.png
Binary files differ
diff --git a/pidgin/plugins/Makefile.am b/pidgin/plugins/Makefile.am
index 6d3957931d..a8b21cb887 100644
--- a/pidgin/plugins/Makefile.am
+++ b/pidgin/plugins/Makefile.am
@@ -123,6 +123,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_srcdir)/pidgin \
$(DEBUG_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(GTK_CFLAGS) \
$(PLUGIN_CFLAGS)
diff --git a/pidgin/plugins/cap/Makefile.am b/pidgin/plugins/cap/Makefile.am
index 67aaefb8b7..d56a4f225f 100644
--- a/pidgin/plugins/cap/Makefile.am
+++ b/pidgin/plugins/cap/Makefile.am
@@ -15,7 +15,7 @@ cap_la_SOURCES = \
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 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/pidgin \
$(DEBUG_CFLAGS) \
$(GTK_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
+ $(GSTPROPS_CFLAGS) \
$(SQLITE3_CFLAGS)
EXTRA_DIST = Makefile.mingw
diff --git a/pidgin/plugins/gestures/Makefile.am b/pidgin/plugins/gestures/Makefile.am
index faa3baf74c..32ba1f09cc 100644
--- a/pidgin/plugins/gestures/Makefile.am
+++ b/pidgin/plugins/gestures/Makefile.am
@@ -23,4 +23,5 @@ AM_CPPFLAGS = \
-I$(top_builddir)/libpurple \
-I$(top_srcdir)/pidgin \
$(DEBUG_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(GTK_CFLAGS)
diff --git a/pidgin/plugins/gevolution/Makefile.am b/pidgin/plugins/gevolution/Makefile.am
index 56c652633b..7b9d6795c7 100644
--- a/pidgin/plugins/gevolution/Makefile.am
+++ b/pidgin/plugins/gevolution/Makefile.am
@@ -26,4 +26,5 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/pidgin \
$(EVOLUTION_ADDRESSBOOK_CFLAGS) \
$(DEBUG_CFLAGS) \
- $(GTK_CFLAGS)
+ $(GTK_CFLAGS) \
+ $(FARSIGHT_CFLAGS)
diff --git a/pidgin/plugins/musicmessaging/Makefile.am b/pidgin/plugins/musicmessaging/Makefile.am
index 9dea5b045f..f67f2bacaa 100644
--- a/pidgin/plugins/musicmessaging/Makefile.am
+++ b/pidgin/plugins/musicmessaging/Makefile.am
@@ -40,5 +40,6 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libpurple \
-I$(top_srcdir)/pidgin \
$(DEBUG_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(GTK_CFLAGS) \
$(DBUS_CFLAGS)
diff --git a/pidgin/plugins/ticker/Makefile.am b/pidgin/plugins/ticker/Makefile.am
index ad137b6c71..5fb4358e91 100644
--- a/pidgin/plugins/ticker/Makefile.am
+++ b/pidgin/plugins/ticker/Makefile.am
@@ -24,4 +24,5 @@ AM_CPPFLAGS = \
-I$(top_builddir)/libpurple \
-I$(top_srcdir)/pidgin \
$(DEBUG_CFLAGS) \
+ $(FARSIGHT_CFLAGS) \
$(GTK_CFLAGS)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7f4d6a8f8b..e711b72934 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,6 +8,7 @@ finch/gntconv.c
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/auth.c
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