# HG changeset patch # User Mike Ruprecht # Date 1237764822 0 # Node ID 7b63af454f266c86be927192e09c0cbbd0c9c6e0 # Parent 399776a9ad98278e69af7340b31943dc3e6ca25c# Parent cd41e1655a3ed39571f5a239e41db8c8086ec4d7 propagate from branch 'im.pidgin.pidgin' (head 5592cb6a8b667422747bafd555fea0aed19931b6) to branch 'im.pidgin.pidgin.vv' (head ff94dfb7c44b84f5d9b4590cc429d93198703b83) diff -r 399776a9ad98 -r 7b63af454f26 ChangeLog.API --- a/ChangeLog.API Sat Mar 21 02:20:52 2009 +0000 +++ b/ChangeLog.API Sun Mar 22 23:33:42 2009 +0000 @@ -1,5 +1,11 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version ?.?.? (??/??/????): + libpurple: + Added: + * PurpleMedia API + * xmlnode_get_parent + version 2.6.0 (??/??/2009): libpurple: Added: diff -r 399776a9ad98 -r 7b63af454f26 Doxyfile.in --- a/Doxyfile.in Sat Mar 21 02:20:52 2009 +0000 +++ b/Doxyfile.in Sun Mar 22 23:33:42 2009 +0000 @@ -1070,7 +1070,7 @@ # undefined via #undef or recursively expanded use the := operator # instead of the = operator. -PREDEFINED = +PREDEFINED = USE_VV # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. diff -r 399776a9ad98 -r 7b63af454f26 configure.ac --- a/configure.ac Sat Mar 21 02:20:52 2009 +0000 +++ b/configure.ac Sun Mar 22 23:33:42 2009 +0000 @@ -47,7 +47,7 @@ m4_define([purple_major_version], [2]) m4_define([purple_minor_version], [6]) m4_define([purple_micro_version], [0]) -m4_define([purple_version_suffix], [devel]) +m4_define([purple_version_suffix], [vv-devel]) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix])) @@ -56,7 +56,7 @@ m4_define([gnt_major_version], [2]) m4_define([gnt_minor_version], [6]) m4_define([gnt_micro_version], [0]) -m4_define([gnt_version_suffix], [devel]) +m4_define([gnt_version_suffix], [vv-devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version]) m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt_version_suffix])) @@ -324,6 +324,9 @@ AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) +GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0` +AC_SUBST(GLIB_GENMARSHAL) + AC_ARG_WITH([extraversion], AC_HELP_STRING([--with-extraversion=STRING], [extra version number to be displayed in Help->About and --help (for packagers)]), @@ -745,6 +748,56 @@ fi dnl ####################################################################### +dnl # Check for Farsight +dnl ####################################################################### +AC_ARG_ENABLE(farsight, + [AC_HELP_STRING([--disable-farsight], [compile without farsight support])], + enable_farsight="$enableval", enable_farsight="yes") +if test "x$enable_farsight" != "xno"; then + PKG_CHECK_MODULES(FARSIGHT, [farsight2-0.10 >= 0.0.3 gstreamer-0.10 gstreamer-plugins-base-0.10 libxml-2.0], [ + AC_DEFINE(USE_FARSIGHT, 1, [Use Farsight for voice and video]) + AC_SUBST(FARSIGHT_CFLAGS) + AC_SUBST(FARSIGHT_LIBS) + ], [ + enable_farsight="no" + ]) +fi + +dnl ####################################################################### +dnl # Check for GStreamer-properties +dnl ####################################################################### +AC_ARG_ENABLE(gstprops, + [AC_HELP_STRING([--disable-gstprops], [compile without gstreamer props])], + enable_gstprops="$enableval", enable_gstprops="yes") +if test "x$enable_gstprops" != "xno"; +then + dnl gstreamer-libs-$GST_MAJORMINOR + dnl gstreamer-gconf-$GST_MAJORMINOR + PKG_CHECK_MODULES(GSTPROPS, [gstreamer-0.10 gstreamer-plugins-base-0.10 libxml-2.0], [ + GSTPROPS_LIBS="$GSTPROPS_LIBS -lgstinterfaces-0.10" + AC_DEFINE(USE_GSTPROPS, 1, [Use GStreamer property probe for finding devices]) + AC_SUBST(GSTPROPS_LIBS) + AC_SUBST(GSTPROPS_CFLAGS) + ], [ + enable_gstprops="no" + ]) +fi + +dnl ####################################################################### +dnl # Check for Voice and Video support +dnl ####################################################################### +AC_ARG_ENABLE(vv, + [AC_HELP_STRING([--disable-vv], [compile without voice and video support])], + enable_vv="$enableval", enable_vv="yes") +if test "x$enable_vv" != "xno"; then + if test "x$enable_farsight" != "xno" -a "x$enable_gstprops" != "xno"; then + AC_DEFINE(USE_VV, 1, [Use voice and video]) + else + enable_vv="no" + fi +fi + +dnl ####################################################################### dnl # Check for Meanwhile headers (for Sametime) dnl ####################################################################### AC_ARG_ENABLE(meanwhile, @@ -2482,6 +2535,7 @@ echo echo Build with GStreamer support.. : $enable_gst echo Build with D-Bus support...... : $enable_dbus +echo Build with voice and video.... : $enable_vv if test "x$enable_dbus" = "xyes" ; then eval eval echo D-Bus services directory...... : $DBUS_SERVICES_DIR fi diff -r 399776a9ad98 -r 7b63af454f26 finch/Makefile.am --- a/finch/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -26,6 +26,7 @@ finch.c \ gntidle.c \ gntlog.c \ + gntmedia.c \ gntnotify.c \ gntplugin.c \ gntpounce.c \ @@ -47,6 +48,7 @@ finch.h \ gntidle.h \ gntlog.h \ + gntmedia.h \ gntnotify.h \ gntplugin.h \ gntpounce.h \ @@ -69,6 +71,7 @@ $(INTLLIBS) \ $(GLIB_LIBS) \ $(LIBXML_LIBS) \ + $(FARSIGHT_LIBS) \ $(GNT_LIBS) \ $(GSTREAMER_LIBS) \ ./libgnt/libgnt.la \ @@ -88,5 +91,6 @@ $(GLIB_CFLAGS) \ $(DBUS_CFLAGS) \ $(LIBXML_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GSTREAMER_CFLAGS) \ $(GNT_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 finch/gntaccount.c --- a/finch/gntaccount.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/gntaccount.c Sun Mar 22 23:33:42 2009 +0000 @@ -1099,3 +1099,4 @@ return &ui_ops; } + diff -r 399776a9ad98 -r 7b63af454f26 finch/gntdebug.c --- a/finch/gntdebug.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/gntdebug.c Sun Mar 22 23:33:42 2009 +0000 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include "util.h" + #include #include #include @@ -36,7 +38,6 @@ #include "gntdebug.h" #include "finch.h" #include "notify.h" -#include "util.h" #include #include @@ -386,6 +387,13 @@ #ifdef USE_GSTREAMER REGISTER_G_LOG_HANDLER("GStreamer"); #endif +#ifdef USE_VV +#ifdef USE_FARSIGHT + REGISTER_G_LOG_HANDLER("farsight"); + REGISTER_G_LOG_HANDLER("farsight-transmitter"); + REGISTER_G_LOG_HANDLER("farsight-rtp"); +#endif +#endif REGISTER_G_LOG_HANDLER("stderr"); g_set_print_handler(print_stderr); /* Redirect the debug messages to stderr */ diff -r 399776a9ad98 -r 7b63af454f26 finch/gntft.c --- a/finch/gntft.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/gntft.c Sun Mar 22 23:33:42 2009 +0000 @@ -25,6 +25,12 @@ */ #include "finch.h" +#include "debug.h" +#include "notify.h" +#include "ft.h" +#include "prpl.h" +#include "util.h" + #include #include #include @@ -32,12 +38,6 @@ #include #include -#include "debug.h" -#include "notify.h" -#include "ft.h" -#include "prpl.h" -#include "util.h" - #include "gntft.h" #include "prefs.h" diff -r 399776a9ad98 -r 7b63af454f26 finch/gntmedia.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntmedia.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,441 @@ +/** + * @file gntmedia.c GNT Media API + * @ingroup finch + */ + +/* finch + * + * Finch is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "finch.h" +#include "mediamanager.h" + +#include "gntconv.h" +#include "gntmedia.h" + +#include "gnt.h" +#include "gntbutton.h" +#include "gntbox.h" +#include "gntlabel.h" + +#include "cmds.h" +#include "conversation.h" +#include "debug.h" + +/* An incredibly large part of the following is from gtkmedia.c */ +#ifdef USE_VV + +#undef hangup + +struct _FinchMediaPrivate +{ + PurpleMedia *media; + + GntWidget *accept; + GntWidget *reject; + GntWidget *hangup; + GntWidget *calling; + + PurpleConversation *conv; +}; + +#define FINCH_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), FINCH_TYPE_MEDIA, FinchMediaPrivate)) + +static void finch_media_class_init (FinchMediaClass *klass); +static void finch_media_init (FinchMedia *media); +static void finch_media_finalize (GObject *object); +static void finch_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void finch_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); + +static GntBoxClass *parent_class = NULL; + +enum { + MESSAGE, + LAST_SIGNAL +}; +static guint finch_media_signals[LAST_SIGNAL] = {0}; + +enum { + PROP_0, + PROP_MEDIA, +}; + +GType +finch_media_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(FinchMediaClass), + NULL, + NULL, + (GClassInitFunc) finch_media_class_init, + NULL, + NULL, + sizeof(FinchMedia), + 0, + (GInstanceInitFunc) finch_media_init, + NULL + }; + type = g_type_register_static(GNT_TYPE_BOX, "FinchMedia", &info, 0); + } + return type; +} + + +static void +finch_media_class_init (FinchMediaClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = finch_media_finalize; + gobject_class->set_property = finch_media_set_property; + gobject_class->get_property = finch_media_get_property; + + g_object_class_install_property(gobject_class, PROP_MEDIA, + g_param_spec_object("media", + "PurpleMedia", + "The PurpleMedia associated with this media.", + PURPLE_TYPE_MEDIA, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + finch_media_signals[MESSAGE] = g_signal_new("message", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + g_type_class_add_private(klass, sizeof(FinchMediaPrivate)); +} + + +static void +finch_media_init (FinchMedia *media) +{ + media->priv = FINCH_MEDIA_GET_PRIVATE(media); + + media->priv->calling = gnt_label_new(_("Calling ... ")); + media->priv->hangup = gnt_button_new(_("Hangup")); + media->priv->accept = gnt_button_new(_("Accept")); + media->priv->reject = gnt_button_new(_("Reject")); + + gnt_box_set_alignment(GNT_BOX(media), GNT_ALIGN_MID); + + gnt_box_add_widget(GNT_BOX(media), media->priv->accept); + gnt_box_add_widget(GNT_BOX(media), media->priv->reject); +} + +static void +finch_media_finalize (GObject *media) +{ + FinchMedia *gntmedia = FINCH_MEDIA(media); + purple_debug_info("gntmedia", "finch_media_finalize\n"); + if (gntmedia->priv->media) + g_object_unref(gntmedia->priv->media); +} + +static void +finch_media_emit_message(FinchMedia *gntmedia, const char *msg) +{ + g_signal_emit(gntmedia, finch_media_signals[MESSAGE], 0, msg); +} + +static void +finch_media_accept_cb(PurpleMedia *media, FinchMedia *gntmedia) +{ + GntWidget *parent; + + finch_media_emit_message(gntmedia, _("Call in progress.")); + + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->accept); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->reject); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->hangup); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->calling); + + gnt_box_add_widget(GNT_BOX(gntmedia), gntmedia->priv->hangup); + + gnt_widget_destroy(gntmedia->priv->accept); + gnt_widget_destroy(gntmedia->priv->reject); + gnt_widget_destroy(gntmedia->priv->calling); + gntmedia->priv->accept = NULL; + gntmedia->priv->reject = NULL; + gntmedia->priv->calling = NULL; + + parent = GNT_WIDGET(gntmedia); + while (parent->parent) + parent = parent->parent; + gnt_box_readjust(GNT_BOX(parent)); + gnt_widget_draw(parent); +} + +static void +finch_media_wait_cb(PurpleMedia *media, FinchMedia *gntmedia) +{ + GntWidget *parent; + + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->accept); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->reject); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->hangup); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->calling); + + gnt_box_add_widget(GNT_BOX(gntmedia), gntmedia->priv->calling); + gnt_box_add_widget(GNT_BOX(gntmedia), gntmedia->priv->hangup); + + parent = GNT_WIDGET(gntmedia); + while (parent->parent) + parent = parent->parent; + gnt_box_readjust(GNT_BOX(parent)); + gnt_widget_draw(parent); +} + +static void +finch_media_state_changed_cb(PurpleMedia *media, PurpleMediaState state, + gchar *sid, gchar *name, FinchMedia *gntmedia) +{ + purple_debug_info("gntmedia", "state: %d sid: %s name: %s\n", + state, sid, name); + if (sid == NULL && name == NULL) { + if (state == PURPLE_MEDIA_STATE_END) { + finch_media_emit_message(gntmedia, + _("The call has been terminated.")); + finch_conversation_set_info_widget( + gntmedia->priv->conv, NULL); + gnt_widget_destroy(GNT_WIDGET(gntmedia)); + /* + * XXX: This shouldn't have to be here + * to free the FinchMedia widget. + */ + g_object_unref(gntmedia); + } + } else if (state == PURPLE_MEDIA_STATE_CONNECTED) { + finch_media_accept_cb(media, gntmedia); + } +} + +static void +finch_media_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, + gchar *sid, gchar *name, FinchMedia *gntmedia) +{ + if (type == PURPLE_MEDIA_INFO_REJECT) { + finch_media_emit_message(gntmedia, + _("You have rejected the call.")); + } +} + +static void +finch_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + FinchMedia *media; + g_return_if_fail(FINCH_IS_MEDIA(object)); + + media = FINCH_MEDIA(object); + switch (prop_id) { + case PROP_MEDIA: + { + gboolean is_initiator; + if (media->priv->media) + g_object_unref(media->priv->media); + media->priv->media = g_value_get_object(value); + g_object_ref(media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->accept), "activate", + G_CALLBACK(purple_media_accept), media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->reject), "activate", + G_CALLBACK(purple_media_reject), media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->hangup), "activate", + G_CALLBACK(purple_media_hangup), media->priv->media); + + g_object_get(G_OBJECT(media->priv->media), "initiator", + &is_initiator, NULL); + if (is_initiator == TRUE) { + finch_media_wait_cb(media->priv->media, media); + } + g_signal_connect(G_OBJECT(media->priv->media), "state-changed", + G_CALLBACK(finch_media_state_changed_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "stream-info", + G_CALLBACK(finch_media_stream_info_cb), media); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finch_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + FinchMedia *media; + g_return_if_fail(FINCH_IS_MEDIA(object)); + + media = FINCH_MEDIA(object); + + switch (prop_id) { + case PROP_MEDIA: + g_value_set_object(value, media->priv->media); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +GntWidget * +finch_media_new(PurpleMedia *media) +{ + return GNT_WIDGET(g_object_new(finch_media_get_type(), + "media", media, + "vertical", FALSE, + "homogeneous", FALSE, + NULL)); +} + +static void +gntmedia_message_cb(FinchMedia *gntmedia, const char *msg, PurpleConversation *conv) +{ + if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { + purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL)); + } +} + +static gboolean +finch_new_media(PurpleMediaManager *manager, PurpleMedia *media, + PurpleConnection *gc, gchar *name, gpointer null) +{ + GntWidget *gntmedia; + PurpleConversation *conv; + + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, + purple_connection_get_account(gc), name); + + gntmedia = finch_media_new(media); + g_signal_connect(G_OBJECT(gntmedia), "message", G_CALLBACK(gntmedia_message_cb), conv); + FINCH_MEDIA(gntmedia)->priv->conv = conv; + finch_conversation_set_info_widget(conv, gntmedia); + return TRUE; +} + +static PurpleCmdRet +call_cmd_cb(PurpleConversation *conv, const char *cmd, char **args, + char **eror, gpointer data) +{ + PurpleAccount *account = purple_conversation_get_account(conv); + + PurpleMedia *media = purple_prpl_initiate_media(account, + purple_conversation_get_name(conv), + PURPLE_MEDIA_AUDIO); + + if (!media) + return PURPLE_CMD_STATUS_FAILED; + + return PURPLE_CMD_STATUS_OK; +} + +static GstElement * +create_default_audio_src(void) +{ + GstElement *bin, *src, *volume; + GstPad *pad, *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; + + bin = 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); + gst_bin_add_many(GST_BIN(bin), src, volume, NULL); + gst_element_link(src, volume); + pad = gst_element_get_pad(volume, "src"); + ghost = gst_ghost_pad_new("ghostsrc", pad); + gst_element_add_pad(bin, ghost); + + if (audio_device != NULL && strcmp(audio_device, "")) + g_object_set(G_OBJECT(src), "device", audio_device, NULL); + + return bin; +} + +static GstElement * +create_default_audio_sink(void) +{ + GstElement *bin, *sink, *volume, *queue; + GstPad *pad, *ghost; + double output_volume = purple_prefs_get_int( + "/purple/media/audio/volume/output")/10.0; + + bin = gst_bin_new("pidginrecvaudiobin"); + sink = gst_element_factory_make("alsasink", "asink"); + g_object_set(G_OBJECT(sink), "async", FALSE, "sync", FALSE, NULL); + volume = gst_element_factory_make("volume", "purpleaudiooutputvolume"); + g_object_set(volume, "volume", output_volume, NULL); + queue = gst_element_factory_make("queue", NULL); + gst_bin_add_many(GST_BIN(bin), sink, volume, queue, NULL); + gst_element_link(volume, sink); + gst_element_link(queue, volume); + pad = gst_element_get_pad(queue, "sink"); + ghost = gst_ghost_pad_new("ghostsink", pad); + gst_element_add_pad(bin, ghost); + + return bin; +} + +static PurpleMediaElementInfo default_audio_src = +{ + "finchdefaultaudiosrc", /* id */ + PURPLE_MEDIA_ELEMENT_AUDIO /* type */ + | PURPLE_MEDIA_ELEMENT_SRC + | PURPLE_MEDIA_ELEMENT_ONE_SRC + | PURPLE_MEDIA_ELEMENT_UNIQUE, + create_default_audio_src, /* create */ +}; + +static PurpleMediaElementInfo default_audio_sink = +{ + "finchdefaultaudiosink", /* id */ + PURPLE_MEDIA_ELEMENT_AUDIO /* type */ + | PURPLE_MEDIA_ELEMENT_SINK + | PURPLE_MEDIA_ELEMENT_ONE_SINK, + create_default_audio_sink, /* create */ +}; + +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); + + purple_debug_info("gntmedia", "Registering media element types\n"); + purple_media_manager_set_active_element(manager, &default_audio_src); + purple_media_manager_set_active_element(manager, &default_audio_sink); +} + +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 -r 399776a9ad98 -r 7b63af454f26 finch/gntmedia.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntmedia.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,77 @@ +/** + * @file gntmedia.h GNT Media API + * @ingroup finch + */ + +/* finch + * + * Finch is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef GNT_MEDIA_H +#define GNT_MEDIA_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef USE_VV + +#include +#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 -r 399776a9ad98 -r 7b63af454f26 finch/gntnotify.c --- a/finch/gntnotify.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/gntnotify.c Sun Mar 22 23:33:42 2009 +0000 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include + #include #include #include @@ -33,7 +35,6 @@ #include "finch.h" -#include #include "gntnotify.h" #include "debug.h" diff -r 399776a9ad98 -r 7b63af454f26 finch/gntplugin.c --- a/finch/gntplugin.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/gntplugin.c Sun Mar 22 23:33:42 2009 +0000 @@ -23,6 +23,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include "notify.h" +#include "request.h" + #include #include #include @@ -34,8 +37,6 @@ #include "finch.h" #include "debug.h" -#include "notify.h" -#include "request.h" #include "gntplugin.h" #include "gntrequest.h" diff -r 399776a9ad98 -r 7b63af454f26 finch/gntpounce.c --- a/finch/gntpounce.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/gntpounce.c Sun Mar 22 23:33:42 2009 +0000 @@ -24,6 +24,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ +#include "internal.h" +#include "account.h" +#include "conversation.h" +#include "debug.h" +#include "notify.h" +#include "prpl.h" +#include "request.h" +#include "server.h" +#include "util.h" + #include #include #include @@ -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 -r 399776a9ad98 -r 7b63af454f26 finch/gntrequest.c --- a/finch/gntrequest.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/gntrequest.c Sun Mar 22 23:33:42 2009 +0000 @@ -23,6 +23,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include "util.h" + #include #include #include diff -r 399776a9ad98 -r 7b63af454f26 finch/gntstatus.c --- a/finch/gntstatus.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/gntstatus.c Sun Mar 22 23:33:42 2009 +0000 @@ -23,6 +23,10 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include +#include + #include #include #include @@ -35,9 +39,6 @@ #include "finch.h" -#include -#include - #include "gntstatus.h" static struct diff -r 399776a9ad98 -r 7b63af454f26 finch/gntui.c --- a/finch/gntui.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/gntui.c Sun Mar 22 23:33:42 2009 +0000 @@ -19,9 +19,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #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 +#include "gntui.h" void gnt_ui_init() { @@ -91,6 +92,11 @@ finch_roomlist_init(); purple_roomlist_set_ui_ops(finch_roomlist_get_ui_ops()); +#ifdef USE_VV + /* Media */ + finch_media_manager_init(); +#endif + gnt_register_action(_("Accounts"), finch_accounts_show_all); gnt_register_action(_("Buddy List"), finch_blist_show); gnt_register_action(_("Buddy Pounces"), finch_pounces_manager_show); @@ -136,6 +142,10 @@ finch_roomlist_uninit(); purple_roomlist_set_ui_ops(NULL); +#ifdef USE_VV + finch_media_manager_uninit(); +#endif + gnt_quit(); #endif } diff -r 399776a9ad98 -r 7b63af454f26 finch/libgnt/Makefile.am --- a/finch/libgnt/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/libgnt/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -91,6 +91,7 @@ AM_CPPFLAGS = \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GNT_CFLAGS) \ $(DEBUG_CFLAGS) \ $(LIBXML_CFLAGS) \ diff -r 399776a9ad98 -r 7b63af454f26 finch/libgnt/gntkeys.h --- a/finch/libgnt/gntkeys.h Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/libgnt/gntkeys.h Sun Mar 22 23:33:42 2009 +0000 @@ -165,5 +165,6 @@ #undef lines #undef buttons #undef newline +#undef set_clock #endif diff -r 399776a9ad98 -r 7b63af454f26 finch/libgnt/wms/Makefile.am --- a/finch/libgnt/wms/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/libgnt/wms/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -36,5 +36,6 @@ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(GNT_CFLAGS) \ - $(PLUGIN_CFLAGS) + $(PLUGIN_CFLAGS) \ + $(FARSIGHT_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 finch/libgnt/wms/s.c --- a/finch/libgnt/wms/s.c Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/libgnt/wms/s.c Sun Mar 22 23:33:42 2009 +0000 @@ -2,6 +2,7 @@ #include #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 -r 399776a9ad98 -r 7b63af454f26 finch/plugins/Makefile.am --- a/finch/plugins/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/finch/plugins/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -42,6 +42,7 @@ -I$(top_srcdir)/finch \ -I$(top_srcdir)/finch/libgnt \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GLIB_CFLAGS) \ $(GNT_CFLAGS) \ $(PLUGIN_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/Makefile.am --- a/libpurple/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -1,6 +1,7 @@ EXTRA_DIST = \ dbus-analyze-functions.py \ dbus-analyze-types.py \ + marshallers.list \ purple-notifications-example \ purple-remote \ purple-send \ @@ -51,6 +52,9 @@ idle.c \ imgstore.c \ log.c \ + marshallers.c \ + media.c \ + mediamanager.c \ mime.c \ nat-pmp.c \ network.c \ @@ -109,6 +113,9 @@ idle.h \ imgstore.h \ log.h \ + marshallers.h \ + media.h \ + mediamanager.h \ mime.h \ nat-pmp.h \ network.h \ @@ -147,6 +154,15 @@ purple_builtheaders = purple.h version.h +marshallers.h: marshallers.list + @echo "Generating marshallers.h" + $(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --header > marshallers.h + +marshallers.c: marshallers.list marshallers.h + @echo "Generating marshallers.c" + echo "#include \"marshallers.h\"" > marshallers.c + $(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --body >> marshallers.c + if ENABLE_DBUS CLEANFILES = \ @@ -155,6 +171,8 @@ dbus-client-binding.h \ dbus-types.c \ dbus-types.h \ + marshallers.c \ + marshallers.h \ purple-client-bindings.c \ purple-client-bindings.h \ purple.service @@ -225,6 +243,8 @@ dbus-types.c \ dbus-types.h \ dbus-bindings.c \ + marshallers.c \ + marshallers.h \ purple-client-bindings.c \ purple-client-bindings.h @@ -258,6 +278,9 @@ $(LIBXML_LIBS) \ $(NETWORKMANAGER_LIBS) \ $(INTLLIBS) \ + $(FARSIGHT_LIBS) \ + $(GSTREAMER_LIBS) \ + $(GSTPROPS_LIBS) \ -lm AM_CPPFLAGS = \ @@ -270,6 +293,9 @@ $(DEBUG_CFLAGS) \ $(DBUS_CFLAGS) \ $(LIBXML_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(GSTPROPS_CFLAGS) \ $(NETWORKMANAGER_CFLAGS) # INSTALL_SSL_CERTIFICATES is true when SSL_CERTIFICATES_DIR is empty. diff -r 399776a9ad98 -r 7b63af454f26 libpurple/example/Makefile.am --- a/libpurple/example/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/example/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -8,6 +8,7 @@ $(INTLLIBS) \ $(GLIB_LIBS) \ $(LIBXML_LIBS) \ + $(FARSIGHT_LIBS) \ $(top_builddir)/libpurple/libpurple.la AM_CPPFLAGS = \ @@ -23,4 +24,5 @@ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(DBUS_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(LIBXML_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/marshallers.list --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/marshallers.list Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,5 @@ +VOID:BOXED,BOXED +VOID:POINTER,POINTER,OBJECT +BOOLEAN:OBJECT,POINTER,STRING +VOID:STRING,STRING +VOID:ENUM,STRING,STRING diff -r 399776a9ad98 -r 7b63af454f26 libpurple/media.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/media.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,2194 @@ +/** + * @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 + +#include "internal.h" + +#include "connection.h" +#include "media.h" +#include "marshallers.h" +#include "mediamanager.h" +#include "network.h" + +#include "debug.h" + +#ifdef USE_VV + +#include + +/** @copydoc _PurpleMediaSession */ +typedef struct _PurpleMediaSession PurpleMediaSession; +/** @copydoc _PurpleMediaStream */ +typedef struct _PurpleMediaStream PurpleMediaStream; + +struct _PurpleMediaSession +{ + gchar *id; + PurpleMedia *media; + GstElement *src; + GstElement *tee; + FsSession *session; + + PurpleMediaSessionType type; + + gulong window_id; +}; + +struct _PurpleMediaStream +{ + PurpleMediaSession *session; + gchar *participant; + FsStream *stream; + GstElement *src; + GstElement *tee; + + GList *local_candidates; + GList *remote_candidates; + + gboolean accepted; + gboolean candidates_prepared; + + GList *active_local_candidates; + GList *active_remote_candidates; + + gulong window_id; + guint connected_cb_id; +}; + +struct _PurpleMediaPrivate +{ + PurpleMediaManager *manager; + PurpleConnection *pc; + FsConference *conference; + gboolean initiator; + gpointer prpl_data; + + GHashTable *sessions; /* PurpleMediaSession table */ + GHashTable *participants; /* FsParticipant table */ + + GList *streams; /* PurpleMediaStream table */ + + GstElement *confbin; +}; + +#define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate)) + +static void purple_media_class_init (PurpleMediaClass *klass); +static void purple_media_init (PurpleMedia *media); +static void purple_media_dispose (GObject *object); +static void purple_media_finalize (GObject *object); +static void purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); + +static void purple_media_new_local_candidate_cb(FsStream *stream, + FsCandidate *local_candidate, PurpleMediaSession *session); +static void purple_media_candidates_prepared_cb(FsStream *stream, + PurpleMediaSession *session); +static void purple_media_candidate_pair_established_cb(FsStream *stream, + FsCandidate *native_candidate, FsCandidate *remote_candidate, + PurpleMediaSession *session); +static gboolean media_bus_call(GstBus *bus, + GstMessage *msg, PurpleMedia *media); + +static GObjectClass *parent_class = NULL; + + + +enum { + ERROR, + ACCEPTED, + CANDIDATES_PREPARED, + CODECS_CHANGED, + NEW_CANDIDATE, + STATE_CHANGED, + STREAM_INFO, + LAST_SIGNAL +}; +static guint purple_media_signals[LAST_SIGNAL] = {0}; + +enum { + PROP_0, + PROP_MANAGER, + PROP_CONNECTION, + PROP_CONFERENCE, + PROP_INITIATOR, + PROP_PRPL_DATA, +}; + +GType +purple_media_get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(PurpleMediaClass), + NULL, + NULL, + (GClassInitFunc) purple_media_class_init, + NULL, + NULL, + sizeof(PurpleMedia), + 0, + (GInstanceInitFunc) purple_media_init, + NULL + }; + type = g_type_register_static(G_TYPE_OBJECT, "PurpleMedia", &info, 0); + } + return type; +} + +GType +purple_media_state_changed_get_type() +{ + static GType type = 0; + if (type == 0) { + static const GEnumValue values[] = { + { PURPLE_MEDIA_STATE_NEW, + "PURPLE_MEDIA_STATE_NEW", "new" }, + { PURPLE_MEDIA_STATE_CONNECTED, + "PURPLE_MEDIA_STATE_CONNECTED", "connected" }, + { PURPLE_MEDIA_STATE_END, + "PURPLE_MEDIA_STATE_END", "end" }, + { 0, NULL, NULL } + }; + type = g_enum_register_static("PurpleMediaState", values); + } + return type; +} + +GType +purple_media_info_type_get_type() +{ + static GType type = 0; + if (type == 0) { + static const GEnumValue values[] = { + { PURPLE_MEDIA_INFO_HANGUP, + "PURPLE_MEDIA_INFO_HANGUP", "hangup" }, + { PURPLE_MEDIA_INFO_REJECT, + "PURPLE_MEDIA_INFO_REJECT", "reject" }, + { PURPLE_MEDIA_INFO_MUTE, + "PURPLE_MEDIA_INFO_MUTE", "mute" }, + { PURPLE_MEDIA_INFO_HOLD, + "PURPLE_MEDIA_INFO_HOLD", "hold" }, + { 0, NULL, NULL } + }; + type = g_enum_register_static("PurpleMediaInfoType", values); + } + return type; +} + +static void +purple_media_class_init (PurpleMediaClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->dispose = purple_media_dispose; + gobject_class->finalize = purple_media_finalize; + gobject_class->set_property = purple_media_set_property; + gobject_class->get_property = purple_media_get_property; + + g_object_class_install_property(gobject_class, PROP_MANAGER, + g_param_spec_object("manager", + "Purple Media Manager", + "The media manager that contains this media session.", + PURPLE_TYPE_MEDIA_MANAGER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_CONNECTION, + g_param_spec_pointer("connection", + "PurpleConnection", + "The connection this media session is on.", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_CONFERENCE, + g_param_spec_object("conference", + "Farsight conference", + "The FsConference associated with this media.", + FS_TYPE_CONFERENCE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_INITIATOR, + g_param_spec_boolean("initiator", + "initiator", + "If the local user initiated the conference.", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_PRPL_DATA, + g_param_spec_pointer("prpl-data", + "gpointer", + "Data the prpl plugin set on the media session.", + G_PARAM_READWRITE)); + + purple_media_signals[ERROR] = g_signal_new("error", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + purple_media_signals[ACCEPTED] = g_signal_new("accepted", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + purple_smarshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + purple_media_signals[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[CODECS_CHANGED] = g_signal_new("codecs-changed", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + purple_media_signals[NEW_CANDIDATE] = g_signal_new("new-candidate", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + purple_smarshal_VOID__POINTER_POINTER_OBJECT, + G_TYPE_NONE, 3, G_TYPE_POINTER, + G_TYPE_POINTER, PURPLE_TYPE_MEDIA_CANDIDATE); + purple_media_signals[STATE_CHANGED] = g_signal_new("state-changed", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + purple_smarshal_VOID__ENUM_STRING_STRING, + G_TYPE_NONE, 3, PURPLE_MEDIA_TYPE_STATE, + G_TYPE_STRING, G_TYPE_STRING); + purple_media_signals[STREAM_INFO] = g_signal_new("stream-info", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + purple_smarshal_VOID__ENUM_STRING_STRING, + G_TYPE_NONE, 3, PURPLE_MEDIA_TYPE_INFO_TYPE, + G_TYPE_STRING, G_TYPE_STRING); + g_type_class_add_private(klass, sizeof(PurpleMediaPrivate)); +} + + +static void +purple_media_init (PurpleMedia *media) +{ + media->priv = PURPLE_MEDIA_GET_PRIVATE(media); + memset(media->priv, 0, sizeof(*media->priv)); +} + +static void +purple_media_stream_free(PurpleMediaStream *stream) +{ + if (stream == NULL) + return; + + /* Remove the connected_cb timeout */ + if (stream->connected_cb_id != 0) + purple_timeout_remove(stream->connected_cb_id); + + g_free(stream->participant); + + if (stream->local_candidates) + fs_candidate_list_destroy(stream->local_candidates); + if (stream->remote_candidates) + fs_candidate_list_destroy(stream->remote_candidates); + + if (stream->active_local_candidates) + fs_candidate_list_destroy(stream->active_local_candidates); + if (stream->active_remote_candidates) + fs_candidate_list_destroy(stream->active_remote_candidates); + + g_free(stream); +} + +static void +purple_media_session_free(PurpleMediaSession *session) +{ + if (session == NULL) + return; + + g_free(session->id); + g_free(session); +} + +static void +purple_media_dispose(GObject *media) +{ + PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media); + GList *iter = NULL; + + purple_debug_info("media","purple_media_dispose\n"); + + purple_media_manager_remove_media(priv->manager, PURPLE_MEDIA(media)); + + if (priv->confbin) { + gst_element_set_locked_state(priv->confbin, TRUE); + gst_element_set_state(GST_ELEMENT(priv->confbin), + GST_STATE_NULL); + gst_bin_remove(GST_BIN(purple_media_manager_get_pipeline( + priv->manager)), priv->confbin); + priv->confbin = NULL; + priv->conference = NULL; + } + + for (iter = priv->streams; iter; iter = g_list_next(iter)) { + PurpleMediaStream *stream = iter->data; + if (stream->stream) { + g_object_unref(stream->stream); + stream->stream = NULL; + } + } + + if (priv->sessions) { + GList *sessions = g_hash_table_get_values(priv->sessions); + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + PurpleMediaSession *session = sessions->data; + if (session->session) { + g_object_unref(session->session); + session->session = NULL; + } + } + } + + if (priv->participants) { + GList *participants = g_hash_table_get_values(priv->participants); + for (; participants; participants = g_list_delete_link(participants, participants)) + g_object_unref(participants->data); + } + + if (priv->manager) { + GstElement *pipeline = purple_media_manager_get_pipeline( + priv->manager); + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + g_signal_handlers_disconnect_matched(G_OBJECT(bus), + G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, + 0, 0, 0, media_bus_call, media); + gst_object_unref(bus); + + g_object_unref(priv->manager); + priv->manager = NULL; + } + + G_OBJECT_CLASS(parent_class)->dispose(media); +} + +static void +purple_media_finalize(GObject *media) +{ + PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media); + purple_debug_info("media","purple_media_finalize\n"); + + for (; priv->streams; priv->streams = g_list_delete_link(priv->streams, priv->streams)) + purple_media_stream_free(priv->streams->data); + + if (priv->sessions) { + GList *sessions = g_hash_table_get_values(priv->sessions); + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + purple_media_session_free(sessions->data); + } + g_hash_table_destroy(priv->sessions); + } + + G_OBJECT_CLASS(parent_class)->finalize(media); +} + +static void +purple_media_setup_pipeline(PurpleMedia *media) +{ + GstBus *bus; + gchar *name; + GstElement *pipeline; + + if (media->priv->conference == NULL || media->priv->manager == NULL) + return; + + pipeline = purple_media_manager_get_pipeline(media->priv->manager); + + name = g_strdup_printf("conf_%p", + media->priv->conference); + media->priv->confbin = gst_bin_new(name); + g_free(name); + + bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + g_signal_connect(G_OBJECT(bus), "message", + G_CALLBACK(media_bus_call), media); + gst_object_unref(bus); + + gst_bin_add(GST_BIN(pipeline), + media->priv->confbin); + gst_bin_add(GST_BIN(media->priv->confbin), + GST_ELEMENT(media->priv->conference)); + gst_element_set_state(GST_ELEMENT(media->priv->confbin), + GST_STATE_PLAYING); +} + +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_MANAGER: + media->priv->manager = g_value_get_object(value); + g_object_ref(media->priv->manager); + + purple_media_setup_pipeline(media); + break; + case PROP_CONNECTION: + media->priv->pc = g_value_get_pointer(value); + break; + case PROP_CONFERENCE: { + if (media->priv->conference) + gst_object_unref(media->priv->conference); + media->priv->conference = g_value_get_object(value); + gst_object_ref(media->priv->conference); + + purple_media_setup_pipeline(media); + break; + } + case PROP_INITIATOR: + media->priv->initiator = g_value_get_boolean(value); + break; + case PROP_PRPL_DATA: + media->priv->prpl_data = 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_MANAGER: + g_value_set_object(value, media->priv->manager); + break; + case PROP_CONNECTION: + g_value_set_pointer(value, media->priv->pc); + break; + case PROP_CONFERENCE: + g_value_set_object(value, media->priv->conference); + break; + case PROP_INITIATOR: + g_value_set_boolean(value, media->priv->initiator); + break; + case PROP_PRPL_DATA: + g_value_set_pointer(value, media->priv->prpl_data); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +PurpleMediaCandidate * +purple_media_candidate_new(const gchar *foundation, guint component_id, + PurpleMediaCandidateType type, + PurpleMediaNetworkProtocol proto, + const gchar *ip, guint port) +{ + PurpleMediaCandidate *candidate = g_new0(PurpleMediaCandidate, 1); + candidate->foundation = g_strdup(foundation); + candidate->component_id = component_id; + candidate->type = type; + candidate->proto = proto; + candidate->ip = g_strdup(ip); + candidate->port = port; + return candidate; +} + +PurpleMediaCandidate * +purple_media_candidate_copy(PurpleMediaCandidate *candidate) +{ + PurpleMediaCandidate *new_candidate; + + if (candidate == NULL) + return NULL; + + new_candidate = g_new0(PurpleMediaCandidate, 1); + new_candidate->foundation = g_strdup(candidate->foundation); + new_candidate->component_id = candidate->component_id; + new_candidate->ip = g_strdup(candidate->ip); + new_candidate->port = candidate->port; + new_candidate->base_ip = g_strdup(candidate->base_ip); + new_candidate->base_port = candidate->base_port; + new_candidate->proto = candidate->proto; + new_candidate->priority = candidate->priority; + new_candidate->type = candidate->type; + new_candidate->username = g_strdup(candidate->username); + new_candidate->password = g_strdup(candidate->password); + new_candidate->ttl = candidate->ttl; + return new_candidate; +} + +static void +purple_media_candidate_free(PurpleMediaCandidate *candidate) +{ + if (candidate == NULL) + return; + + g_free((gchar*)candidate->foundation); + g_free((gchar*)candidate->ip); + g_free((gchar*)candidate->base_ip); + g_free((gchar*)candidate->username); + g_free((gchar*)candidate->password); + g_free(candidate); +} + +static FsCandidate * +purple_media_candidate_to_fs(PurpleMediaCandidate *candidate) +{ + FsCandidate *fscandidate; + + if (candidate == NULL) + return NULL; + + fscandidate = fs_candidate_new(candidate->foundation, + candidate->component_id, candidate->type, + candidate->proto, candidate->ip, candidate->port); + + fscandidate->base_ip = g_strdup(candidate->base_ip); + fscandidate->base_port = candidate->base_port; + fscandidate->priority = candidate->priority; + fscandidate->username = g_strdup(candidate->username); + fscandidate->password = g_strdup(candidate->password); + fscandidate->ttl = candidate->ttl; + return fscandidate; +} + +static PurpleMediaCandidate * +purple_media_candidate_from_fs(FsCandidate *fscandidate) +{ + PurpleMediaCandidate *candidate; + + if (fscandidate == NULL) + return NULL; + + candidate = purple_media_candidate_new(fscandidate->foundation, + fscandidate->component_id, fscandidate->type, + fscandidate->proto, fscandidate->ip, fscandidate->port); + candidate->base_ip = g_strdup(fscandidate->base_ip); + candidate->base_port = fscandidate->base_port; + candidate->priority = fscandidate->priority; + candidate->username = g_strdup(fscandidate->username); + candidate->password = g_strdup(fscandidate->password); + candidate->ttl = fscandidate->ttl; + return candidate; +} + +static GList * +purple_media_candidate_list_from_fs(GList *candidates) +{ + GList *new_list = NULL; + + for (; candidates; candidates = g_list_next(candidates)) { + new_list = g_list_prepend(new_list, + purple_media_candidate_from_fs( + candidates->data)); + } + + new_list = g_list_reverse(new_list); + return new_list; +} + +static GList * +purple_media_candidate_list_to_fs(GList *candidates) +{ + GList *new_list = NULL; + + for (; candidates; candidates = g_list_next(candidates)) { + new_list = g_list_prepend(new_list, + purple_media_candidate_to_fs( + candidates->data)); + } + + new_list = g_list_reverse(new_list); + return new_list; +} + +GList * +purple_media_candidate_list_copy(GList *candidates) +{ + GList *new_list = NULL; + + for (; candidates; candidates = g_list_next(candidates)) { + new_list = g_list_prepend(new_list, g_boxed_copy( + PURPLE_TYPE_MEDIA_CANDIDATE, + candidates->data)); + } + + new_list = g_list_reverse(new_list); + return new_list; +} + +void +purple_media_candidate_list_free(GList *candidates) +{ + for (; candidates; candidates = + g_list_delete_link(candidates, candidates)) { + g_boxed_free(PURPLE_TYPE_MEDIA_CANDIDATE, + candidates->data); + } +} + +GType +purple_media_candidate_get_type() +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static("PurpleMediaCandidate", + (GBoxedCopyFunc)purple_media_candidate_copy, + (GBoxedFreeFunc)purple_media_candidate_free); + } + return type; +} + +static FsMediaType +purple_media_to_fs_media_type(PurpleMediaSessionType type) +{ + if (type & PURPLE_MEDIA_AUDIO) + return FS_MEDIA_TYPE_AUDIO; + else if (type & PURPLE_MEDIA_VIDEO) + return FS_MEDIA_TYPE_VIDEO; + else + return 0; +} + +static FsStreamDirection +purple_media_to_fs_stream_direction(PurpleMediaSessionType type) +{ + if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO || + (type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO) + return FS_DIRECTION_BOTH; + else if ((type & PURPLE_MEDIA_SEND_AUDIO) || + (type & PURPLE_MEDIA_SEND_VIDEO)) + return FS_DIRECTION_SEND; + else if ((type & PURPLE_MEDIA_RECV_AUDIO) || + (type & PURPLE_MEDIA_RECV_VIDEO)) + return FS_DIRECTION_RECV; + else + return FS_DIRECTION_NONE; +} + +static PurpleMediaSessionType +purple_media_from_fs(FsMediaType type, FsStreamDirection direction) +{ + PurpleMediaSessionType result = PURPLE_MEDIA_NONE; + if (type == FS_MEDIA_TYPE_AUDIO) { + if (direction & FS_DIRECTION_SEND) + result |= PURPLE_MEDIA_SEND_AUDIO; + if (direction & FS_DIRECTION_RECV) + result |= PURPLE_MEDIA_RECV_AUDIO; + } else if (type == FS_MEDIA_TYPE_VIDEO) { + if (direction & FS_DIRECTION_SEND) + result |= PURPLE_MEDIA_SEND_VIDEO; + if (direction & FS_DIRECTION_RECV) + result |= PURPLE_MEDIA_RECV_VIDEO; + } + return result; +} + +void +purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec, + const gchar *name, const gchar *value) +{ + PurpleMediaCodecParameter *new_param; + + g_return_if_fail(codec != NULL); + g_return_if_fail(name != NULL && value != NULL); + + new_param = g_new0(PurpleMediaCodecParameter, 1); + new_param->name = g_strdup(name); + new_param->value = g_strdup(value); + codec->optional_params = g_list_append( + codec->optional_params, new_param); +} + +void +purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec, + PurpleMediaCodecParameter *param) +{ + g_return_if_fail(codec != NULL && param != NULL); + + g_free(param->name); + g_free(param->value); + g_free(param); + + codec->optional_params = + g_list_remove(codec->optional_params, param); +} + +PurpleMediaCodecParameter * +purple_media_codec_get_optional_parameter(PurpleMediaCodec *codec, + const gchar *name, const gchar *value) +{ + GList *iter; + + g_return_val_if_fail(codec != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + for (iter = codec->optional_params; iter; iter = g_list_next(iter)) { + PurpleMediaCodecParameter *param = iter->data; + if (!g_ascii_strcasecmp(param->name, name) && + (value == NULL || + !g_ascii_strcasecmp(param->value, value))) + return param; + } + + return NULL; +} + +PurpleMediaCodec * +purple_media_codec_new(int id, const char *encoding_name, + PurpleMediaSessionType media_type, guint clock_rate) +{ + PurpleMediaCodec *codec = g_new0(PurpleMediaCodec, 1); + + codec->id = id; + codec->encoding_name = g_strdup(encoding_name); + codec->media_type = media_type; + codec->clock_rate = clock_rate; + return codec; +} + +PurpleMediaCodec * +purple_media_codec_copy(PurpleMediaCodec *codec) +{ + PurpleMediaCodec *new_codec; + GList *iter; + + if (codec == NULL) + return NULL; + + new_codec = purple_media_codec_new(codec->id, codec->encoding_name, + codec->media_type, codec->clock_rate); + new_codec->channels = codec->channels; + + for (iter = codec->optional_params; iter; iter = g_list_next(iter)) { + PurpleMediaCodecParameter *param = + (PurpleMediaCodecParameter*)iter->data; + purple_media_codec_add_optional_parameter(new_codec, + param->name, param->value); + } + + return new_codec; +} + +static void +purple_media_codec_free(PurpleMediaCodec *codec) +{ + if (codec == NULL) + return; + + g_free(codec->encoding_name); + + for (; codec->optional_params; codec->optional_params = + g_list_delete_link(codec->optional_params, + codec->optional_params)) { + purple_media_codec_remove_optional_parameter(codec, + codec->optional_params->data); + } + + g_free(codec); +} + +static FsCodec * +purple_media_codec_to_fs(const PurpleMediaCodec *codec) +{ + FsCodec *new_codec; + GList *iter; + + if (codec == NULL) + return NULL; + + new_codec = fs_codec_new(codec->id, codec->encoding_name, + purple_media_to_fs_media_type(codec->media_type), + codec->clock_rate); + new_codec->channels = codec->channels; + + for (iter = codec->optional_params; iter; iter = g_list_next(iter)) { + PurpleMediaCodecParameter *param = + (PurpleMediaCodecParameter*)iter->data; + fs_codec_add_optional_parameter(new_codec, + param->name, param->value); + } + + return new_codec; +} + +static PurpleMediaCodec * +purple_media_codec_from_fs(const FsCodec *codec) +{ + PurpleMediaCodec *new_codec; + GList *iter; + + if (codec == NULL) + return NULL; + + new_codec = purple_media_codec_new(codec->id, codec->encoding_name, + purple_media_from_fs(codec->media_type, + FS_DIRECTION_BOTH), codec->clock_rate); + new_codec->channels = codec->channels; + + for (iter = codec->optional_params; iter; iter = g_list_next(iter)) { + FsCodecParameter *param = (FsCodecParameter*)iter->data; + purple_media_codec_add_optional_parameter(new_codec, + param->name, param->value); + } + + return new_codec; +} + +gchar * +purple_media_codec_to_string(const PurpleMediaCodec *codec) +{ + FsCodec *fscodec = purple_media_codec_to_fs(codec); + gchar *str = fs_codec_to_string(fscodec); + fs_codec_destroy(fscodec); + return str; +} + +static GList * +purple_media_codec_list_from_fs(GList *codecs) +{ + GList *new_list = NULL; + + for (; codecs; codecs = g_list_next(codecs)) { + new_list = g_list_prepend(new_list, + purple_media_codec_from_fs( + codecs->data)); + } + + new_list = g_list_reverse(new_list); + return new_list; +} + +static GList * +purple_media_codec_list_to_fs(GList *codecs) +{ + GList *new_list = NULL; + + for (; codecs; codecs = g_list_next(codecs)) { + new_list = g_list_prepend(new_list, + purple_media_codec_to_fs( + codecs->data)); + } + + new_list = g_list_reverse(new_list); + return new_list; +} + +GList * +purple_media_codec_list_copy(GList *codecs) +{ + GList *new_list = NULL; + + for (; codecs; codecs = g_list_next(codecs)) { + new_list = g_list_prepend(new_list, g_boxed_copy( + PURPLE_TYPE_MEDIA_CODEC, + codecs->data)); + } + + new_list = g_list_reverse(new_list); + return new_list; +} + +void +purple_media_codec_list_free(GList *codecs) +{ + for (; codecs; codecs = + g_list_delete_link(codecs, codecs)) { + g_boxed_free(PURPLE_TYPE_MEDIA_CODEC, + codecs->data); + } +} + +GType +purple_media_codec_get_type() +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static("PurpleMediaCodec", + (GBoxedCopyFunc)purple_media_codec_copy, + (GBoxedFreeFunc)purple_media_codec_free); + } + return type; +} + +static PurpleMediaSession* +purple_media_get_session(PurpleMedia *media, const gchar *sess_id) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + return (PurpleMediaSession*) (media->priv->sessions) ? + g_hash_table_lookup(media->priv->sessions, sess_id) : NULL; +} + +static FsParticipant* +purple_media_get_participant(PurpleMedia *media, const gchar *name) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + return (FsParticipant*) (media->priv->participants) ? + g_hash_table_lookup(media->priv->participants, name) : NULL; +} + +static PurpleMediaStream* +purple_media_get_stream(PurpleMedia *media, const gchar *session, const gchar *participant) +{ + GList *streams; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + + streams = media->priv->streams; + + for (; streams; streams = g_list_next(streams)) { + PurpleMediaStream *stream = streams->data; + if (!strcmp(stream->session->id, session) && + !strcmp(stream->participant, participant)) + return stream; + } + + return NULL; +} + +static GList * +purple_media_get_streams(PurpleMedia *media, const gchar *session, + const gchar *participant) +{ + GList *streams; + GList *ret = NULL; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + + streams = media->priv->streams; + + for (; streams; streams = g_list_next(streams)) { + PurpleMediaStream *stream = streams->data; + if ((session == NULL || + !strcmp(stream->session->id, session)) && + (participant == NULL || + !strcmp(stream->participant, participant))) + ret = g_list_append(ret, stream); + } + + return ret; +} + +static void +purple_media_add_session(PurpleMedia *media, PurpleMediaSession *session) +{ + g_return_if_fail(PURPLE_IS_MEDIA(media)); + g_return_if_fail(session != NULL); + + if (!media->priv->sessions) { + purple_debug_info("media", "Creating hash table for sessions\n"); + media->priv->sessions = g_hash_table_new(g_str_hash, g_str_equal); + } + g_hash_table_insert(media->priv->sessions, g_strdup(session->id), session); +} + +static gboolean +purple_media_remove_session(PurpleMedia *media, PurpleMediaSession *session) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + return g_hash_table_remove(media->priv->sessions, session->id); +} + +static FsParticipant * +purple_media_add_participant(PurpleMedia *media, const gchar *name) +{ + FsParticipant *participant; + GError *err = NULL; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + + participant = purple_media_get_participant(media, name); + + if (participant) + return participant; + + participant = fs_conference_new_participant(media->priv->conference, + (gchar*)name, &err); + + if (err) { + purple_debug_error("media", "Error creating participant: %s\n", + err->message); + g_error_free(err); + return NULL; + } + + if (!media->priv->participants) { + purple_debug_info("media", "Creating hash table for participants\n"); + media->priv->participants = g_hash_table_new_full(g_str_hash, + g_str_equal, g_free, NULL); + } + + g_hash_table_insert(media->priv->participants, g_strdup(name), participant); + + return participant; +} + +static PurpleMediaStream * +purple_media_insert_stream(PurpleMediaSession *session, const gchar *name, FsStream *stream) +{ + PurpleMediaStream *media_stream; + + g_return_val_if_fail(session != NULL, NULL); + + media_stream = g_new0(PurpleMediaStream, 1); + media_stream->stream = stream; + media_stream->participant = g_strdup(name); + media_stream->session = session; + + session->media->priv->streams = + g_list_append(session->media->priv->streams, media_stream); + + return media_stream; +} + +static void +purple_media_insert_local_candidate(PurpleMediaSession *session, const gchar *name, + FsCandidate *candidate) +{ + PurpleMediaStream *stream; + + g_return_if_fail(session != NULL); + + stream = purple_media_get_stream(session->media, session->id, name); + stream->local_candidates = g_list_append(stream->local_candidates, candidate); +} + +GList * +purple_media_get_session_names(PurpleMedia *media) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + return media->priv->sessions != NULL ? + g_hash_table_get_keys(media->priv->sessions) : NULL; +} + +static void +purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src) +{ + PurpleMediaSession *session; + GstPad *sinkpad; + GstPad *srcpad; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + session = purple_media_get_session(media, sess_id); + + if (session == NULL) { + purple_debug_warning("media", "purple_media_set_src: trying" + " to set src on non-existent session\n"); + return; + } + + if (session->src) + gst_object_unref(session->src); + session->src = src; + gst_element_set_locked_state(session->src, TRUE); + gst_bin_add(GST_BIN(session->media->priv->confbin), + session->src); + + session->tee = gst_element_factory_make("tee", NULL); + gst_bin_add(GST_BIN(session->media->priv->confbin), session->tee); + gst_element_link(session->src, session->tee); + gst_element_set_state(session->tee, GST_STATE_PLAYING); + + g_object_get(session->session, "sink-pad", &sinkpad, NULL); + srcpad = gst_element_get_request_pad(session->tee, "src%d"); + purple_debug_info("media", "connecting pad: %s\n", + gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK + ? "success" : "failure"); + gst_element_set_locked_state(session->src, FALSE); +} + +#if 0 +static void +purple_media_set_sink(PurpleMedia *media, const gchar *sess_id, + const gchar *participant, GstElement *sink) +{ + PurpleMediaStream *stream; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + stream = purple_media_get_stream(media, sess_id, participant); + + if (stream == NULL) { + purple_debug_warning("media", "purple_media_set_sink: trying" + " to set sink on non-existent stream\n"); + return; + } + + if (stream->sink) + gst_object_unref(stream->sink); + stream->sink = sink; + gst_bin_add(GST_BIN(stream->session->media->priv->confbin), + stream->sink); +} +#endif + +GstElement * +purple_media_get_src(PurpleMedia *media, const gchar *sess_id) +{ + PurpleMediaSession *session; + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + session = purple_media_get_session(media, sess_id); + return (session != NULL) ? session->src : NULL; +} + +static PurpleMediaSession * +purple_media_session_from_fs_stream(PurpleMedia *media, FsStream *stream) +{ + FsSession *fssession; + GList *values; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + g_return_val_if_fail(FS_IS_STREAM(stream), NULL); + + g_object_get(stream, "session", &fssession, NULL); + + values = g_hash_table_get_values(media->priv->sessions); + + for (; values; values = g_list_delete_link(values, values)) { + PurpleMediaSession *session = values->data; + + if (session->session == fssession) { + g_list_free(values); + g_object_unref(fssession); + return session; + } + } + + g_object_unref(fssession); + return NULL; +} + +static gboolean +media_bus_call(GstBus *bus, GstMessage *msg, PurpleMedia *media) +{ + switch(GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ELEMENT: { + if (!FS_IS_CONFERENCE(GST_MESSAGE_SRC(msg)) || + !PURPLE_IS_MEDIA(media) || + media->priv->conference != + FS_CONFERENCE(GST_MESSAGE_SRC(msg))) + break; + + if (gst_structure_has_name(msg->structure, "farsight-error")) { + FsError error_no; + gst_structure_get_enum(msg->structure, "error-no", + FS_TYPE_ERROR, (gint*)&error_no); + /* + * Unknown CName is only a problem for the + * multicast transmitter which isn't used. + */ + if (error_no != FS_ERROR_UNKNOWN_CNAME) + purple_debug_error("media", "farsight-error: %i: %s\n", error_no, + gst_structure_get_string(msg->structure, "error-msg")); + } else if (gst_structure_has_name(msg->structure, + "farsight-new-local-candidate")) { + FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream")); + FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "candidate")); + PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream); + purple_media_new_local_candidate_cb(stream, local_candidate, session); + } else if (gst_structure_has_name(msg->structure, + "farsight-local-candidates-prepared")) { + FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream")); + PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream); + purple_media_candidates_prepared_cb(stream, session); + } else if (gst_structure_has_name(msg->structure, + "farsight-new-active-candidate-pair")) { + FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream")); + FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "local-candidate")); + FsCandidate *remote_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "remote-candidate")); + PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream); + purple_media_candidate_pair_established_cb(stream, local_candidate, remote_candidate, session); + } else if (gst_structure_has_name(msg->structure, + "farsight-recv-codecs-changed")) { + GList *codecs = g_value_get_boxed(gst_structure_get_value(msg->structure, "codecs")); + FsCodec *codec = codecs->data; + purple_debug_info("media", "farsight-recv-codecs-changed: %s\n", codec->encoding_name); + + } else if (gst_structure_has_name(msg->structure, + "farsight-component-state-changed")) { + FsStreamState fsstate = g_value_get_enum(gst_structure_get_value(msg->structure, "state")); + guint component = g_value_get_uint(gst_structure_get_value(msg->structure, "component")); + const gchar *state; + switch (fsstate) { + case FS_STREAM_STATE_FAILED: + state = "FAILED"; + break; + case FS_STREAM_STATE_DISCONNECTED: + state = "DISCONNECTED"; + break; + case FS_STREAM_STATE_GATHERING: + state = "GATHERING"; + break; + case FS_STREAM_STATE_CONNECTING: + state = "CONNECTING"; + break; + case FS_STREAM_STATE_CONNECTED: + state = "CONNECTED"; + break; + case FS_STREAM_STATE_READY: + state = "READY"; + break; + default: + state = "UNKNOWN"; + break; + } + purple_debug_info("media", "farsight-component-state-changed: component: %u state: %s\n", component, state); + } else if (gst_structure_has_name(msg->structure, + "farsight-send-codec-changed")) { + FsCodec *codec = g_value_get_boxed(gst_structure_get_value(msg->structure, "codec")); + gchar *codec_str = fs_codec_to_string(codec); + purple_debug_info("media", "farsight-send-codec-changed: codec: %s\n", codec_str); + g_free(codec_str); + } 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) { + gchar *session_id = g_strdup(session->id); + g_signal_emit(media, purple_media_signals[CODECS_CHANGED], 0, session_id); + g_free(session_id); + g_list_free(sessions); + break; + } + } + } + break; + } + default: + break; + } + + return TRUE; +} + +GstElement * +purple_media_get_pipeline(PurpleMedia *media) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + + return purple_media_manager_get_pipeline(media->priv->manager); +} + +PurpleConnection * +purple_media_get_connection(PurpleMedia *media) +{ + PurpleConnection *pc; + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + g_object_get(G_OBJECT(media), "connection", &pc, NULL); + return pc; +} + +gpointer +purple_media_get_prpl_data(PurpleMedia *media) +{ + gpointer prpl_data; + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + g_object_get(G_OBJECT(media), "prpl-data", &prpl_data, NULL); + return prpl_data; +} + +void +purple_media_set_prpl_data(PurpleMedia *media, gpointer prpl_data) +{ + g_return_if_fail(PURPLE_IS_MEDIA(media)); + g_object_set(G_OBJECT(media), "prpl-data", prpl_data, NULL); +} + +void +purple_media_error(PurpleMedia *media, const gchar *error, ...) +{ + va_list args; + gchar *message; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + va_start(args, error); + message = g_strdup_vprintf(error, args); + va_end(args); + + purple_debug_error("media", "%s\n", message); + g_signal_emit(media, purple_media_signals[ERROR], 0, message); + + g_free(message); +} + +void +purple_media_accept(PurpleMedia *media) +{ + GList *streams; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + streams = media->priv->streams; + + for (; streams; streams = g_list_next(streams)) { + PurpleMediaStream *stream = streams->data; + g_object_set(G_OBJECT(stream->stream), "direction", + purple_media_to_fs_stream_direction( + stream->session->type), NULL); + stream->accepted = TRUE; + } + + g_signal_emit(media, purple_media_signals[ACCEPTED], + 0, NULL, NULL); +} + +void +purple_media_hangup(PurpleMedia *media) +{ + g_return_if_fail(PURPLE_IS_MEDIA(media)); + g_signal_emit(media, purple_media_signals[STREAM_INFO], + 0, PURPLE_MEDIA_INFO_HANGUP, + NULL, NULL); + purple_media_end(media, NULL, NULL); +} + +void +purple_media_reject(PurpleMedia *media) +{ + g_return_if_fail(PURPLE_IS_MEDIA(media)); + g_signal_emit(media, purple_media_signals[STREAM_INFO], + 0, PURPLE_MEDIA_INFO_REJECT, + NULL, NULL); + purple_media_end(media, NULL, NULL); +} + +void +purple_media_end(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ + g_return_if_fail(PURPLE_IS_MEDIA(media)); + if (session_id == NULL && participant == NULL) { + g_signal_emit(media, purple_media_signals[STATE_CHANGED], + 0, PURPLE_MEDIA_STATE_END, + NULL, NULL); + g_object_unref(media); + } +} + +static void +purple_media_new_local_candidate_cb(FsStream *stream, + FsCandidate *local_candidate, + PurpleMediaSession *session) +{ + gchar *name; + FsParticipant *participant; + PurpleMediaCandidate *candidate; + + g_return_if_fail(FS_IS_STREAM(stream)); + g_return_if_fail(session != NULL); + + purple_debug_info("media", "got new local candidate: %s\n", local_candidate->foundation); + g_object_get(stream, "participant", &participant, NULL); + g_object_get(participant, "cname", &name, NULL); + g_object_unref(participant); + + purple_media_insert_local_candidate(session, name, fs_candidate_copy(local_candidate)); + + candidate = purple_media_candidate_from_fs(local_candidate); + g_signal_emit(session->media, purple_media_signals[NEW_CANDIDATE], + 0, session->id, name, candidate); + purple_media_candidate_free(candidate); + + g_free(name); +} + +static void +purple_media_candidates_prepared_cb(FsStream *stream, PurpleMediaSession *session) +{ + gchar *name; + FsParticipant *participant; + PurpleMediaStream *stream_data; + + g_return_if_fail(FS_IS_STREAM(stream)); + g_return_if_fail(session != NULL); + + g_object_get(stream, "participant", &participant, NULL); + g_object_get(participant, "cname", &name, NULL); + g_object_unref(participant); + + stream_data = purple_media_get_stream(session->media, session->id, name); + stream_data->candidates_prepared = TRUE; + + 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 *fsstream, + FsCandidate *native_candidate, + FsCandidate *remote_candidate, + PurpleMediaSession *session) +{ + gchar *name; + FsParticipant *participant; + PurpleMediaStream *stream; + GList *iter; + + g_return_if_fail(FS_IS_STREAM(fsstream)); + g_return_if_fail(session != NULL); + + g_object_get(fsstream, "participant", &participant, NULL); + g_object_get(participant, "cname", &name, NULL); + g_object_unref(participant); + + stream = purple_media_get_stream(session->media, session->id, name); + + iter = stream->active_local_candidates; + for(; iter; iter = g_list_next(iter)) { + FsCandidate *c = iter->data; + if (native_candidate->component_id == c->component_id) { + fs_candidate_destroy(c); + stream->active_local_candidates = + g_list_delete_link(iter, iter); + stream->active_local_candidates = g_list_prepend( + stream->active_local_candidates, + fs_candidate_copy(native_candidate)); + break; + } + } + if (iter == NULL) + stream->active_local_candidates = g_list_prepend( + stream->active_local_candidates, + fs_candidate_copy(native_candidate)); + + iter = stream->active_remote_candidates; + for(; iter; iter = g_list_next(iter)) { + FsCandidate *c = iter->data; + if (native_candidate->component_id == c->component_id) { + fs_candidate_destroy(c); + stream->active_remote_candidates = + g_list_delete_link(iter, iter); + stream->active_remote_candidates = g_list_prepend( + stream->active_remote_candidates, + fs_candidate_copy(remote_candidate)); + break; + } + } + if (iter == NULL) + stream->active_remote_candidates = g_list_prepend( + stream->active_remote_candidates, + fs_candidate_copy(remote_candidate)); + + purple_debug_info("media", "candidate pair established\n"); +} + +static gboolean +purple_media_connected_cb(PurpleMediaStream *stream) +{ + g_return_val_if_fail(stream != NULL, FALSE); + + stream->connected_cb_id = 0; + + purple_media_manager_create_output_window( + stream->session->media->priv->manager, + stream->session->media, + stream->session->id, stream->participant); + + g_signal_emit(stream->session->media, + purple_media_signals[STATE_CHANGED], + 0, PURPLE_MEDIA_STATE_CONNECTED, + stream->session->id, stream->participant); + return FALSE; +} + +static void +purple_media_src_pad_added_cb(FsStream *fsstream, GstPad *srcpad, + FsCodec *codec, PurpleMediaStream *stream) +{ + PurpleMediaPrivate *priv; + GstPad *sinkpad; + + g_return_if_fail(FS_IS_STREAM(fsstream)); + g_return_if_fail(stream != NULL); + + priv = stream->session->media->priv; + + if (stream->src == NULL) { + GstElement *sink = NULL; + + if (codec->media_type == FS_MEDIA_TYPE_AUDIO) { + /* + * Should this instead be: + * audioconvert ! audioresample ! liveadder ! + * audioresample ! audioconvert ! realsink + */ + stream->src = gst_element_factory_make( + "liveadder", NULL); + sink = purple_media_manager_get_element(priv->manager, + PURPLE_MEDIA_RECV_AUDIO); + } else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) { + stream->src = gst_element_factory_make( + "fsfunnel", NULL); + sink = gst_element_factory_make( + "fakesink", NULL); + g_object_set(G_OBJECT(sink), "async", FALSE, NULL); + } + stream->tee = gst_element_factory_make("tee", NULL); + gst_bin_add_many(GST_BIN(priv->confbin), + stream->src, stream->tee, sink, NULL); + gst_element_sync_state_with_parent(sink); + gst_element_sync_state_with_parent(stream->tee); + gst_element_sync_state_with_parent(stream->src); + gst_element_link_many(stream->src, stream->tee, sink, NULL); + } + + sinkpad = gst_element_get_request_pad(stream->src, "sink%d"); + gst_pad_link(srcpad, sinkpad); + gst_object_unref(sinkpad); + + stream->connected_cb_id = purple_timeout_add(0, + (GSourceFunc)purple_media_connected_cb, stream); +} + +static gboolean +purple_media_add_stream_internal(PurpleMedia *media, const gchar *sess_id, + const gchar *who, FsMediaType type, + FsStreamDirection type_direction, + const gchar *transmitter, + guint num_params, GParameter *params) +{ + PurpleMediaSession *session; + FsParticipant *participant = NULL; + PurpleMediaStream *stream = NULL; + FsStreamDirection *direction = NULL; + PurpleMediaSessionType session_type; + gboolean is_nice = !strcmp(transmitter, "nice"); + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + session = purple_media_get_session(media, sess_id); + + if (!session) { + GError *err = NULL; + GList *codec_conf = NULL; + gchar *filename = NULL; + + session = g_new0(PurpleMediaSession, 1); + + session->session = fs_conference_new_session(media->priv->conference, type, &err); + + if (err != NULL) { + purple_media_error(media, "Error creating session: %s\n", err->message); + g_error_free(err); + g_free(session); + return FALSE; + } + + /* XXX: SPEEX has a latency of 5 or 6 seconds for me */ +#if 0 + /* SPEEX is added through the configuration */ + codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY, + "SPEEX", FS_MEDIA_TYPE_AUDIO, 8000)); + codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY, + "SPEEX", FS_MEDIA_TYPE_AUDIO, 16000)); +#endif + + filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL); + codec_conf = fs_codec_list_from_keyfile(filename, &err); + g_free(filename); + + if (err != NULL) { + if (err->code == 4) + purple_debug_info("media", "Couldn't read " + "fs-codec.conf: %s\n", + err->message); + else + purple_debug_error("media", "Error reading " + "fs-codec.conf: %s\n", + err->message); + g_error_free(err); + } + + fs_session_set_codec_preferences(session->session, codec_conf, NULL); + + /* + * Removes a 5-7 second delay before + * receiving the src-pad-added signal. + * Only works for non-multicast FsRtpSessions. + */ + if (is_nice || !strcmp(transmitter, "rawudp")) + g_object_set(G_OBJECT(session->session), + "no-rtcp-timeout", 0, NULL); + + fs_codec_list_destroy(codec_conf); + + session->id = g_strdup(sess_id); + session->media = media; + session->type = purple_media_from_fs(type, type_direction); + + purple_media_add_session(media, session); + g_signal_emit(media, purple_media_signals[STATE_CHANGED], + 0, PURPLE_MEDIA_STATE_NEW, + session->id, NULL); + + session_type = purple_media_from_fs(type, FS_DIRECTION_SEND); + purple_media_set_src(media, session->id, + purple_media_manager_get_element( + media->priv->manager, session_type)); + gst_element_set_state(session->src, GST_STATE_PLAYING); + + purple_media_manager_create_output_window( + media->priv->manager, + session->media, + session->id, NULL); + } + + if (!(participant = purple_media_add_participant(media, who))) { + purple_media_remove_session(media, session); + g_free(session); + return FALSE; + } else { + g_signal_emit(media, purple_media_signals[STATE_CHANGED], + 0, PURPLE_MEDIA_STATE_NEW, + NULL, who); + } + + stream = purple_media_get_stream(media, sess_id, who); + + if (!stream) { + GError *err = NULL; + FsStream *fsstream = NULL; + const gchar *stun_ip = purple_network_get_stun_ip(); + const gchar *turn_ip = purple_network_get_turn_ip(); + + if (stun_ip || turn_ip) { + guint new_num_params = + (stun_ip && is_nice) && turn_ip ? + num_params + 2 : num_params + 1; + guint next_param_index = num_params; + GParameter *param = g_new0(GParameter, new_num_params); + memcpy(param, params, sizeof(GParameter) * num_params); + + if (stun_ip) { + purple_debug_info("media", + "setting property stun-ip on new stream: %s\n", stun_ip); + + param[next_param_index].name = "stun-ip"; + g_value_init(¶m[next_param_index].value, G_TYPE_STRING); + g_value_set_string(¶m[next_param_index].value, stun_ip); + next_param_index++; + } + + if (turn_ip && is_nice) { + GValueArray *relay_info = g_value_array_new(0); + GValue value; + gint turn_port = + purple_prefs_get_int("/purple/network/turn_port"); + const gchar *username = + purple_prefs_get_string("/purple/network/turn_username"); + const gchar *password = + purple_prefs_get_string("/purple/network/turn_password"); + GstStructure *turn_setup = gst_structure_new("relay-info", + "ip", G_TYPE_STRING, turn_ip, + "port", G_TYPE_UINT, turn_port, + "username", G_TYPE_STRING, username, + "password", G_TYPE_STRING, password, + NULL); + + if (turn_setup) { + memset(&value, 0, sizeof(GValue)); + g_value_init(&value, GST_TYPE_STRUCTURE); + gst_value_set_structure(&value, turn_setup); + relay_info = g_value_array_append(relay_info, &value); + gst_structure_free(turn_setup); + + purple_debug_info("media", + "setting property relay-info on new stream\n"); + param[next_param_index].name = "relay-info"; + g_value_init(¶m[next_param_index].value, + G_TYPE_VALUE_ARRAY); + g_value_set_boxed(¶m[next_param_index].value, + relay_info); + g_value_array_free(relay_info); + } else { + purple_debug_error("media", "Error relay info"); + g_object_unref(participant); + g_hash_table_remove(media->priv->participants, who); + purple_media_remove_session(media, session); + g_free(session); + return FALSE; + } + } + + fsstream = fs_session_new_stream(session->session, + participant, type_direction & + FS_DIRECTION_RECV, transmitter, + new_num_params, param, &err); + g_free(param); + } else { + fsstream = fs_session_new_stream(session->session, + participant, type_direction & + FS_DIRECTION_RECV, transmitter, + num_params, params, &err); + } + + if (err) { + purple_debug_error("media", "Error creating stream: %s\n", + err->message); + g_error_free(err); + g_object_unref(participant); + g_hash_table_remove(media->priv->participants, who); + purple_media_remove_session(media, session); + g_free(session); + return FALSE; + } + + stream = purple_media_insert_stream(session, who, fsstream); + + /* callback for source pad added (new stream source ready) */ + g_signal_connect(G_OBJECT(fsstream), + "src-pad-added", G_CALLBACK(purple_media_src_pad_added_cb), stream); + + g_signal_emit(media, purple_media_signals[STATE_CHANGED], + 0, PURPLE_MEDIA_STATE_NEW, + session->id, who); + } else if (*direction != type_direction) { + /* change direction */ + g_object_set(stream->stream, "direction", type_direction, NULL); + } + + return TRUE; +} + +gboolean +purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who, + PurpleMediaSessionType type, + const gchar *transmitter, + guint num_params, GParameter *params) +{ + FsStreamDirection type_direction; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + if (type & PURPLE_MEDIA_AUDIO) { + type_direction = purple_media_to_fs_stream_direction(type & PURPLE_MEDIA_AUDIO); + + if (!purple_media_add_stream_internal(media, sess_id, who, + FS_MEDIA_TYPE_AUDIO, type_direction, + transmitter, num_params, params)) { + return FALSE; + } + } + else if (type & PURPLE_MEDIA_VIDEO) { + type_direction = purple_media_to_fs_stream_direction(type & PURPLE_MEDIA_VIDEO); + + if (!purple_media_add_stream_internal(media, sess_id, who, + FS_MEDIA_TYPE_VIDEO, type_direction, + transmitter, num_params, params)) { + return FALSE; + } + } + return TRUE; +} + +void +purple_media_remove_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who) +{ + /* Add state-changed end emits in here when this is implemented */ +} + +PurpleMediaSessionType +purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id) +{ + PurpleMediaSession *session; + g_return_val_if_fail(PURPLE_IS_MEDIA(media), PURPLE_MEDIA_NONE); + session = purple_media_get_session(media, sess_id); + return session->type; +} +/* XXX: Should wait until codecs-ready is TRUE before using this function */ +GList * +purple_media_get_codecs(PurpleMedia *media, const gchar *sess_id) +{ + GList *fscodecs; + GList *codecs; + PurpleMediaSession *session; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + + session = purple_media_get_session(media, sess_id); + + if (session == NULL) + return NULL; + + g_object_get(G_OBJECT(session->session), + "codecs", &fscodecs, NULL); + codecs = purple_media_codec_list_from_fs(fscodecs); + fs_codec_list_destroy(fscodecs); + return codecs; +} + +GList * +purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *name) +{ + PurpleMediaStream *stream; + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + stream = purple_media_get_stream(media, sess_id, name); + return purple_media_candidate_list_from_fs(stream->local_candidates); +} + +void +purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id, + const gchar *name, GList *remote_candidates) +{ + PurpleMediaStream *stream; + GError *err = NULL; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + stream = purple_media_get_stream(media, sess_id, name); + + stream->remote_candidates = g_list_concat(stream->remote_candidates, + purple_media_candidate_list_to_fs(remote_candidates)); + + fs_stream_set_remote_candidates(stream->stream, + stream->remote_candidates, &err); + + if (err) { + purple_debug_error("media", "Error adding remote" + " candidates: %s\n", err->message); + g_error_free(err); + } +} + +GList * +purple_media_get_active_local_candidates(PurpleMedia *media, + const gchar *sess_id, const gchar *name) +{ + PurpleMediaStream *stream; + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + stream = purple_media_get_stream(media, sess_id, name); + return purple_media_candidate_list_from_fs( + stream->active_local_candidates); +} + +GList * +purple_media_get_active_remote_candidates(PurpleMedia *media, + const gchar *sess_id, const gchar *name) +{ + PurpleMediaStream *stream; + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + stream = purple_media_get_stream(media, sess_id, name); + return purple_media_candidate_list_from_fs( + stream->active_remote_candidates); +} + +gboolean +purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id, const gchar *name, GList *codecs) +{ + PurpleMediaStream *stream; + FsStream *fsstream; + GList *fscodecs; + GError *err = NULL; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + stream = purple_media_get_stream(media, sess_id, name); + + if (stream == NULL) + return FALSE; + + fsstream = stream->stream; + fscodecs = purple_media_codec_list_to_fs(codecs); + fs_stream_set_remote_codecs(fsstream, fscodecs, &err); + fs_codec_list_destroy(fscodecs); + + if (err) { + purple_debug_error("media", "Error setting remote codecs: %s\n", + err->message); + g_error_free(err); + return FALSE; + } + return TRUE; +} + +gboolean +purple_media_candidates_prepared(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ + GList *streams; + gboolean prepared = TRUE; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + streams = purple_media_get_streams(media, session_id, participant); + + for (; streams; streams = g_list_delete_link(streams, streams)) { + PurpleMediaStream *stream = streams->data; + if (stream->candidates_prepared == FALSE) { + g_list_free(streams); + prepared = FALSE; + break; + } + } + + return prepared; +} + +gboolean +purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, PurpleMediaCodec *codec) +{ + PurpleMediaSession *session; + FsCodec *fscodec; + GError *err = NULL; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + session = purple_media_get_session(media, sess_id); + + if (session != NULL) + return FALSE; + + fscodec = purple_media_codec_to_fs(codec); + fs_session_set_send_codec(session->session, fscodec, &err); + fs_codec_destroy(fscodec); + + if (err) { + purple_debug_error("media", "Error setting send codec\n"); + g_error_free(err); + return FALSE; + } + return TRUE; +} + +gboolean +purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id) +{ + gboolean ret; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + if (sess_id != NULL) { + PurpleMediaSession *session; + session = purple_media_get_session(media, sess_id); + + if (session == NULL) + return FALSE; + + g_object_get(session->session, "codecs-ready", &ret, NULL); + } else { + GList *values = g_hash_table_get_values(media->priv->sessions); + for (; values; values = g_list_delete_link(values, values)) { + PurpleMediaSession *session = values->data; + g_object_get(session->session, + "codecs-ready", &ret, NULL); + if (ret == FALSE) + break; + } + if (values != NULL) + g_list_free(values); + } + return ret; +} + +gboolean +purple_media_accepted(PurpleMedia *media, const gchar *sess_id, + const gchar *participant) +{ + gboolean accepted = TRUE; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + if (sess_id == NULL && participant == NULL) { + GList *streams = media->priv->streams; + + for (; streams; streams = g_list_next(streams)) { + PurpleMediaStream *stream = streams->data; + if (stream->accepted == FALSE) { + accepted = FALSE; + break; + } + } + } else if (sess_id != NULL && participant == NULL) { + GList *streams = purple_media_get_streams( + media, sess_id, NULL); + for (; streams; streams = + g_list_delete_link(streams, streams)) { + PurpleMediaStream *stream = streams->data; + if (stream->accepted == FALSE) { + g_list_free(streams); + accepted = FALSE; + break; + } + } + } else if (sess_id != NULL && participant != NULL) { + PurpleMediaStream *stream = purple_media_get_stream( + media, sess_id, participant); + if (stream == NULL || stream->accepted == FALSE) + accepted = FALSE; + } + + return accepted; +} + +void purple_media_mute(PurpleMedia *media, gboolean active) +{ + GList *sessions; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + sessions = g_hash_table_get_values(media->priv->sessions); + purple_debug_info("media", "Turning mute %s\n", active ? "on" : "off"); + + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + PurpleMediaSession *session = sessions->data; + if (session->type & PURPLE_MEDIA_SEND_AUDIO) { + GstElement *volume = gst_bin_get_by_name( + GST_BIN(session->src), + "purpleaudioinputvolume"); + g_object_set(volume, "mute", active, NULL); + } + } +} + +void purple_media_set_input_volume(PurpleMedia *media, + const gchar *session_id, double level) +{ + GList *sessions; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + if (session_id == NULL) + sessions = g_hash_table_get_values(media->priv->sessions); + else + sessions = g_list_append(NULL, + purple_media_get_session(media, session_id)); + + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + PurpleMediaSession *session = sessions->data; + + if (session->type & PURPLE_MEDIA_SEND_AUDIO) { + GstElement *volume = gst_bin_get_by_name( + GST_BIN(session->src), + "purpleaudioinputvolume"); + g_object_set(volume, "volume", level, NULL); + } + } +} + +void purple_media_set_output_volume(PurpleMedia *media, + const gchar *session_id, const gchar *participant, + double level) +{ + GList *streams; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + streams = purple_media_get_streams(media, + session_id, participant); + + for (; streams; streams = g_list_delete_link(streams, streams)) { + PurpleMediaStream *stream = streams->data; + + if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO) { + GstElement *tee = stream->tee; + GstIterator *iter = gst_element_iterate_src_pads(tee); + GstPad *sinkpad; + while (gst_iterator_next(iter, (gpointer)&sinkpad) + == GST_ITERATOR_OK) { + GstPad *peer = gst_pad_get_peer(sinkpad); + GstElement *volume; + + if (peer == NULL) { + gst_object_unref(sinkpad); + continue; + } + + volume = gst_bin_get_by_name(GST_BIN( + GST_OBJECT_PARENT(peer)), + "purpleaudiooutputvolume"); + g_object_set(volume, "volume", level, NULL); + gst_object_unref(peer); + gst_object_unref(sinkpad); + } + gst_iterator_free(iter); + } + } +} + +gulong +purple_media_set_output_window(PurpleMedia *media, const gchar *session_id, + const gchar *participant, gulong window_id) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + return purple_media_manager_set_output_window(media->priv->manager, + media, session_id, participant, window_id); +} + +void +purple_media_remove_output_windows(PurpleMedia *media) +{ + GList *iter = media->priv->streams; + for (; iter; iter = g_list_next(iter)) { + PurpleMediaStream *stream = iter->data; + purple_media_manager_remove_output_windows( + media->priv->manager, media, + stream->session->id, stream->participant); + } + + iter = purple_media_get_session_names(media); + for (; iter; iter = g_list_delete_link(iter, iter)) { + gchar *session_name = iter->data; + purple_media_manager_remove_output_windows( + media->priv->manager, media, + session_name, NULL); + } +} + +GstElement * +purple_media_get_tee(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + + if (session_id != NULL && participant == NULL) { + PurpleMediaSession *session = + purple_media_get_session(media, session_id); + return (session != NULL) ? session->tee : NULL; + } else if (session_id != NULL && participant != NULL) { + PurpleMediaStream *stream = + purple_media_get_stream(media, + session_id, participant); + return (stream != NULL) ? stream->tee : NULL; + } + g_return_val_if_reached(NULL); +} + +#endif /* USE_VV */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/media.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/media.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,653 @@ +/** + * @file media.h Media API + * @ingroup core + */ + +/* purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __MEDIA_H_ +#define __MEDIA_H_ + +#ifdef USE_VV + +#include +#include +#include + +G_BEGIN_DECLS + +#define PURPLE_TYPE_MEDIA (purple_media_get_type()) +#define PURPLE_TYPE_MEDIA_CANDIDATE (purple_media_candidate_get_type()) +#define PURPLE_TYPE_MEDIA_CODEC (purple_media_codec_get_type()) +#define PURPLE_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA, PurpleMedia)) +#define PURPLE_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA, PurpleMediaClass)) +#define PURPLE_IS_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA)) +#define PURPLE_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA)) +#define PURPLE_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA, PurpleMediaClass)) + +#define PURPLE_MEDIA_TYPE_STATE (purple_media_state_changed_get_type()) +#define PURPLE_MEDIA_TYPE_INFO_TYPE (purple_media_info_type_get_type()) + +/** @copydoc _PurpleMedia */ +typedef struct _PurpleMedia PurpleMedia; +/** @copydoc _PurpleMediaClass */ +typedef struct _PurpleMediaClass PurpleMediaClass; +/** @copydoc _PurpleMediaPrivate */ +typedef struct _PurpleMediaPrivate PurpleMediaPrivate; +/** @copydoc _PurpleMediaCandidate */ +typedef struct _PurpleMediaCandidate PurpleMediaCandidate; +/** @copydoc _PurpleMediaCodec */ +typedef struct _PurpleMediaCodec PurpleMediaCodec; +/** @copydoc _PurpleMediaCodecParameter */ +typedef struct _PurpleMediaCodecParameter PurpleMediaCodecParameter; + +#else + +typedef void PurpleMedia; + +#endif /* USE_VV */ + +/** Media caps */ +typedef enum { + PURPLE_MEDIA_CAPS_NONE = 0, + PURPLE_MEDIA_CAPS_AUDIO = 1, + PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION = 1 << 1, + PURPLE_MEDIA_CAPS_VIDEO = 1 << 2, + PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION = 1 << 3, + PURPLE_MEDIA_CAPS_AUDIO_VIDEO = 1 << 4, + PURPLE_MEDIA_CAPS_MODIFY_SESSION = 1 << 5, + PURPLE_MEDIA_CAPS_CHANGE_DIRECTION = 1 << 6, +} PurpleMediaCaps; + +/** Media session types */ +typedef enum { + PURPLE_MEDIA_NONE = 0, + PURPLE_MEDIA_RECV_AUDIO = 1 << 0, + PURPLE_MEDIA_SEND_AUDIO = 1 << 1, + PURPLE_MEDIA_RECV_VIDEO = 1 << 2, + PURPLE_MEDIA_SEND_VIDEO = 1 << 3, + PURPLE_MEDIA_AUDIO = PURPLE_MEDIA_RECV_AUDIO | PURPLE_MEDIA_SEND_AUDIO, + PURPLE_MEDIA_VIDEO = PURPLE_MEDIA_RECV_VIDEO | PURPLE_MEDIA_SEND_VIDEO +} PurpleMediaSessionType; + +/** Media state-changed types */ +typedef enum { + PURPLE_MEDIA_STATE_NEW = 0, + PURPLE_MEDIA_STATE_CONNECTED, + PURPLE_MEDIA_STATE_END, +} PurpleMediaState; + +/** Media info types */ +typedef enum { + PURPLE_MEDIA_INFO_HANGUP = 0, + PURPLE_MEDIA_INFO_REJECT, + PURPLE_MEDIA_INFO_MUTE, + PURPLE_MEDIA_INFO_HOLD, +} PurpleMediaInfoType; + +typedef enum { + PURPLE_MEDIA_CANDIDATE_TYPE_HOST, + PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX, + PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX, + PURPLE_MEDIA_CANDIDATE_TYPE_RELAY, + PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST, +} PurpleMediaCandidateType; + +typedef enum { + PURPLE_MEDIA_COMPONENT_NONE = 0, + PURPLE_MEDIA_COMPONENT_RTP = 1, + PURPLE_MEDIA_COMPONENT_RTCP = 2, +} PurpleMediaComponentType; + +typedef enum { + PURPLE_MEDIA_NETWORK_PROTOCOL_UDP, + PURPLE_MEDIA_NETWORK_PROTOCOL_TCP, +} PurpleMediaNetworkProtocol; + +#ifdef USE_VV + +/** The media class */ +struct _PurpleMediaClass +{ + GObjectClass parent_class; /**< The parent class. */ +}; + +/** The media class's private data */ +struct _PurpleMedia +{ + GObject parent; /**< The parent of this object. */ + PurpleMediaPrivate *priv; /**< The private data of this object. */ +}; + +struct _PurpleMediaCandidate +{ + const gchar *foundation; + guint component_id; + const gchar *ip; + guint16 port; + const gchar *base_ip; + guint16 base_port; + PurpleMediaNetworkProtocol proto; + guint32 priority; + PurpleMediaCandidateType type; + const gchar *username; + const gchar *password; + guint ttl; +}; + +struct _PurpleMediaCodecParameter +{ + gchar *name; + gchar *value; +}; + +struct _PurpleMediaCodec +{ + gint id; + char *encoding_name; + PurpleMediaSessionType media_type; + guint clock_rate; + guint channels; + GList *optional_params; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets the media class's GType + * + * @return The media class's GType. + */ +GType purple_media_get_type(void); + +/** + * Gets the type of the state-changed enum + * + * @return The state-changed enum's GType + */ +GType purple_media_state_changed_get_type(void); + +/** + * Gets the type of the info type enum + * + * @return The info type enum's GType + */ +GType purple_media_info_type_get_type(void); + +/** + * Gets the type of the media candidate structure. + * + * @return The media canditate's GType + */ +GType purple_media_candidate_get_type(void); + +/** + * Creates a PurpleMediaCandidate instance. + * + * @param foundation The foundation of the candidate. + * @param component_id The component this candidate is for. + * @param type The type of candidate. + * @param proto The protocol this component is for. + * @param ip The IP address of this component. + * @param port The network port. + * + * @return The newly created PurpleMediaCandidate instance. + */ +PurpleMediaCandidate *purple_media_candidate_new( + const gchar *foundation, guint component_id, + PurpleMediaCandidateType type, + PurpleMediaNetworkProtocol proto, + const gchar *ip, guint port); + +/** + * Copies a PurpleMediaCandidate instance. + * + * @param candidate The candidate to copy + * + * @return The newly created PurpleMediaCandidate copy. + */ +PurpleMediaCandidate *purple_media_candidate_copy( + PurpleMediaCandidate *candidate); + +/** + * Copies a GList of PurpleMediaCandidate and its contents. + * + * @param candidates The list of candidates to be copied. + * + * @return The copy of the GList. + */ +GList *purple_media_candidate_list_copy(GList *candidates); + +/** + * Frees a GList of PurpleMediaCandidate and its contents. + * + * @param candidates The list of candidates to be freed. + */ +void purple_media_candidate_list_free(GList *candidates); + +/** + * Gets the type of the media codec structure. + * + * @return The media codec's GType + */ +GType purple_media_codec_get_type(void); + +/** + * Creates a new PurpleMediaCodec instance. + * + * @param id Codec identifier. + * @param encoding_name Name of the media type this encodes. + * @param media_type PurpleMediaSessionType of this codec. + * @param clock_rate The clock rate this codec encodes at, if applicable. + * + * @return The newly created PurpleMediaCodec. + */ +PurpleMediaCodec *purple_media_codec_new(int id, const char *encoding_name, + PurpleMediaSessionType media_type, guint clock_rate); + +/** + * Copies a PurpleMediaCodec instance. + * + * @param codec The codec to copy + * + * @return The newly created PurpleMediaCodec copy. + */ +PurpleMediaCodec *purple_media_codec_copy(PurpleMediaCodec *codec); + +/** + * Creates a string representation of the codec. + * + * @param codec The codec to create the string of. + * + * @return The new string representation. + */ +gchar *purple_media_codec_to_string(const PurpleMediaCodec *codec); + +/** + * Adds an optional parameter to the codec. + * + * @param codec The codec to add the parameter to. + * @param name The name of the parameter to add. + * @param value The value of the parameter to add. + */ +void purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec, + const gchar *name, const gchar *value); + +/** + * Removes an optional parameter from the codec. + * + * @param codec The codec to remove the parameter from. + * @param param A pointer to the parameter to remove. + */ +void purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec, + PurpleMediaCodecParameter *param); + +/** + * Gets an optional parameter based on the values given. + * + * @param codec The codec to find the parameter in. + * @param name The name of the parameter to search for. + * @param value The value to search for or NULL. + * + * @return The value found or NULL. + */ +PurpleMediaCodecParameter *purple_media_codec_get_optional_parameter( + PurpleMediaCodec *codec, const gchar *name, + const gchar *value); + +/** + * Copies a GList of PurpleMediaCodec and its contents. + * + * @param codecs The list of codecs to be copied. + * + * @return The copy of the GList. + */ +GList *purple_media_codec_list_copy(GList *codecs); + +/** + * Frees a GList of PurpleMediaCodec and its contents. + * + * @param codecs The list of codecs to be freed. + */ +void purple_media_codec_list_free(GList *codecs); + +/** + * 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 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 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 PurpleConnection this media session is on. + * + * @param media The media session to retrieve the connection from. + * + * @return The connection retrieved. + */ +PurpleConnection *purple_media_get_connection(PurpleMedia *media); + +/** + * Gets the prpl data from the media session. + * + * @param media The media session to retrieve the prpl data from. + * + * @return The prpl data retrieved. + */ +gpointer purple_media_get_prpl_data(PurpleMedia *media); + +/** + * Sets the prpl data on the media session. + * + * @param media The media session to set the prpl data on. + * @param prpl_data The data to set on the media session. + */ +void purple_media_set_prpl_data(PurpleMedia *media, gpointer prpl_data); + +/** + * Signals an error in the media session. + * + * @param media The media object to set the state on. + * @param error The format of the error message to send in the signal. + * @param ... The arguments to plug into the format. + */ +void purple_media_error(PurpleMedia *media, const gchar *error, ...); + +/** + * Set the media session to the accepted state. + * + * @param media The media object to set the state on. + */ +void purple_media_accept(PurpleMedia *media); + +/** + * Set the media session to the rejected state. + * + * @param media The media object to set the state on. + */ +void purple_media_reject(PurpleMedia *media); + +/** + * Set the media session to the hangup state. + * + * @param media The media object to set the state on. + */ +void purple_media_hangup(PurpleMedia *media); + +/** + * Ends all streams that match the given parameters + * + * @param media The media object with which to end streams. + * @param session_id The session to end streams on. + * @param participant The participant to end streams with. + */ +void purple_media_end(PurpleMedia *media, const gchar *session_id, + const gchar *participant); + +/** + * Adds a stream to a session. + * + * It only adds a stream to one audio session or video session as + * the @c sess_id must be unique between sessions. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to add the stream to. + * @param who The name of the remote user to add the stream for. + * @param type The type of stream to create. + * @param transmitter The transmitter to use for the stream. + * @param num_params The number of parameters to pass to Farsight. + * @param params The parameters to pass to Farsight. + * + * @return @c TRUE The stream was added successfully, @c FALSE otherwise. + */ +gboolean purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who, + PurpleMediaSessionType type, const gchar *transmitter, + guint num_params, GParameter *params); + +/** + * Removes a stream from a session. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to remove the stream from. + * @param who The name of the remote user to remove the stream from. + */ +void purple_media_remove_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who); + +/** + * Gets the session type from a session + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to get the type from. + * + * @return The retreived session type. + */ +PurpleMediaSessionType purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id); + +/** + * Gets the codecs from a session. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to get the codecs from. + * + * @return The retreieved codecs. + */ +GList *purple_media_get_codecs(PurpleMedia *media, const gchar *sess_id); + +/** + * Adds remote candidates to the stream. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session find the stream in. + * @param name The name of the remote user to add the candidates for. + * @param remote_candidates The remote candidates to add. + */ +void purple_media_add_remote_candidates(PurpleMedia *media, + const gchar *sess_id, + const gchar *name, + GList *remote_candidates); + +/** + * Gets the local candidates from a stream. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to find the stream in. + * @param name The name of the remote user to get the candidates from. + */ +GList *purple_media_get_local_candidates(PurpleMedia *media, + const gchar *sess_id, + const gchar *name); + +/** + * Gets the active local candidates 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 candidates retrieved. + */ +GList *purple_media_get_active_local_candidates(PurpleMedia *media, + const gchar *sess_id, const gchar *name); + +/** + * Gets the active remote candidates 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 candidates retrieved. + */ +GList *purple_media_get_active_remote_candidates(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 set of streams are prepared + * + * @param media The media object to find the remote user in. + * @param session_id The session id of the session to check. + * @param participant The remote user to check for. + * + * @return @c TRUE All streams for the given session_id/participant combination have candidates prepared, @c FALSE otherwise. + */ +gboolean purple_media_candidates_prepared(PurpleMedia *media, + const gchar *session_id, const gchar *participant); + +/** + * Sets the send codec for the a session. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to set the codec for. + * @param codec The codec to set the session to stream. + * + * @return @c TRUE The codec was successfully changed, or @c FALSE otherwise. + */ +gboolean purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, PurpleMediaCodec *codec); + +/** + * Gets whether a session's codecs are ready to be used. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to check. + * + * @return @c TRUE The codecs are ready, or @c FALSE otherwise. + */ +gboolean purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id); + +/** + * Gets whether a streams selected have been accepted. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to check. + * @param participant The participant to check. + * + * @return @c TRUE The selected streams have been accepted, or @c FALSE otherwise. + */ +gboolean purple_media_accepted(PurpleMedia *media, const gchar *sess_id, + const gchar *participant); + +/** + * Mutes or unmutes all the audio local audio sources. + * + * @param media The media object to mute or unmute + * @param active @c TRUE to mutes all of the local audio sources, or @c FALSE to unmute. + */ +void purple_media_mute(PurpleMedia *media, gboolean active); + +/** + * Sets the input volume of all the selected sessions. + * + * @param media The media object the sessions are in. + * @param session_id The session to select (if any). + * @param level The level to set the volume to. + */ +void purple_media_set_input_volume(PurpleMedia *media, const gchar *session_id, double level); + +/** + * Sets the output volume of all the selected streams. + * + * @param media The media object the streams are in. + * @param session_id The session to limit the streams to (if any). + * @param participant The participant to limit the streams to (if any). + * @param level The level to set the volume to. + */ +void purple_media_set_output_volume(PurpleMedia *media, const gchar *session_id, + const gchar *participant, double level); + +/** + * Sets a video output window for the given session/stream. + * + * @param media The media instance to set the output window on. + * @param session_id The session to set the output window on. + * @param participant Optionally, the participant to set the output window on. + * @param window_id The window id use for embedding the video in. + * + * @return An id to reference the output window. + */ +gulong purple_media_set_output_window(PurpleMedia *media, + const gchar *session_id, const gchar *participant, + gulong window_id); + +/** + * Removes all output windows from a given media session. + * + * @param media The instance to remove all output windows from. + */ +void purple_media_remove_output_windows(PurpleMedia *media); + +/** + * Gets the tee from a given session/stream. + * + * @param media The instance to get the tee from. + * @param session_id The id of the session to get the tee from. + * @param participant Optionally, the participant of the stream to get the tee from. + * + * @return The GstTee element from the chosen session/stream. + */ +GstElement *purple_media_get_tee(PurpleMedia *media, + const gchar *session_id, const gchar *participant); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* USE_VV */ + + +#endif /* __MEDIA_H_ */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/mediamanager.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/mediamanager.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,636 @@ +/** + * @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 +#include + +typedef struct _PurpleMediaOutputWindow PurpleMediaOutputWindow; + +struct _PurpleMediaOutputWindow +{ + gulong id; + PurpleMedia *media; + gchar *session_id; + gchar *participant; + gulong window_id; + GstElement *sink; +}; + +struct _PurpleMediaManagerPrivate +{ + GstElement *pipeline; + GList *medias; + GList *elements; + GList *output_windows; + gulong next_output_window_id; + + PurpleMediaElementInfo *video_src; + PurpleMediaElementInfo *video_sink; + PurpleMediaElementInfo *audio_src; + PurpleMediaElementInfo *audio_sink; +}; + +#define PURPLE_MEDIA_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerPrivate)) + +static void purple_media_manager_class_init (PurpleMediaManagerClass *klass); +static void purple_media_manager_init (PurpleMediaManager *media); +static void purple_media_manager_finalize (GObject *object); + +static GObjectClass *parent_class = NULL; + + + +enum { + INIT_MEDIA, + LAST_SIGNAL +}; +static guint purple_media_manager_signals[LAST_SIGNAL] = {0}; + +enum { + PROP_0, + PROP_FARSIGHT_SESSION, + PROP_NAME, + PROP_CONNECTION, + PROP_MIC_ELEMENT, + PROP_SPEAKER_ELEMENT, +}; + +GType +purple_media_manager_get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(PurpleMediaManagerClass), + NULL, + NULL, + (GClassInitFunc) purple_media_manager_class_init, + NULL, + NULL, + sizeof(PurpleMediaManager), + 0, + (GInstanceInitFunc) purple_media_manager_init, + NULL + }; + type = g_type_register_static(G_TYPE_OBJECT, "PurpleMediaManager", &info, 0); + } + return type; +} + + +static void +purple_media_manager_class_init (PurpleMediaManagerClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = purple_media_manager_finalize; + + purple_media_manager_signals[INIT_MEDIA] = g_signal_new ("init-media", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + purple_smarshal_BOOLEAN__OBJECT_POINTER_STRING, + G_TYPE_BOOLEAN, 3, PURPLE_TYPE_MEDIA, + G_TYPE_POINTER, G_TYPE_STRING); + g_type_class_add_private(klass, sizeof(PurpleMediaManagerPrivate)); +} + +static void +purple_media_manager_init (PurpleMediaManager *media) +{ + media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media); + media->priv->medias = NULL; + media->priv->next_output_window_id = 1; +} + +static void +purple_media_manager_finalize (GObject *media) +{ + PurpleMediaManagerPrivate *priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media); + for (; priv->medias; priv->medias = + g_list_delete_link(priv->medias, priv->medias)) { + g_object_unref(priv->medias->data); + } + for (; priv->elements; priv->elements = + g_list_delete_link(priv->elements, priv->elements)); + parent_class->finalize(media); +} + +PurpleMediaManager * +purple_media_manager_get() +{ + static PurpleMediaManager *manager = NULL; + + if (manager == NULL) + manager = PURPLE_MEDIA_MANAGER(g_object_new(purple_media_manager_get_type(), NULL)); + return manager; +} + +static gboolean +pipeline_bus_call(GstBus *bus, GstMessage *msg, PurpleMediaManager *manager) +{ + switch(GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + purple_debug_info("mediamanager", "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("mediamanager", + "gst pipeline error: %s\n", + err->message); + g_error_free(err); + + if (debug) { + purple_debug_error("mediamanager", + "Debug details: %s\n", debug); + g_free (debug); + } + break; + } + default: + break; + } + return TRUE; +} + +GstElement * +purple_media_manager_get_pipeline(PurpleMediaManager *manager) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); + + if (manager->priv->pipeline == NULL) { + GstBus *bus; + manager->priv->pipeline = gst_pipeline_new(NULL); + + bus = gst_pipeline_get_bus( + GST_PIPELINE(manager->priv->pipeline)); + gst_bus_add_signal_watch(GST_BUS(bus)); + g_signal_connect(G_OBJECT(bus), "message", + G_CALLBACK(pipeline_bus_call), manager); + gst_bus_set_sync_handler(bus, + gst_bus_sync_signal_handler, NULL); + gst_object_unref(bus); + + gst_element_set_state(manager->priv->pipeline, + GST_STATE_PLAYING); + } + + return manager->priv->pipeline; +} + +PurpleMedia * +purple_media_manager_create_media(PurpleMediaManager *manager, + PurpleConnection *gc, + const char *conference_type, + const char *remote_user, + gboolean initiator) +{ + PurpleMedia *media; + FsConference *conference = FS_CONFERENCE(gst_element_factory_make(conference_type, NULL)); + GstStateChangeReturn ret; + gboolean signal_ret; + + if (conference == NULL) { + purple_conv_present_error(remote_user, + purple_connection_get_account(gc), + _("Error creating conference.")); + purple_debug_error("media", "Conference == NULL\n"); + return NULL; + } + + media = PURPLE_MEDIA(g_object_new(purple_media_get_type(), + "manager", manager, + "connection", gc, + "conference", conference, + "initiator", initiator, + NULL)); + + ret = gst_element_set_state(GST_ELEMENT(conference), GST_STATE_PLAYING); + + if (ret == GST_STATE_CHANGE_FAILURE) { + purple_conv_present_error(remote_user, + purple_connection_get_account(gc), + _("Error creating conference.")); + purple_debug_error("media", "Failed to start conference.\n"); + g_object_unref(media); + return NULL; + } + + g_signal_emit(manager, purple_media_manager_signals[INIT_MEDIA], 0, + media, gc, remote_user, &signal_ret); + + if (signal_ret == FALSE) { + g_object_unref(media); + return NULL; + } + + manager->priv->medias = g_list_append(manager->priv->medias, media); + return media; +} + +GList * +purple_media_manager_get_media(PurpleMediaManager *manager) +{ + return manager->priv->medias; +} + +GList * +purple_media_manager_get_media_by_connection(PurpleMediaManager *manager, + PurpleConnection *pc) +{ + GList *media = NULL; + GList *iter; + + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); + + iter = manager->priv->medias; + for (; iter; iter = g_list_next(iter)) { + if (purple_media_get_connection(iter->data) == pc) { + media = g_list_prepend(media, iter->data); + } + } + + return media; +} + +void +purple_media_manager_remove_media(PurpleMediaManager *manager, + PurpleMedia *media) +{ + GList *list = g_list_find(manager->priv->medias, media); + if (list) + manager->priv->medias = + g_list_delete_link(manager->priv->medias, list); +} + +GstElement * +purple_media_manager_get_element(PurpleMediaManager *manager, + PurpleMediaSessionType type) +{ + GstElement *ret = NULL; + + /* TODO: If src, retrieve current src */ + /* TODO: Send a signal here to allow for overriding the source/sink */ + + if (type & PURPLE_MEDIA_SEND_AUDIO + && manager->priv->audio_src != NULL) + ret = manager->priv->audio_src->create(); + else if (type & PURPLE_MEDIA_RECV_AUDIO + && manager->priv->audio_sink != NULL) + ret = manager->priv->audio_sink->create(); + else if (type & PURPLE_MEDIA_SEND_VIDEO + && manager->priv->video_src != NULL) + ret = manager->priv->video_src->create(); + else if (type & PURPLE_MEDIA_RECV_VIDEO + && manager->priv->video_sink != NULL) + ret = manager->priv->video_sink->create(); + + if (ret == NULL) + purple_debug_error("media", "Error creating source or sink\n"); + + return ret; +} + +PurpleMediaElementInfo * +purple_media_manager_get_element_info(PurpleMediaManager *manager, + const gchar *id) +{ + GList *iter; + + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); + + iter = manager->priv->elements; + + for (; iter; iter = g_list_next(iter)) { + PurpleMediaElementInfo *info = iter->data; + if (!strcmp(info->id, id)) + return info; + } + + return NULL; +} + +gboolean +purple_media_manager_register_element(PurpleMediaManager *manager, + PurpleMediaElementInfo *info) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); + g_return_val_if_fail(info != NULL, FALSE); + + if (purple_media_manager_get_element_info(manager, info->id) != NULL) + return FALSE; + + manager->priv->elements = + g_list_prepend(manager->priv->elements, info); + return TRUE; +} + +gboolean +purple_media_manager_unregister_element(PurpleMediaManager *manager, + const gchar *id) +{ + PurpleMediaElementInfo *info; + + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); + + info = purple_media_manager_get_element_info(manager, id); + + if (info == NULL) + return FALSE; + + if (manager->priv->audio_src == info) + manager->priv->audio_src = NULL; + if (manager->priv->audio_sink == info) + manager->priv->audio_sink = NULL; + if (manager->priv->video_src == info) + manager->priv->video_src = NULL; + if (manager->priv->video_sink == info) + manager->priv->video_sink = NULL; + + manager->priv->elements = g_list_remove( + manager->priv->elements, info); + return TRUE; +} + +gboolean +purple_media_manager_set_active_element(PurpleMediaManager *manager, + PurpleMediaElementInfo *info) +{ + gboolean ret = FALSE; + + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); + g_return_val_if_fail(info != NULL, FALSE); + + if (purple_media_manager_get_element_info(manager, info->id) == NULL) + purple_media_manager_register_element(manager, info); + + if (info->type & PURPLE_MEDIA_ELEMENT_SRC) { + if (info->type & PURPLE_MEDIA_ELEMENT_AUDIO) { + manager->priv->audio_src = info; + ret = TRUE; + } + if (info->type & PURPLE_MEDIA_ELEMENT_VIDEO) { + manager->priv->video_src = info; + ret = TRUE; + } + } + if (info->type & PURPLE_MEDIA_ELEMENT_SINK) { + if (info->type & PURPLE_MEDIA_ELEMENT_AUDIO) { + manager->priv->audio_sink = info; + ret = TRUE; + } + if (info->type & PURPLE_MEDIA_ELEMENT_VIDEO) { + manager->priv->video_sink = info; + ret = TRUE; + } + } + + return ret; +} + +PurpleMediaElementInfo * +purple_media_manager_get_active_element(PurpleMediaManager *manager, + PurpleMediaElementType type) +{ + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); + + if (type & PURPLE_MEDIA_ELEMENT_SRC) { + if (type & PURPLE_MEDIA_ELEMENT_AUDIO) + return manager->priv->audio_src; + else if (type & PURPLE_MEDIA_ELEMENT_VIDEO) + return manager->priv->video_src; + } else if (type & PURPLE_MEDIA_ELEMENT_SINK) { + if (type & PURPLE_MEDIA_ELEMENT_AUDIO) + return manager->priv->audio_sink; + else if (type & PURPLE_MEDIA_ELEMENT_VIDEO) + return manager->priv->video_sink; + } + + return NULL; +} + +static void +window_id_cb(GstBus *bus, GstMessage *msg, PurpleMediaOutputWindow *ow) +{ + if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT || + !gst_structure_has_name(msg->structure, + "prepare-xwindow-id")) + return; + + if (GST_ELEMENT_PARENT(GST_MESSAGE_SRC(msg)) == ow->sink) { + g_signal_handlers_disconnect_matched(bus, G_SIGNAL_MATCH_FUNC + | G_SIGNAL_MATCH_DATA, 0, 0, NULL, + window_id_cb, ow); + + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY( + GST_MESSAGE_SRC(msg)), ow->window_id); + } +} + +gboolean +purple_media_manager_create_output_window(PurpleMediaManager *manager, + PurpleMedia *media, const gchar *session_id, + const gchar *participant) +{ + GList *iter; + + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + iter = manager->priv->output_windows; + for(; iter; iter = g_list_next(iter)) { + PurpleMediaOutputWindow *ow = iter->data; + + if (ow->sink == NULL && ow->media == media && + ((participant != NULL && + ow->participant != NULL && + !strcmp(participant, ow->participant)) || + (participant == ow->participant)) && + !strcmp(session_id, ow->session_id)) { + GstBus *bus; + GstElement *queue; + GstElement *tee = purple_media_get_tee(media, + session_id, participant); + + if (tee == NULL) + continue; + + queue = gst_element_factory_make( + "queue", NULL); + ow->sink = purple_media_manager_get_element( + manager, PURPLE_MEDIA_RECV_VIDEO); + + if (participant == NULL) { + /* aka this is a preview sink */ + GObjectClass *klass = + G_OBJECT_GET_CLASS(ow->sink); + if (g_object_class_find_property(klass, + "sync")) + g_object_set(G_OBJECT(ow->sink), + "sync", "FALSE", NULL); + if (g_object_class_find_property(klass, + "async")) + g_object_set(G_OBJECT(ow->sink), + "async", FALSE, NULL); + } + + gst_bin_add_many(GST_BIN(GST_ELEMENT_PARENT(tee)), + queue, ow->sink, NULL); + + bus = gst_pipeline_get_bus(GST_PIPELINE( + manager->priv->pipeline)); + g_signal_connect(bus, "sync-message::element", + G_CALLBACK(window_id_cb), ow); + gst_object_unref(bus); + + gst_element_sync_state_with_parent(ow->sink); + gst_element_link(queue, ow->sink); + gst_element_sync_state_with_parent(queue); + gst_element_link(tee, queue); + } + } + return TRUE; +} + +gulong +purple_media_manager_set_output_window(PurpleMediaManager *manager, + PurpleMedia *media, const gchar *session_id, + const gchar *participant, gulong window_id) +{ + PurpleMediaOutputWindow *output_window; + + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + output_window = g_new0(PurpleMediaOutputWindow, 1); + output_window->id = manager->priv->next_output_window_id++; + output_window->media = media; + output_window->session_id = g_strdup(session_id); + output_window->participant = g_strdup(participant); + output_window->window_id = window_id; + + manager->priv->output_windows = g_list_prepend( + manager->priv->output_windows, output_window); + + if (purple_media_get_tee(media, session_id, participant) != NULL) + purple_media_manager_create_output_window(manager, + media, session_id, participant); + + return output_window->id; +} + +gboolean +purple_media_manager_remove_output_window(PurpleMediaManager *manager, + gulong output_window_id) +{ + PurpleMediaOutputWindow *output_window = NULL; + GList *iter; + + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); + + iter = manager->priv->output_windows; + for (; iter; iter = g_list_next(iter)) { + PurpleMediaOutputWindow *ow = iter->data; + if (ow->id == output_window_id) { + manager->priv->output_windows = g_list_delete_link( + manager->priv->output_windows, iter); + output_window = ow; + break; + } + } + + if (output_window == NULL) + return FALSE; + + if (output_window->sink != NULL) { + GstPad *pad = gst_element_get_static_pad( + output_window->sink, "sink"); + GstPad *peer = gst_pad_get_peer(pad); + GstElement *queue = GST_ELEMENT_PARENT(peer); + gst_object_unref(pad); + pad = gst_element_get_static_pad(queue, "sink"); + peer = gst_pad_get_peer(pad); + gst_object_unref(pad); + gst_element_release_request_pad(GST_ELEMENT_PARENT(peer), peer); + gst_element_set_locked_state(queue, TRUE); + gst_element_set_state(queue, GST_STATE_NULL); + gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(queue)), queue); + gst_element_set_locked_state(output_window->sink, TRUE); + gst_element_set_state(output_window->sink, GST_STATE_NULL); + gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(output_window->sink)), + output_window->sink); + } + + g_free(output_window->session_id); + g_free(output_window->participant); + g_free(output_window); + + return TRUE; +} + +void +purple_media_manager_remove_output_windows(PurpleMediaManager *manager, + PurpleMedia *media, const gchar *session_id, + const gchar *participant) +{ + GList *iter; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + iter = manager->priv->output_windows; + + for (; iter;) { + PurpleMediaOutputWindow *ow = iter->data; + iter = g_list_next(iter); + + if (media == ow->media && + ((session_id != NULL && ow->session_id != NULL && + !strcmp(session_id, ow->session_id)) || + (session_id == ow->session_id)) && + ((participant != NULL && ow->participant != NULL && + !strcmp(participant, ow->participant)) || + (participant == ow->participant))) + purple_media_manager_remove_output_window( + manager, ow->id); + } +} + +#endif /* USE_VV */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/mediamanager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/mediamanager.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,223 @@ +/** + * @file mediamanager.h Media Manager API + * @ingroup core + */ + +/* purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __MEDIA_MANAGER_H_ +#define __MEDIA_MANAGER_H_ + +#ifdef USE_VV + +#include +#include + +#include "connection.h" +#include "media.h" + +G_BEGIN_DECLS + +#define PURPLE_TYPE_MEDIA_MANAGER (purple_media_manager_get_type()) +#define PURPLE_MEDIA_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManager)) +#define PURPLE_MEDIA_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerClass)) +#define PURPLE_IS_MEDIA_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_MANAGER)) +#define PURPLE_IS_MEDIA_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_MANAGER)) +#define PURPLE_MEDIA_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerClass)) + +/** @copydoc _PurpleMediaManager */ +typedef struct _PurpleMediaManager PurpleMediaManager; +/** @copydoc _PurpleMediaManagerClass */ +typedef struct _PurpleMediaManagerClass PurpleMediaManagerClass; +/** @copydoc _PurpleMediaManagerPrivate */ +typedef struct _PurpleMediaManagerPrivate PurpleMediaManagerPrivate; +/** @copydoc _PurpleMediaElementInfo */ +typedef struct _PurpleMediaElementInfo PurpleMediaElementInfo; + +/** The media manager class. */ +struct _PurpleMediaManagerClass +{ + GObjectClass parent_class; /**< The parent class. */ +}; + +/** The media manager's data. */ +struct _PurpleMediaManager +{ + GObject parent; /**< The parent of this manager. */ + PurpleMediaManagerPrivate *priv; /**< Private data for the manager. */ +}; + +typedef enum { + PURPLE_MEDIA_ELEMENT_AUDIO = 1, /** supports audio */ + PURPLE_MEDIA_ELEMENT_VIDEO = 1 << 1, /** supports video */ + PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO = PURPLE_MEDIA_ELEMENT_AUDIO + | PURPLE_MEDIA_ELEMENT_VIDEO, /** supports audio and video */ + + PURPLE_MEDIA_ELEMENT_NO_SRCS = 0, /** has no src pads */ + PURPLE_MEDIA_ELEMENT_ONE_SRC = 1 << 2, /** has one src pad */ + PURPLE_MEDIA_ELEMENT_MULTI_SRC = 1 << 3, /** has multiple src pads */ + PURPLE_MEDIA_ELEMENT_REQUEST_SRC = 1 << 4, /** src pads must be requested */ + + PURPLE_MEDIA_ELEMENT_NO_SINKS = 0, /** has no sink pads */ + PURPLE_MEDIA_ELEMENT_ONE_SINK = 1 << 5, /** has one sink pad */ + PURPLE_MEDIA_ELEMENT_MULTI_SINK = 1 << 6, /** has multiple sink pads */ + PURPLE_MEDIA_ELEMENT_REQUEST_SINK = 1 << 7, /** sink pads must be requested */ + + PURPLE_MEDIA_ELEMENT_UNIQUE = 1 << 8, /** This element is unique and + only one instance of it should + be created at a time */ + + PURPLE_MEDIA_ELEMENT_SRC = 1 << 9, /** can be set as an active src */ + PURPLE_MEDIA_ELEMENT_SINK = 1 << 10, /** can be set as an active sink */ +} PurpleMediaElementType; + +struct _PurpleMediaElementInfo +{ + const gchar *id; + PurpleMediaElementType type; + GstElement *(*create)(void); +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @cname Media Manager API */ +/**************************************************************************/ +/*@{*/ + +/** + * Gets the media manager's GType. + * + * @return The media manager's GType. + */ +GType purple_media_manager_get_type(void); + +/** + * Gets the "global" media manager object. It's created if it doesn't already exist. + * + * @return The "global" instance of the media manager object. + */ +PurpleMediaManager *purple_media_manager_get(void); + +/** + * Gets the pipeline from the media manager. + * + * @param manager The media manager to get the pipeline from. + * + * @return The pipeline. + */ +GstElement * +purple_media_manager_get_pipeline(PurpleMediaManager *manager); + +/** + * Creates a media session. + * + * @param manager The media manager to create the session under. + * @param gc The connection to create the session on. + * @param conference_type The conference type to feed into Farsight2. + * @param remote_user The remote user to initiate the session with. + * + * @return A newly created media session. + */ +PurpleMedia *purple_media_manager_create_media(PurpleMediaManager *manager, + PurpleConnection *gc, + const char *conference_type, + const char *remote_user, + gboolean initiator); + +/** + * Gets all of the media sessions. + * + * @param manager The media manager to get all of the sessions from. + * + * @return A list of all the media sessions. + */ +GList *purple_media_manager_get_media(PurpleMediaManager *manager); + +/** + * Gets all of the media sessions for a given connection. + * + * @param manager The media manager to get the sessions from. + * @param pc The connection the sessions are on. + * + * @return A list of the media sessions on the given connection. + */ +GList *purple_media_manager_get_media_by_connection( + PurpleMediaManager *manager, PurpleConnection *pc); + +/** + * Removes a media session from the media manager. + * + * @param manager The media manager to remove the media session from. + * @param media The media session to remove. + */ +void +purple_media_manager_remove_media(PurpleMediaManager *manager, + PurpleMedia *media); + +/** + * Returns a GStreamer source or sink for audio or video. + * + * @param manager The media manager to use to obtain the source/sink. + * @param type The type of source/sink to get. + */ +GstElement *purple_media_manager_get_element(PurpleMediaManager *manager, + PurpleMediaSessionType type); + +PurpleMediaElementInfo *purple_media_manager_get_element_info( + PurpleMediaManager *manager, const gchar *name); +gboolean purple_media_manager_register_element(PurpleMediaManager *manager, + PurpleMediaElementInfo *info); +gboolean purple_media_manager_unregister_element(PurpleMediaManager *manager, + const gchar *name); +gboolean purple_media_manager_set_active_element(PurpleMediaManager *manager, + PurpleMediaElementInfo *info); +PurpleMediaElementInfo *purple_media_manager_get_active_element( + PurpleMediaManager *manager, PurpleMediaElementType type); +/** + * This shouldn't be called outside of mediamanager.c and media.c + */ +gboolean purple_media_manager_create_output_window( + PurpleMediaManager *manager, PurpleMedia *media, + const gchar *session_id, const gchar *participant); +gulong purple_media_manager_set_output_window(PurpleMediaManager *manager, + PurpleMedia *media, const gchar *session_id, + const gchar *participant, gulong window_id); +gboolean purple_media_manager_remove_output_window( + PurpleMediaManager *manager, gulong output_window_id); +void purple_media_manager_remove_output_windows( + PurpleMediaManager *manager, PurpleMedia *media, + const gchar *session_id, const gchar *participant); +/*}@*/ + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* USE_VV */ + + +#endif /* __MEDIA_MANAGER_H_ */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/network.c --- a/libpurple/network.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/network.c Sun Mar 22 23:33:42 2009 +0000 @@ -48,6 +48,7 @@ #include "prefs.h" #include "stun.h" #include "upnp.h" +#include "dnsquery.h" /* * Calling sizeof(struct ifreq) isn't always correct on @@ -100,6 +101,10 @@ static gboolean force_online; #endif +/* Cached IP addresses for STUN and TURN servers (set globally in prefs) */ +static gchar *stun_ip = NULL; +static gchar *turn_ip = NULL; + const unsigned char * purple_network_ip_atoi(const char *ip) { @@ -723,6 +728,14 @@ case NM_STATE_CONNECTED: /* Call res_init in case DNS servers have changed */ res_init(); + /* update STUN IP in case we it changed (theoretically we could + have gone from IPv4 to IPv6, f.ex. or we were previously + offline */ + purple_network_set_stun_server( + purple_prefs_get_string("/purple/network/stun_server")); + purple_network_set_turn_server( + purple_prefs_get_string("/purple/network/turn_server")); + if (ui_ops != NULL && ui_ops->network_connected != NULL) ui_ops->network_connected(); break; @@ -784,6 +797,88 @@ #endif +static void +purple_network_ip_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) +{ + const gchar **ip = (const gchar **) data; + + if (error_message) { + purple_debug_error("network", "lookup of IP address failed: %s\n", + error_message); + g_slist_free(hosts); + return; + } + + if (hosts && g_slist_next(hosts)) { + struct sockaddr *addr = g_slist_next(hosts)->data; + char dst[INET6_ADDRSTRLEN]; + + if (addr->sa_family == AF_INET6) { + inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, + dst, sizeof(dst)); + } else { + inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, + dst, sizeof(dst)); + } + + *ip = g_strdup(dst); + purple_debug_info("network", "set IP address: %s\n", *ip); + } + + g_slist_free(hosts); +} + +void +purple_network_set_stun_server(const gchar *stun_server) +{ + if (stun_server && stun_server[0] != '\0') { + if (purple_network_is_available()) { + purple_debug_info("network", "running DNS query for STUN server\n"); + purple_dnsquery_a(stun_server, 3478, purple_network_ip_lookup_cb, + &stun_ip); + } else { + purple_debug_info("network", + "network is unavailable, don't try to update STUN IP"); + } + } else if (stun_ip) { + g_free(stun_ip); + stun_ip = NULL; + } +} + +void +purple_network_set_turn_server(const gchar *turn_server) +{ + if (turn_server && turn_server[0] != '\0') { + if (purple_network_is_available()) { + purple_debug_info("network", "running DNS query for TURN server\n"); + purple_dnsquery_a(turn_server, + purple_prefs_get_int("/purple/network/turn_port"), + purple_network_ip_lookup_cb, &turn_ip); + } else { + purple_debug_info("network", + "network is unavailable, don't try to update TURN IP"); + } + } else if (turn_ip) { + g_free(turn_ip); + turn_ip = NULL; + } +} + + +const gchar * +purple_network_get_stun_ip(void) +{ + return stun_ip; +} + +const gchar * +purple_network_get_turn_ip(void) +{ + return turn_ip; +} + void * purple_network_get_handle(void) { @@ -816,6 +911,11 @@ #endif purple_prefs_add_none ("/purple/network"); + purple_prefs_add_string("/purple/network/stun_server", ""); + purple_prefs_add_string("/purple/network/turn_server", ""); + purple_prefs_add_int ("/purple/network/turn_port", 3478); + purple_prefs_add_string("/purple/network/turn_username", ""); + purple_prefs_add_string("/purple/network/turn_password", ""); purple_prefs_add_bool ("/purple/network/auto_ip", TRUE); purple_prefs_add_string("/purple/network/public_ip", ""); purple_prefs_add_bool ("/purple/network/map_ports", TRUE); @@ -854,6 +954,11 @@ purple_pmp_init(); purple_upnp_init(); + + purple_network_set_stun_server( + purple_prefs_get_string("/purple/network/stun_server")); + purple_network_set_turn_server( + purple_prefs_get_string("/purple/network/turn_server")); } void @@ -895,4 +1000,7 @@ #endif purple_signal_unregister(purple_network_get_handle(), "network-configuration-changed"); + + if (stun_ip) + g_free(stun_ip); } diff -r 399776a9ad98 -r 7b63af454f26 libpurple/network.h --- a/libpurple/network.h Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/network.h Sun Mar 22 23:33:42 2009 +0000 @@ -225,6 +225,37 @@ */ void *purple_network_get_handle(void); +/** + * Update the STUN server IP given the host name + * Will result in a DNS query being executed asynchronous + * + * @param stun_server The host name of the STUN server to set + */ +void purple_network_set_stun_server(const gchar *stun_server); + +/** + * Get the IP address of the STUN server as a string representation + * + * @return the IP address + */ +const gchar *purple_network_get_stun_ip(void); + +/** + * Update the TURN server IP given the host name + * Will result in a DNS query being executed asynchronous + * + * @param stun_server The host name of the STUN server to set + */ +void purple_network_set_turn_server(const gchar *stun_server); + +/** + * Get the IP address of the STUN server as a string representation + * + * @return the IP address + */ +const gchar *purple_network_get_turn_ip(void); + + /** * Initializes the network subsystem. */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/plugins/Makefile.am --- a/libpurple/plugins/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/plugins/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -144,6 +144,9 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) \ $(GLIB_CFLAGS) \ $(PLUGIN_CFLAGS) \ $(DBUS_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/plugins/perl/Makefile.am --- a/libpurple/plugins/perl/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/plugins/perl/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -5,7 +5,7 @@ plugin_LTLIBRARIES = perl.la perl_la_LDFLAGS = -module -avoid-version -perl_la_LIBADD = $(GLIB_LIBS) $(PERL_LIBS) +perl_la_LIBADD = $(GLIB_LIBS) $(PERL_LIBS) $(FARSIGHT_LIBS) perl_la_SOURCES = \ perl.c \ perl-common.c \ @@ -167,4 +167,5 @@ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(PLUGIN_CFLAGS) \ - $(PERL_CFLAGS) + $(PERL_CFLAGS) \ + $(FARSIGHT_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/plugins/ssl/Makefile.am --- a/libpurple/plugins/ssl/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/plugins/ssl/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -52,6 +52,9 @@ -I$(top_builddir)/libpurple \ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) \ $(PLUGIN_CFLAGS) ssl_gnutls_la_CFLAGS = $(AM_CPPFLAGS) $(GNUTLS_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/plugins/tcl/Makefile.am --- a/libpurple/plugins/tcl/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/plugins/tcl/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -7,7 +7,7 @@ tcl_la_SOURCES = tcl.c tcl_glib.c tcl_glib.h tcl_cmds.c tcl_signals.c tcl_purple.h \ tcl_ref.c tcl_cmd.c -tcl_la_LIBADD = $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS) +tcl_la_LIBADD = $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS) $(FARSIGHT_LIBS) EXTRA_DIST = signal-test.tcl Makefile.mingw @@ -18,5 +18,6 @@ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(PLUGIN_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(TK_CFLAGS) \ $(TCL_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/bonjour/Makefile.am --- a/libpurple/protocols/bonjour/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/bonjour/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -50,4 +50,11 @@ $(GLIB_CFLAGS) \ $(DEBUG_CFLAGS) \ $(LIBXML_CFLAGS) \ - $(AVAHI_CFLAGS) \ No newline at end of file + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) + +#if MDNS_AVAHI +# AM_CPPFLAGS += $(AVAHI_CFLAGS) +#else +#endif diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/bonjour/bonjour.c --- a/libpurple/protocols/bonjour/bonjour.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Sun Mar 22 23:33:42 2009 +0000 @@ -498,13 +498,13 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, - sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = @@ -727,3 +727,4 @@ } PURPLE_INIT_PLUGIN(bonjour, init_plugin, info); + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/gg/Makefile.am --- a/libpurple/protocols/gg/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/gg/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -71,5 +71,8 @@ -I$(top_builddir)/libpurple \ $(INTGG_CFLAGS) \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) \ $(DEBUG_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/gg/gg.c --- a/libpurple/protocols/gg/gg.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/gg/gg.c Sun Mar 22 23:33:42 2009 +0000 @@ -2293,13 +2293,13 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = { @@ -2377,3 +2377,4 @@ PURPLE_INIT_PLUGIN(gg, init_plugin, info); /* vim: set ts=8 sts=0 sw=8 noet: */ + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/irc/Makefile.am --- a/libpurple/protocols/irc/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/irc/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -35,4 +35,6 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ $(DEBUG_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/irc/irc.c --- a/libpurple/protocols/irc/irc.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/irc/irc.c Sun Mar 22 23:33:42 2009 +0000 @@ -912,13 +912,13 @@ NULL, /* whiteboard_prpl_ops */ irc_send_raw, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, - sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static gboolean load_plugin (PurplePlugin *plugin) { diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/Makefile.am --- a/libpurple/protocols/jabber/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -23,6 +23,20 @@ iq.h \ jabber.c \ jabber.h \ + jingle/jingle.c \ + jingle/jingle.h \ + jingle/content.c \ + jingle/content.h \ + jingle/iceudp.c \ + jingle/iceudp.h \ + jingle/rawudp.c \ + jingle/rawudp.h \ + jingle/rtp.c \ + jingle/rtp.h \ + jingle/session.c \ + jingle/session.h \ + jingle/transport.c \ + jingle/transport.h \ jutil.c \ jutil.h \ message.c \ @@ -82,4 +96,6 @@ -I$(top_builddir)/libpurple \ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ $(LIBXML_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/Makefile.mingw --- a/libpurple/protocols/jabber/Makefile.mingw Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/Makefile.mingw Sun Mar 22 23:33:42 2009 +0000 @@ -54,6 +54,13 @@ ibb.c \ iq.c \ jabber.c \ + jingle/jingle.c \ + jingle/content.c \ + jingle/iceudp.c \ + jingle/rawudp.c \ + jingle/rtp.c \ + jingle/session.c \ + jingle/transport.c \ jutil.c \ message.c \ oob.c \ @@ -79,6 +86,7 @@ ## LIBS = \ -lglib-2.0 \ + -lgobject-2.0 \ -lxml2 \ -lws2_32 \ -lintl \ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/caps.c --- a/libpurple/protocols/jabber/caps.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/caps.c Sun Mar 22 23:33:42 2009 +0000 @@ -27,6 +27,7 @@ #include "util.h" #include "iq.h" + #define JABBER_CAPS_FILENAME "xmpp-caps.xml" static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Sun Mar 22 23:33:42 2009 +0000 @@ -28,6 +28,7 @@ #include "iq.h" #include "disco.h" #include "jabber.h" +#include "jingle/jingle.h" #include "presence.h" #include "roster.h" #include "pep.h" @@ -88,7 +89,7 @@ void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) { const char *from = xmlnode_get_attrib(packet, "from"); const char *type = xmlnode_get_attrib(packet, "type"); - + if(!from || !type) return; @@ -114,7 +115,7 @@ if(node) xmlnode_set_attrib(query, "node", node); - + if(!node || !strcmp(node, CAPS0115_NODE "#" VERSION)) { identity = xmlnode_new_child(query, "identity"); xmlnode_set_attrib(identity, "category", "client"); @@ -149,6 +150,16 @@ SUPPORT_FEATURE(feat->namespace); } } +#ifdef USE_VV + } else if (node && !strcmp(node, CAPS0115_NODE "#voice-v1")) { + SUPPORT_FEATURE("http://www.google.com/xmpp/protocol/session"); + SUPPORT_FEATURE("http://www.google.com/xmpp/protocol/voice/v1"); + SUPPORT_FEATURE(JINGLE); + SUPPORT_FEATURE(JINGLE_APP_RTP_SUPPORT_AUDIO); + SUPPORT_FEATURE(JINGLE_APP_RTP_SUPPORT_VIDEO); + SUPPORT_FEATURE(JINGLE_TRANSPORT_RAWUDP); + SUPPORT_FEATURE(JINGLE_TRANSPORT_ICEUDP); +#endif } else { const char *ext = NULL; unsigned pos; @@ -443,7 +454,12 @@ if (!strcmp(name, "Google Talk")) { purple_debug_info("jabber", "Google Talk!\n"); js->googletalk = TRUE; - } + + /* autodiscover stun and relays */ + jabber_google_send_jingle_info(js); + } else { + /* TODO: add external service discovery here... */ + } } for (child = xmlnode_get_child(query, "feature"); child; diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/google.c --- a/libpurple/protocols/jabber/google.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/google.c Sun Mar 22 23:33:42 2009 +0000 @@ -20,8 +20,11 @@ #include "internal.h" #include "debug.h" +#include "mediamanager.h" #include "util.h" #include "privacy.h" +#include "dnsquery.h" +#include "network.h" #include "buddy.h" #include "google.h" @@ -29,6 +32,547 @@ #include "presence.h" #include "iq.h" +#include "jingle/jingle.h" + +#ifdef USE_VV + +typedef struct { + char *id; + char *initiator; +} GoogleSessionId; + +typedef enum { + UNINIT, + SENT_INITIATE, + RECEIVED_INITIATE, + IN_PRORESS, + TERMINATED +} GoogleSessionState; + +typedef struct { + GoogleSessionId id; + GoogleSessionState state; + PurpleMedia *media; + JabberStream *js; + char *remote_jid; +} GoogleSession; + +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_free(session->id.id); + g_free(session->id.initiator); + g_free(session->remote_jid); + g_free(session); +} + +static xmlnode * +google_session_create_xmlnode(GoogleSession *session, const char *type) +{ + xmlnode *node = xmlnode_new("session"); + xmlnode_set_namespace(node, "http://www.google.com/session"); + xmlnode_set_attrib(node, "id", session->id.id); + xmlnode_set_attrib(node, "initiator", session->id.initiator); + xmlnode_set_attrib(node, "type", type); + return node; +} + +static void +google_session_send_terminate(GoogleSession *session) +{ + xmlnode *sess; + JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); + + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + sess = google_session_create_xmlnode(session, "terminate"); + xmlnode_insert_child(iq->node, sess); + + jabber_iq_send(iq); + google_session_destroy(session); +} + +static void +google_session_send_candidates(PurpleMedia *media, gchar *session_id, + gchar *participant, GoogleSession *session) +{ + JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); + GList *candidates = purple_media_get_local_candidates(session->media, "google-voice", + session->remote_jid); + PurpleMediaCandidate *transport; + xmlnode *sess; + xmlnode *candidate; + sess = google_session_create_xmlnode(session, "candidates"); + xmlnode_insert_child(iq->node, sess); + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + + for (;candidates;candidates = candidates->next) { + char port[8]; + char pref[8]; + transport = (PurpleMediaCandidate*)(candidates->data); + + if (!strcmp(transport->ip, "127.0.0.1")) + continue; + + candidate = xmlnode_new("candidate"); + + g_snprintf(port, sizeof(port), "%d", transport->port); + g_snprintf(pref, sizeof(pref), "%d", transport->priority); + + xmlnode_set_attrib(candidate, "address", transport->ip); + xmlnode_set_attrib(candidate, "port", port); + xmlnode_set_attrib(candidate, "name", "rtp"); + xmlnode_set_attrib(candidate, "username", transport->username); + /* + * As of this writing, Farsight 2 in Google compatibility + * mode doesn't provide a password. The Gmail client + * requires this to be set. + */ + xmlnode_set_attrib(candidate, "password", + transport->password != NULL ? + transport->password : ""); + xmlnode_set_attrib(candidate, "preference", pref); + xmlnode_set_attrib(candidate, "protocol", transport->proto == + PURPLE_MEDIA_NETWORK_PROTOCOL_UDP ? "udp" : "tcp"); + xmlnode_set_attrib(candidate, "type", transport->type == + PURPLE_MEDIA_CANDIDATE_TYPE_HOST ? "local" : + transport->type == + PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX ? "stun" : + transport->type == + PURPLE_MEDIA_CANDIDATE_TYPE_RELAY ? "relay" : NULL); + xmlnode_set_attrib(candidate, "generation", "0"); + xmlnode_set_attrib(candidate, "network", "0"); + xmlnode_insert_child(sess, candidate); + } + jabber_iq_send(iq); +} + +static void +google_session_ready(GoogleSession *session) +{ + PurpleMedia *media = session->media; + if (purple_media_codecs_ready(media, NULL) && + purple_media_candidates_prepared(media, NULL, NULL)) { + gchar *me = g_strdup_printf("%s@%s/%s", + session->js->user->node, + session->js->user->domain, + session->js->user->resource); + JabberIq *iq; + xmlnode *sess, *desc, *payload; + GList *codecs, *iter; + gboolean is_initiator = !strcmp(session->id.initiator, me); + + if (!is_initiator && + !purple_media_accepted(media, NULL, NULL)) { + g_free(me); + return; + } + + iq = jabber_iq_new(session->js, JABBER_IQ_SET); + + if (is_initiator) { + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + xmlnode_set_attrib(iq->node, "from", session->id.initiator); + sess = google_session_create_xmlnode(session, "initiate"); + } else { + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + xmlnode_set_attrib(iq->node, "from", me); + sess = google_session_create_xmlnode(session, "accept"); + } + xmlnode_insert_child(iq->node, sess); + desc = xmlnode_new_child(sess, "description"); + xmlnode_set_namespace(desc, "http://www.google.com/session/phone"); + + codecs = purple_media_get_codecs(media, "google-voice"); + + for (iter = codecs; iter; iter = g_list_next(iter)) { + PurpleMediaCodec *codec = (PurpleMediaCodec*)iter->data; + gchar *id = g_strdup_printf("%d", codec->id); + gchar *clock_rate = g_strdup_printf("%d", codec->clock_rate); + payload = xmlnode_new_child(desc, "payload-type"); + xmlnode_set_attrib(payload, "id", id); + xmlnode_set_attrib(payload, "name", codec->encoding_name); + xmlnode_set_attrib(payload, "clockrate", clock_rate); + g_free(clock_rate); + g_free(id); + } + purple_media_codec_list_free(codecs); + + jabber_iq_send(iq); + + google_session_send_candidates(session->media, + "google-voice", session->remote_jid, session); + + g_signal_handlers_disconnect_by_func(G_OBJECT(session->media), + G_CALLBACK(google_session_ready), session); + } +} + +static void +google_session_state_changed_cb(PurpleMedia *media, PurpleMediaState state, + gchar *sid, gchar *name, GoogleSession *session) +{ + if (sid == NULL && name == NULL) { + if (state == PURPLE_MEDIA_STATE_END) { + google_session_destroy(session); + } + } +} + +static void +google_session_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, + gchar *sid, gchar *name, GoogleSession *session) +{ + if (type == PURPLE_MEDIA_INFO_HANGUP) { + xmlnode *sess; + JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); + + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + sess = google_session_create_xmlnode(session, "terminate"); + xmlnode_insert_child(iq->node, sess); + + jabber_iq_send(iq); + } else if (type == PURPLE_MEDIA_INFO_REJECT) { + xmlnode *sess; + JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); + + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + sess = google_session_create_xmlnode(session, "reject"); + xmlnode_insert_child(iq->node, sess); + + jabber_iq_send(iq); + } +} + +static GParameter * +jabber_google_session_get_params(JabberStream *js, guint *num) +{ + guint num_params; + GParameter *params = jingle_get_params(js, &num_params); + GParameter *new_params = g_new0(GParameter, num_params + 1); + + memcpy(new_params, params, sizeof(GParameter) * num_params); + + purple_debug_info("jabber", "setting Google jingle compatibility param\n"); + new_params[num_params].name = "compatibility-mode"; + g_value_init(&new_params[num_params].value, G_TYPE_UINT); + g_value_set_uint(&new_params[num_params].value, 1); /* NICE_COMPATIBILITY_GOOGLE */ + + g_free(params); + *num = num_params + 1; + return new_params; +} + + +PurpleMedia* +jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type) +{ + GoogleSession *session; + JabberBuddy *jb; + JabberBuddyResource *jbr; + gchar *jid; + GParameter *params; + guint num_params; + + /* construct JID to send to */ + jb = jabber_buddy_find(js, who, FALSE); + if (!jb) { + purple_debug_error("jingle-rtp", + "Could not find Jabber buddy\n"); + return NULL; + } + jbr = jabber_buddy_find_resource(jb, NULL); + if (!jbr) { + purple_debug_error("jingle-rtp", + "Could not find buddy's resource\n"); + } + + if ((strchr(who, '/') == NULL) && jbr && (jbr->name != NULL)) { + jid = g_strdup_printf("%s/%s", who, jbr->name); + } else { + jid = g_strdup(who); + } + + session = g_new0(GoogleSession, 1); + session->id.id = jabber_get_next_id(js); + session->id.initiator = g_strdup_printf("%s@%s/%s", js->user->node, + js->user->domain, js->user->resource); + session->state = SENT_INITIATE; + session->js = js; + session->remote_jid = jid; + + session->media = purple_media_manager_create_media( + purple_media_manager_get(), js->gc, + "fsrtpconference", session->remote_jid, TRUE); + + purple_media_set_prpl_data(session->media, session); + + params = jabber_google_session_get_params(js, &num_params); + + if (purple_media_add_stream(session->media, "google-voice", + session->remote_jid, PURPLE_MEDIA_AUDIO, + "nice", num_params, params) == FALSE) { + purple_media_error(session->media, "Error adding stream."); + purple_media_hangup(session->media); + google_session_destroy(session); + g_free(params); + return NULL; + } + + g_signal_connect_swapped(G_OBJECT(session->media), + "candidates-prepared", + G_CALLBACK(google_session_ready), session); + g_signal_connect_swapped(G_OBJECT(session->media), "codecs-changed", + G_CALLBACK(google_session_ready), session); + g_signal_connect(G_OBJECT(session->media), "state-changed", + G_CALLBACK(google_session_state_changed_cb), session); + g_signal_connect(G_OBJECT(session->media), "stream-info", + G_CALLBACK(google_session_stream_info_cb), session); + + g_free(params); + + return session->media; +} + +static void +google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) +{ + JabberIq *result; + GList *codecs = NULL; + xmlnode *desc_element, *codec_element; + PurpleMediaCodec *codec; + const char *id, *encoding_name, *clock_rate; + GParameter *params; + guint num_params; + + if (session->state != UNINIT) { + purple_debug_error("jabber", "Received initiate for active session.\n"); + return; + } + + session->media = purple_media_manager_create_media(purple_media_manager_get(), js->gc, + "fsrtpconference", session->remote_jid, FALSE); + + purple_media_set_prpl_data(session->media, session); + + params = jabber_google_session_get_params(js, &num_params); + + if (purple_media_add_stream(session->media, "google-voice", session->remote_jid, + PURPLE_MEDIA_AUDIO, "nice", num_params, params) == FALSE) { + purple_media_error(session->media, "Error adding stream."); + purple_media_hangup(session->media); + google_session_send_terminate(session); + g_free(params); + return; + } + + g_free(params); + + desc_element = xmlnode_get_child(sess, "description"); + + for (codec_element = xmlnode_get_child(desc_element, "payload-type"); + codec_element; + codec_element = xmlnode_get_next_twin(codec_element)) { + encoding_name = xmlnode_get_attrib(codec_element, "name"); + id = xmlnode_get_attrib(codec_element, "id"); + clock_rate = xmlnode_get_attrib(codec_element, "clockrate"); + + codec = purple_media_codec_new(atoi(id), encoding_name, PURPLE_MEDIA_AUDIO, + clock_rate ? atoi(clock_rate) : 0); + codecs = g_list_append(codecs, codec); + } + + purple_media_set_remote_codecs(session->media, "google-voice", session->remote_jid, codecs); + + g_signal_connect_swapped(G_OBJECT(session->media), "accepted", + G_CALLBACK(google_session_ready), session); + g_signal_connect_swapped(G_OBJECT(session->media), + "candidates-prepared", + G_CALLBACK(google_session_ready), session); + g_signal_connect_swapped(G_OBJECT(session->media), "codecs-changed", + G_CALLBACK(google_session_ready), session); + g_signal_connect(G_OBJECT(session->media), "state-changed", + G_CALLBACK(google_session_state_changed_cb), session); + g_signal_connect(G_OBJECT(session->media), "stream-info", + G_CALLBACK(google_session_stream_info_cb), session); + + purple_media_codec_list_free(codecs); + + result = jabber_iq_new(js, JABBER_IQ_RESULT); + jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "to", session->remote_jid); + jabber_iq_send(result); +} + +static void +google_session_handle_candidates(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) +{ + JabberIq *result; + GList *list = NULL; + xmlnode *cand; + static int name = 0; + char n[4]; + + for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) { + PurpleMediaCandidate *info; + g_snprintf(n, sizeof(n), "S%d", name++); + info = purple_media_candidate_new(n, PURPLE_MEDIA_COMPONENT_RTP, + !strcmp(xmlnode_get_attrib(cand, "type"), "local") ? + PURPLE_MEDIA_CANDIDATE_TYPE_HOST : + !strcmp(xmlnode_get_attrib(cand, "type"), "stun") ? + PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX : + !strcmp(xmlnode_get_attrib(cand, "type"), "relay") ? + PURPLE_MEDIA_CANDIDATE_TYPE_RELAY : + PURPLE_MEDIA_CANDIDATE_TYPE_HOST, + !strcmp(xmlnode_get_attrib(cand, "protocol"),"udp") ? + PURPLE_MEDIA_NETWORK_PROTOCOL_UDP : + PURPLE_MEDIA_NETWORK_PROTOCOL_TCP, + xmlnode_get_attrib(cand, "address"), + atoi(xmlnode_get_attrib(cand, "port"))); + + info->username = g_strdup(xmlnode_get_attrib(cand, "username")); + info->password = g_strdup(xmlnode_get_attrib(cand, "password")); + + list = g_list_append(list, info); + } + + purple_media_add_remote_candidates(session->media, "google-voice", session->remote_jid, list); + purple_media_candidate_list_free(list); + + result = jabber_iq_new(js, JABBER_IQ_RESULT); + jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "to", session->remote_jid); + jabber_iq_send(result); +} + +static void +google_session_handle_accept(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) +{ + xmlnode *desc_element = xmlnode_get_child(sess, "description"); + xmlnode *codec_element = xmlnode_get_child(desc_element, "payload-type"); + GList *codecs = NULL; + + for (; codec_element; codec_element = + xmlnode_get_next_twin(codec_element)) { + const gchar *encoding_name = + xmlnode_get_attrib(codec_element, "name"); + const gchar *id = xmlnode_get_attrib(codec_element, "id"); + const gchar *clock_rate = + xmlnode_get_attrib(codec_element, "clockrate"); + + PurpleMediaCodec *codec = purple_media_codec_new(atoi(id), + encoding_name, PURPLE_MEDIA_AUDIO, + clock_rate ? atoi(clock_rate) : 0); + codecs = g_list_append(codecs, codec); + } + + purple_media_set_remote_codecs(session->media, "google-voice", + session->remote_jid, codecs); + + purple_media_accept(session->media); +} + +static void +google_session_handle_reject(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) +{ + purple_media_end(session->media, NULL, NULL); +} + +static void +google_session_handle_terminate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) +{ + purple_media_end(session->media, NULL, NULL); +} + +static void +google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *packet) +{ + xmlnode *sess = xmlnode_get_child(packet, "session"); + const char *type = xmlnode_get_attrib(sess, "type"); + + if (!strcmp(type, "initiate")) { + google_session_handle_initiate(js, session, packet, sess); + } else if (!strcmp(type, "accept")) { + google_session_handle_accept(js, session, packet, sess); + } else if (!strcmp(type, "reject")) { + google_session_handle_reject(js, session, packet, sess); + } else if (!strcmp(type, "terminate")) { + google_session_handle_terminate(js, session, packet, sess); + } else if (!strcmp(type, "candidates")) { + google_session_handle_candidates(js, session, packet, sess); + } +} + +void +jabber_google_session_parse(JabberStream *js, xmlnode *packet) +{ + GoogleSession *session = NULL; + GoogleSessionId id; + + xmlnode *session_node; + xmlnode *desc_node; + + GList *iter = NULL; + + 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; + + iter = purple_media_manager_get_media_by_connection( + purple_media_manager_get(), js->gc); + for (; iter; iter = g_list_delete_link(iter, iter)) { + GoogleSession *gsession = + purple_media_get_prpl_data(iter->data); + if (google_session_id_equal(&(gsession->id), &id)) { + session = gsession; + break; + } + } + if (iter != NULL) { + g_list_free(iter); + } + + 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); + + google_session_parse_iq(js, session, packet); +} +#endif /* USE_VV */ + static void jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul) { @@ -529,3 +1073,105 @@ const char *attr = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE); return attr ? g_strdup_printf("♫ %s", attr) : g_strdup(""); } + +static void +jabber_google_stun_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) +{ + JabberStream *js = (JabberStream *) data; + + if (error_message) { + purple_debug_error("jabber", "Google STUN lookup failed: %s\n", + error_message); + g_slist_free(hosts); + return; + } + + if (hosts && g_slist_next(hosts)) { + struct sockaddr *addr = g_slist_next(hosts)->data; + char dst[INET6_ADDRSTRLEN]; + int port; + + if (addr->sa_family == AF_INET6) { + inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, + dst, sizeof(dst)); + port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port); + } else { + inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, + dst, sizeof(dst)); + port = ntohs(((struct sockaddr_in *) addr)->sin_port); + } + + if (js) { + if (js->stun_ip) { + g_free(js->stun_ip); + } + js->stun_ip = g_strdup(dst); + purple_debug_info("jabber", "set Google STUN IP address: %s\n", dst); + js->stun_port = port; + purple_debug_info("jabber", "set Google STUN port: %d\n", port); + purple_debug_info("jabber", "set Google STUN port: %d\n", port); + /* unmark ongoing query */ + js->stun_query = NULL; + } + } + + g_slist_free(hosts); +} + +static void +jabber_google_jingle_info_cb(JabberStream *js, xmlnode *result, + gpointer nullus) +{ + if (result) { + const xmlnode *query = + xmlnode_get_child_with_namespace(result, "query", + GOOGLE_JINGLE_INFO_NAMESPACE); + + if (query) { + const xmlnode *stun = xmlnode_get_child(query, "stun"); + + purple_debug_info("jabber", "got google:jingleinfo\n"); + + if (stun) { + xmlnode *server = xmlnode_get_child(stun, "server"); + + if (server) { + const gchar *host = xmlnode_get_attrib(server, "host"); + const gchar *udp = xmlnode_get_attrib(server, "udp"); + + if (host && udp) { + int port = atoi(udp); + /* if there, would already be an ongoing query, + cancel it */ + if (js->stun_query) + purple_dnsquery_destroy(js->stun_query); + + js->stun_query = purple_dnsquery_a(host, port, + jabber_google_stun_lookup_cb, js); + } + } + } + /* should perhaps handle relays later on, or maybe wait until + Google supports a common standard... */ + } + } +} + +void +jabber_google_handle_jingle_info(JabberStream *js, xmlnode *packet) +{ + jabber_google_jingle_info_cb(js, packet, NULL); +} + +void +jabber_google_send_jingle_info(JabberStream *js) +{ + JabberIq *jingle_info = + jabber_iq_new_query(js, JABBER_IQ_GET, GOOGLE_JINGLE_INFO_NAMESPACE); + + jabber_iq_set_callback(jingle_info, jabber_google_jingle_info_cb, + NULL); + purple_debug_info("jabber", "sending google:jingleinfo query\n"); + jabber_iq_send(jingle_info); +} diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/google.h --- a/libpurple/protocols/jabber/google.h Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/google.h Sun Mar 22 23:33:42 2009 +0000 @@ -25,6 +25,10 @@ * such that they don't intermingle with code for the XMPP RFCs and XEPs :) */ #include "jabber.h" +#include "media.h" + +#define GOOGLE_VOICE_CAP "http://www.google.com/xmpp/protocol/voice/v1" +#define GOOGLE_JINGLE_INFO_NAMESPACE "google:jingleinfo" void jabber_gmail_init(JabberStream *js); void jabber_gmail_poke(JabberStream *js, xmlnode *node); @@ -45,6 +49,10 @@ char *jabber_google_format_to_html(const char *text); +PurpleMedia *jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type); +void jabber_google_session_parse(JabberStream *js, xmlnode *node); +void jabber_google_handle_jingle_info(JabberStream *js, xmlnode *packet); +void jabber_google_send_jingle_info(JabberStream *js); #endif /* _PURPLE_GOOGLE_H_ */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/iq.c --- a/libpurple/protocols/jabber/iq.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/iq.c Sun Mar 22 23:33:42 2009 +0000 @@ -28,6 +28,7 @@ #include "disco.h" #include "google.h" #include "iq.h" +#include "jingle/jingle.h" #include "oob.h" #include "roster.h" #include "si.h" @@ -372,6 +373,13 @@ } } +#ifdef USE_VV + if (xmlnode_get_child_with_namespace(packet, "session", "http://www.google.com/session")) { + jabber_google_session_parse(js, packet); + return; + } +#endif + if(xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si")) { jabber_si_parse(js, packet); return; @@ -401,6 +409,11 @@ return; } + if (xmlnode_get_child_with_namespace(packet, "jingle", JINGLE)) { + jingle_parse(js, packet); + return; + } + /* If we get here, send the default error reply mandated by XMPP-CORE */ if(!strcmp(type, "set") || !strcmp(type, "get")) { JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR); @@ -440,6 +453,11 @@ 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); + jabber_iq_register_handler(JINGLE, jingle_parse); + + /* handle Google jingleinfo */ + jabber_iq_register_handler(GOOGLE_JINGLE_INFO_NAMESPACE, + jabber_google_handle_jingle_info); } void jabber_iq_uninit(void) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sun Mar 22 23:33:42 2009 +0000 @@ -59,6 +59,8 @@ #include "pep.h" #include "adhoccommands.h" +#include "jingle/jingle.h" +#include "jingle/rtp.h" #define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5) @@ -729,6 +731,10 @@ js->old_length = 0; js->keepalive_timeout = -1; js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user ? js->user->domain : NULL); + js->sessions = NULL; + js->stun_ip = NULL; + js->stun_port = 0; + js->stun_query = NULL; if(!js->user) { purple_connection_error_reason (gc, @@ -1223,6 +1229,10 @@ server = connect_server[0] ? connect_server : js->user->domain; js->certificate_CN = g_strdup(server); + js->stun_ip = NULL; + js->stun_port = 0; + js->stun_query = NULL; + jabber_stream_set_state(js, JABBER_STREAM_CONNECTING); if(purple_account_get_bool(account, "old_ssl", FALSE)) { @@ -1320,6 +1330,9 @@ { JabberStream *js = gc->proto_data; + /* Close all of the open Jingle sessions on this stream */ + jingle_terminate_sessions(js); + /* Don't perform any actions on the ssl connection * if we were forcibly disconnected because it will crash * on some SSL backends. @@ -1421,6 +1434,15 @@ g_free(js->srv_rec); js->srv_rec = NULL; + g_free(js->stun_ip); + js->stun_ip = NULL; + + /* cancel DNS query for STUN, if one is ongoing */ + if (js->stun_query) { + purple_dnsquery_destroy(js->stun_query); + js->stun_query = NULL; + } + g_free(js); gc->proto_data = NULL; @@ -2590,6 +2612,87 @@ return TRUE; } +PurpleMedia * +jabber_initiate_media(PurpleConnection *gc, const char *who, + PurpleMediaSessionType type) +{ +#ifdef USE_VV + JabberStream *js = (JabberStream *) gc->proto_data; + JabberBuddy *jb; + + if (!js) { + purple_debug_error("jabber", + "jabber_initiate_media: NULL stream\n"); + return NULL; + } + + jb = jabber_buddy_find(js, who, FALSE); + + if (!jb) { + purple_debug_error("jabber", "Could not find buddy\n"); + return NULL; + } + + if (type & PURPLE_MEDIA_AUDIO && + !jabber_buddy_has_capability(jb, + JINGLE_APP_RTP_SUPPORT_AUDIO) && + jabber_buddy_has_capability(jb, GOOGLE_VOICE_CAP)) + return jabber_google_session_initiate(gc->proto_data, who, type); + else + return jingle_rtp_initiate_media(gc->proto_data, who, type); +#else + return NULL; +#endif +} + +PurpleMediaCaps jabber_get_media_caps(PurpleConnection *gc, const char *who) +{ +#ifdef USE_VV + JabberStream *js = (JabberStream *) gc->proto_data; + JabberBuddy *jb; + PurpleMediaCaps caps = PURPLE_MEDIA_CAPS_NONE; + + if (!js) { + purple_debug_error("jabber", "jabber_can_do_media: NULL stream\n"); + return FALSE; + } + + jb = jabber_buddy_find(js, who, FALSE); + + if (!jb) { + purple_debug_error("jabber", "Could not find buddy\n"); + return FALSE; + } + + if (jabber_buddy_has_capability(jb, JINGLE_APP_RTP_SUPPORT_AUDIO)) + caps |= PURPLE_MEDIA_CAPS_AUDIO | + PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION; + if (jabber_buddy_has_capability(jb, JINGLE_APP_RTP_SUPPORT_VIDEO)) + caps |= PURPLE_MEDIA_CAPS_VIDEO | + PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION; + if (caps & PURPLE_MEDIA_CAPS_AUDIO && caps & PURPLE_MEDIA_CAPS_VIDEO) + caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO; + if (caps != PURPLE_MEDIA_CAPS_NONE) { + if (!jabber_buddy_has_capability(jb, + JINGLE_TRANSPORT_ICEUDP) && + !jabber_buddy_has_capability(jb, + JINGLE_TRANSPORT_RAWUDP)) { + purple_debug_info("jingle-rtp", "Buddy doesn't " + "support the same transport types\n"); + caps = PURPLE_MEDIA_CAPS_NONE; + } else + caps |= PURPLE_MEDIA_CAPS_MODIFY_SESSION | + PURPLE_MEDIA_CAPS_CHANGE_DIRECTION; + } + if (jabber_buddy_has_capability(jb, GOOGLE_VOICE_CAP)) + caps |= PURPLE_MEDIA_CAPS_AUDIO; + + return caps; +#else + return PURPLE_MEDIA_CAPS_NONE; +#endif +} + void jabber_register_commands(void) { purple_cmd_register("config", "", PURPLE_CMD_P_PRPL, diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Sun Mar 22 23:33:42 2009 +0000 @@ -54,8 +54,11 @@ #include "circbuffer.h" #include "connection.h" #include "dnssrv.h" +#include "media.h" +#include "mediamanager.h" #include "roomlist.h" #include "sslconn.h" +#include "dnsquery.h" #include "jutil.h" #include "xmlnode.h" @@ -242,6 +245,15 @@ * for when we lookup buddy icons from a url */ GSList *url_datas; + + /* keep a hash table of JingleSessions */ + GHashTable *sessions; + + /* maybe this should only be present when USE_VV? */ + gchar *stun_ip; + int stun_port; + PurpleDnsQueryData *stun_query; + /* later add stuff to handle TURN relays... */ }; typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace); @@ -309,7 +321,9 @@ gboolean jabber_offline_message(const PurpleBuddy *buddy); int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len); GList *jabber_actions(PurplePlugin *plugin, gpointer context); +PurpleMedia *jabber_initiate_media(PurpleConnection *gc, const char *who, + PurpleMediaSessionType type); +PurpleMediaCaps jabber_get_media_caps(PurpleConnection *gc, const char *who); void jabber_register_commands(void); void jabber_init_plugin(PurplePlugin *plugin); - #endif /* _PURPLE_JABBER_H_ */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/content.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/content.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,455 @@ +/** + * @file content.c + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" + +#include "debug.h" +#include "content.h" +#include "jingle.h" + +#include + +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); + 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); + 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); + g_object_unref(transport); + } + + return node; +} + +xmlnode * +jingle_content_to_xml(JingleContent *content, xmlnode *jingle, JingleActionType action) +{ + g_return_val_if_fail(JINGLE_IS_CONTENT(content), NULL); + return JINGLE_CONTENT_GET_CLASS(content)->to_xml(content, jingle, action); +} + +void +jingle_content_handle_action(JingleContent *content, xmlnode *xmlcontent, JingleActionType action) +{ + g_return_if_fail(JINGLE_IS_CONTENT(content)); + JINGLE_CONTENT_GET_CLASS(content)->handle_action(content, xmlcontent, action); +} + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/content.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/content.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,117 @@ +/** + * @file content.h + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef JINGLE_CONTENT_H +#define JINGLE_CONTENT_H + + +#include "jabber.h" +#include "jingle.h" +#include "session.h" +#include "transport.h" + +#include +#include + +G_BEGIN_DECLS + +#define JINGLE_TYPE_CONTENT (jingle_content_get_type()) +#define JINGLE_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_CONTENT, JingleContent)) +#define JINGLE_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_CONTENT, JingleContentClass)) +#define JINGLE_IS_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_CONTENT)) +#define JINGLE_IS_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_CONTENT)) +#define JINGLE_CONTENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_CONTENT, JingleContentClass)) + +/** @copydoc _JingleContent */ +typedef struct _JingleContent JingleContent; +/** @copydoc _JingleContentClass */ +typedef struct _JingleContentClass JingleContentClass; +/** @copydoc _JingleContentPrivate */ +typedef struct _JingleContentPrivate JingleContentPrivate; + +/** The content class */ +struct _JingleContentClass +{ + GObjectClass parent_class; /**< The parent class. */ + + xmlnode *(*to_xml) (JingleContent *content, xmlnode *jingle, JingleActionType action); + JingleContent *(*parse) (xmlnode *content); + void (*handle_action) (JingleContent *content, xmlnode *xmlcontent, JingleActionType action); + const gchar *description_type; +}; + +/** The content class's private data */ +struct _JingleContent +{ + GObject parent; /**< The parent of this object. */ + JingleContentPrivate *priv; /**< The private data of this object. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets the content class's GType + * + * @return The content class's GType. + */ +GType jingle_content_get_type(void); + +JingleContent *jingle_content_create(const gchar *type, const gchar *creator, + const gchar *disposition, const gchar *name, + const gchar *senders, JingleTransport *transport); + +JingleSession *jingle_content_get_session(JingleContent *content); +const gchar *jingle_content_get_description_type(JingleContent *content); +gchar *jingle_content_get_creator(JingleContent *content); +gchar *jingle_content_get_disposition(JingleContent *content); +gchar *jingle_content_get_name(JingleContent *content); +gchar *jingle_content_get_senders(JingleContent *content); +JingleTransport *jingle_content_get_transport(JingleContent *content); +JingleTransport *jingle_content_get_pending_transport(JingleContent *content); + +void jingle_content_set_session(JingleContent *content, JingleSession *session); +void jingle_content_set_pending_transport(JingleContent *content, JingleTransport *transport); +void jingle_content_accept_transport(JingleContent *content); +void jingle_content_remove_pending_transport(JingleContent *content); +void jingle_content_modify(JingleContent *content, const gchar *senders); + +#define jingle_content_create_content_accept(session) \ + jingle_session_to_packet(session, JINGLE_CONTENT_ACCEPT) +#define jingle_content_create_content_add(session) \ + jingle_session_to_packet(session, JINGLE_CONTENT_ADD) +#define jingle_content_create_content_modify(session) \ + jingle_session_to_packet(session, JINGLE_CONTENT_MODIFY) +#define jingle_content_create_content_remove(session) \ + jingle_session_to_packet(session, JINGLE_CONTENT_REMOVE) + +JingleContent *jingle_content_parse(xmlnode *content); +xmlnode *jingle_content_to_xml(JingleContent *content, xmlnode *jingle, JingleActionType action); +void jingle_content_handle_action(JingleContent *content, xmlnode *xmlcontent, JingleActionType action); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* JINGLE_CONTENT_H */ + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/iceudp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/iceudp.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,413 @@ +/** + * @file iceudp.c + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" + +#include "iceudp.h" +#include "jingle.h" +#include "debug.h" + +#include + +struct _JingleIceUdpPrivate +{ + GList *local_candidates; + GList *remote_candidates; +}; + +#define JINGLE_ICEUDP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_ICEUDP, JingleIceUdpPrivate)) + +static void jingle_iceudp_class_init (JingleIceUdpClass *klass); +static void jingle_iceudp_init (JingleIceUdp *iceudp); +static void jingle_iceudp_finalize (GObject *object); +static void jingle_iceudp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void jingle_iceudp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static JingleTransport *jingle_iceudp_parse_internal(xmlnode *iceudp); +static xmlnode *jingle_iceudp_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action); + +static JingleTransportClass *parent_class = NULL; + +enum { + PROP_0, + PROP_LOCAL_CANDIDATES, + PROP_REMOTE_CANDIDATES, +}; + +static JingleIceUdpCandidate * +jingle_iceudp_candidate_copy(JingleIceUdpCandidate *candidate) +{ + JingleIceUdpCandidate *new_candidate = g_new0(JingleIceUdpCandidate, 1); + new_candidate->component = candidate->component; + new_candidate->foundation = g_strdup(candidate->foundation); + new_candidate->generation = candidate->generation; + new_candidate->id = g_strdup(candidate->id); + new_candidate->ip = g_strdup(candidate->ip); + new_candidate->network = candidate->network; + new_candidate->port = candidate->port; + new_candidate->priority = candidate->priority; + new_candidate->protocol = g_strdup(candidate->protocol); + new_candidate->type = g_strdup(candidate->type); + + new_candidate->username = g_strdup(candidate->username); + new_candidate->password = g_strdup(candidate->password); + + new_candidate->rem_known = candidate->rem_known; + + return new_candidate; +} + +static void +jingle_iceudp_candidate_free(JingleIceUdpCandidate *candidate) +{ + g_free(candidate->foundation); + g_free(candidate->id); + g_free(candidate->ip); + g_free(candidate->protocol); + g_free(candidate->reladdr); + g_free(candidate->type); + + g_free(candidate->username); + g_free(candidate->password); +} + +GType +jingle_iceudp_candidate_get_type() +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static("JingleIceUdpCandidate", + (GBoxedCopyFunc)jingle_iceudp_candidate_copy, + (GBoxedFreeFunc)jingle_iceudp_candidate_free); + } + return type; +} + +JingleIceUdpCandidate * +jingle_iceudp_candidate_new(guint component, const gchar *foundation, + guint generation, const gchar *id, const gchar *ip, + guint network, guint port, guint priority, + const gchar *protocol, const gchar *type, + const gchar *username, const gchar *password) +{ + JingleIceUdpCandidate *candidate = g_new0(JingleIceUdpCandidate, 1); + candidate->component = component; + candidate->foundation = g_strdup(foundation); + candidate->generation = generation; + candidate->id = g_strdup(id); + candidate->ip = g_strdup(ip); + candidate->network = network; + candidate->port = port; + candidate->priority = priority; + candidate->protocol = g_strdup(protocol); + candidate->type = g_strdup(type); + + candidate->username = g_strdup(username); + candidate->password = g_strdup(password); + + candidate->rem_known = FALSE; + return candidate; +} + +GType +jingle_iceudp_get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(JingleIceUdpClass), + NULL, + NULL, + (GClassInitFunc) jingle_iceudp_class_init, + NULL, + NULL, + sizeof(JingleIceUdp), + 0, + (GInstanceInitFunc) jingle_iceudp_init, + NULL + }; + type = g_type_register_static(JINGLE_TYPE_TRANSPORT, "JingleIceUdp", &info, 0); + } + return type; +} + +static void +jingle_iceudp_class_init (JingleIceUdpClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = jingle_iceudp_finalize; + gobject_class->set_property = jingle_iceudp_set_property; + gobject_class->get_property = jingle_iceudp_get_property; + klass->parent_class.to_xml = jingle_iceudp_to_xml_internal; + klass->parent_class.parse = jingle_iceudp_parse_internal; + klass->parent_class.transport_type = JINGLE_TRANSPORT_ICEUDP; + + g_object_class_install_property(gobject_class, PROP_LOCAL_CANDIDATES, + g_param_spec_pointer("local-candidates", + "Local candidates", + "The local candidates for this transport.", + G_PARAM_READABLE)); + + g_object_class_install_property(gobject_class, PROP_REMOTE_CANDIDATES, + g_param_spec_pointer("remote-candidates", + "Remote candidates", + "The remote candidates for this transport.", + G_PARAM_READABLE)); + + g_type_class_add_private(klass, sizeof(JingleIceUdpPrivate)); +} + +static void +jingle_iceudp_init (JingleIceUdp *iceudp) +{ + iceudp->priv = JINGLE_ICEUDP_GET_PRIVATE(iceudp); + iceudp->priv->local_candidates = NULL; + iceudp->priv->remote_candidates = NULL; +} + +static void +jingle_iceudp_finalize (GObject *iceudp) +{ +/* JingleIceUdpPrivate *priv = JINGLE_ICEUDP_GET_PRIVATE(iceudp); */ + purple_debug_info("jingle","jingle_iceudp_finalize\n"); + + G_OBJECT_CLASS(parent_class)->finalize(iceudp); +} + +static void +jingle_iceudp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + JingleIceUdp *iceudp; + g_return_if_fail(JINGLE_IS_ICEUDP(object)); + + iceudp = JINGLE_ICEUDP(object); + + switch (prop_id) { + case PROP_LOCAL_CANDIDATES: + iceudp->priv->local_candidates = + g_value_get_pointer(value); + break; + case PROP_REMOTE_CANDIDATES: + iceudp->priv->remote_candidates = + g_value_get_pointer(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +jingle_iceudp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + JingleIceUdp *iceudp; + g_return_if_fail(JINGLE_IS_ICEUDP(object)); + + iceudp = JINGLE_ICEUDP(object); + + switch (prop_id) { + case PROP_LOCAL_CANDIDATES: + g_value_set_pointer(value, iceudp->priv->local_candidates); + break; + case PROP_REMOTE_CANDIDATES: + g_value_set_pointer(value, iceudp->priv->remote_candidates); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +void +jingle_iceudp_add_local_candidate(JingleIceUdp *iceudp, JingleIceUdpCandidate *candidate) +{ + GList *iter = iceudp->priv->local_candidates; + + for (; iter; iter = g_list_next(iter)) { + JingleIceUdpCandidate *c = iter->data; + if (!strcmp(c->id, candidate->id)) { + guint generation = c->generation + 1; + + g_boxed_free(JINGLE_TYPE_ICEUDP_CANDIDATE, c); + iceudp->priv->local_candidates = g_list_delete_link( + iceudp->priv->local_candidates, iter); + + candidate->generation = generation; + + iceudp->priv->local_candidates = g_list_append( + iceudp->priv->local_candidates, candidate); + return; + } + } + + iceudp->priv->local_candidates = g_list_append( + iceudp->priv->local_candidates, candidate); +} + +GList * +jingle_iceudp_get_remote_candidates(JingleIceUdp *iceudp) +{ + return g_list_copy(iceudp->priv->remote_candidates); +} + +static JingleIceUdpCandidate * +jingle_iceudp_get_remote_candidate_by_id(JingleIceUdp *iceudp, + const gchar *id) +{ + GList *iter = iceudp->priv->remote_candidates; + for (; iter; iter = g_list_next(iter)) { + JingleIceUdpCandidate *candidate = iter->data; + if (!strcmp(candidate->id, id)) { + return candidate; + } + } + return NULL; +} + +static void +jingle_iceudp_add_remote_candidate(JingleIceUdp *iceudp, JingleIceUdpCandidate *candidate) +{ + JingleIceUdpPrivate *priv = JINGLE_ICEUDP_GET_PRIVATE(iceudp); + JingleIceUdpCandidate *iceudp_candidate = + jingle_iceudp_get_remote_candidate_by_id(iceudp, + candidate->id); + if (iceudp_candidate != NULL) { + priv->remote_candidates = g_list_remove( + priv->remote_candidates, iceudp_candidate); + g_boxed_free(JINGLE_TYPE_ICEUDP_CANDIDATE, iceudp_candidate); + } + priv->remote_candidates = g_list_append(priv->remote_candidates, candidate); +} + +static JingleTransport * +jingle_iceudp_parse_internal(xmlnode *iceudp) +{ + JingleTransport *transport = parent_class->parse(iceudp); + xmlnode *candidate = xmlnode_get_child(iceudp, "candidate"); + JingleIceUdpCandidate *iceudp_candidate = NULL; + + const gchar *username = xmlnode_get_attrib(iceudp, "ufrag"); + const gchar *password = xmlnode_get_attrib(iceudp, "pwd"); + + for (; candidate; candidate = xmlnode_get_next_twin(candidate)) { + const gchar *relport = + xmlnode_get_attrib(candidate, "rel-port"); + iceudp_candidate = jingle_iceudp_candidate_new( + atoi(xmlnode_get_attrib(candidate, "component")), + xmlnode_get_attrib(candidate, "foundation"), + atoi(xmlnode_get_attrib(candidate, "generation")), + xmlnode_get_attrib(candidate, "id"), + xmlnode_get_attrib(candidate, "ip"), + atoi(xmlnode_get_attrib(candidate, "network")), + atoi(xmlnode_get_attrib(candidate, "port")), + atoi(xmlnode_get_attrib(candidate, "priority")), + xmlnode_get_attrib(candidate, "protocol"), + xmlnode_get_attrib(candidate, "type"), + username, password); + iceudp_candidate->reladdr = g_strdup( + xmlnode_get_attrib(candidate, "rel-addr")); + iceudp_candidate->relport = + relport != NULL ? atoi(relport) : 0; + iceudp_candidate->rem_known = TRUE; + jingle_iceudp_add_remote_candidate(JINGLE_ICEUDP(transport), iceudp_candidate); + } + + return transport; +} + +static xmlnode * +jingle_iceudp_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action) +{ + xmlnode *node = parent_class->to_xml(transport, content, action); + + if (action == JINGLE_SESSION_INITIATE || + action == JINGLE_SESSION_ACCEPT || + action == JINGLE_TRANSPORT_INFO || + action == JINGLE_CONTENT_ADD || + action == JINGLE_TRANSPORT_REPLACE) { + JingleIceUdpPrivate *priv = JINGLE_ICEUDP_GET_PRIVATE(transport); + GList *iter = priv->local_candidates; + gboolean used_candidate = FALSE; + + for (; iter; iter = g_list_next(iter)) { + JingleIceUdpCandidate *candidate = iter->data; + xmlnode *xmltransport; + gchar *component, *generation, *network, + *port, *priority; + + if (candidate->rem_known == TRUE) + continue; + + used_candidate = TRUE; + candidate->rem_known = TRUE; + + xmltransport = xmlnode_new_child(node, "candidate"); + component = g_strdup_printf("%d", candidate->component); + generation = g_strdup_printf("%d", + candidate->generation); + network = g_strdup_printf("%d", candidate->network); + port = g_strdup_printf("%d", candidate->port); + priority = g_strdup_printf("%d", candidate->priority); + + xmlnode_set_attrib(xmltransport, "component", component); + xmlnode_set_attrib(xmltransport, "foundation", candidate->foundation); + xmlnode_set_attrib(xmltransport, "generation", generation); + xmlnode_set_attrib(xmltransport, "id", candidate->id); + xmlnode_set_attrib(xmltransport, "ip", candidate->ip); + xmlnode_set_attrib(xmltransport, "network", network); + xmlnode_set_attrib(xmltransport, "port", port); + xmlnode_set_attrib(xmltransport, "priority", priority); + xmlnode_set_attrib(xmltransport, "protocol", candidate->protocol); + + if (candidate->reladdr != NULL && + (strcmp(candidate->ip, candidate->reladdr) || + (candidate->port != candidate->relport))) { + gchar *relport = g_strdup_printf("%d", + candidate->relport); + xmlnode_set_attrib(xmltransport, "rel-addr", + candidate->reladdr); + xmlnode_set_attrib(xmltransport, "rel-port", + relport); + g_free(relport); + } + + xmlnode_set_attrib(xmltransport, "type", candidate->type); + + g_free(component); + g_free(generation); + g_free(network); + g_free(port); + g_free(priority); + } + + if (used_candidate == TRUE) { + JingleIceUdpCandidate *candidate = + priv->local_candidates->data; + xmlnode_set_attrib(node, "pwd", candidate->password); + xmlnode_set_attrib(node, "ufrag", candidate->username); + } + } + + return node; +} + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/iceudp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/iceudp.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,114 @@ +/** + * @file iceudp.h + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef JINGLE_ICEUDP_H +#define JINGLE_ICEUDP_H + +#include +#include + +#include "transport.h" + +G_BEGIN_DECLS + +#define JINGLE_TYPE_ICEUDP (jingle_iceudp_get_type()) +#define JINGLE_TYPE_ICEUDP_CANDIDATE (jingle_iceudp_candidate_get_type()) +#define JINGLE_ICEUDP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_ICEUDP, JingleIceUdp)) +#define JINGLE_ICEUDP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_ICEUDP, JingleIceUdpClass)) +#define JINGLE_IS_ICEUDP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_ICEUDP)) +#define JINGLE_IS_ICEUDP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_ICEUDP)) +#define JINGLE_ICEUDP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_ICEUDP, JingleIceUdpClass)) + +/** @copydoc _JingleIceUdp */ +typedef struct _JingleIceUdp JingleIceUdp; +/** @copydoc _JingleIceUdpClass */ +typedef struct _JingleIceUdpClass JingleIceUdpClass; +/** @copydoc _JingleIceUdpPrivate */ +typedef struct _JingleIceUdpPrivate JingleIceUdpPrivate; +/** @copydoc _JingleIceUdpCandidate */ +typedef struct _JingleIceUdpCandidate JingleIceUdpCandidate; + +/** The iceudp class */ +struct _JingleIceUdpClass +{ + JingleTransportClass parent_class; /**< The parent class. */ + + xmlnode *(*to_xml) (JingleTransport *transport, xmlnode *content, JingleActionType action); + JingleTransport *(*parse) (xmlnode *transport); +}; + +/** The iceudp class's private data */ +struct _JingleIceUdp +{ + JingleTransport parent; /**< The parent of this object. */ + JingleIceUdpPrivate *priv; /**< The private data of this object. */ +}; + +struct _JingleIceUdpCandidate +{ + guint component; + gchar *foundation; + guint generation; + gchar *id; + gchar *ip; + guint network; + guint port; + guint priority; + gchar *protocol; + gchar *reladdr; + guint relport; + gchar *type; + + gchar *username; + gchar *password; + + gboolean rem_known; /* TRUE if the remote side knows + * about this candidate */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +GType jingle_iceudp_candidate_get_type(void); + +/** + * Gets the iceudp class's GType + * + * @return The iceudp class's GType. + */ +GType jingle_iceudp_get_type(void); + +JingleIceUdpCandidate *jingle_iceudp_candidate_new(guint component, + const gchar *foundation, guint generation, const gchar *id, + const gchar *ip, guint network, guint port, guint priority, + const gchar *protocol, const gchar *type, + const gchar *username, const gchar *password); +void jingle_iceudp_add_local_candidate(JingleIceUdp *iceudp, JingleIceUdpCandidate *candidate); +GList *jingle_iceudp_get_remote_candidates(JingleIceUdp *iceudp); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* JINGLE_ICEUDP_H */ + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/jingle.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/jingle.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,463 @@ +/* + * @file jingle.c + * + * purple - Jabber Protocol Plugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ + +#include "internal.h" +#include "network.h" + +#include "content.h" +#include "debug.h" +#include "jingle.h" +#include +#include "session.h" +#include "iceudp.h" +#include "rawudp.h" +#include "rtp.h" + +GType +jingle_get_type(const gchar *type) +{ + if (!strcmp(type, JINGLE_TRANSPORT_RAWUDP)) + return JINGLE_TYPE_RAWUDP; + else if (!strcmp(type, JINGLE_TRANSPORT_ICEUDP)) + return JINGLE_TYPE_ICEUDP; +#if 0 + else if (!strcmp(type, JINGLE_TRANSPORT_SOCKS)) + return JINGLE_TYPE_SOCKS; + else if (!strcmp(type, JINGLE_TRANSPORT_IBB)) + return JINGLE_TYPE_IBB; +#endif +#ifdef USE_VV + else if (!strcmp(type, JINGLE_APP_RTP)) + return JINGLE_TYPE_RTP; +#endif +#if 0 + else if (!strcmp(type, JINGLE_APP_FT)) + return JINGLE_TYPE_FT; + else if (!strcmp(type, JINGLE_APP_XML)) + return JINGLE_TYPE_XML; +#endif + else + return G_TYPE_NONE; +} + +static void +jingle_handle_unknown_type(JingleSession *session, xmlnode *jingle) +{ + /* Send error */ +} + +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_description_info(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = xmlnode_get_child(jingle, "content"); + + jabber_iq_send(jingle_session_create_ack(session, jingle)); + + jingle_session_accept_session(session); + + for (; content; content = xmlnode_get_next_twin(content)) { + const gchar *name = xmlnode_get_attrib(content, "name"); + const gchar *creator = xmlnode_get_attrib(content, "creator"); + JingleContent *parsed_content = + jingle_session_find_content(session, name, creator); + if (parsed_content == NULL) { + purple_debug_error("jingle", "Error parsing content\n"); + /* XXX: send error */ + } else { + jingle_content_handle_action(parsed_content, content, + JINGLE_DESCRIPTION_INFO); + } + } +} + +static void +jingle_handle_security_info(JingleSession *session, xmlnode *jingle) +{ + jabber_iq_send(jingle_session_create_ack(session, jingle)); +} + +static void +jingle_handle_session_accept(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = xmlnode_get_child(jingle, "content"); + + jabber_iq_send(jingle_session_create_ack(session, jingle)); + + jingle_session_accept_session(session); + + for (; content; content = xmlnode_get_next_twin(content)) { + const gchar *name = xmlnode_get_attrib(content, "name"); + const gchar *creator = xmlnode_get_attrib(content, "creator"); + JingleContent *parsed_content = + jingle_session_find_content(session, name, creator); + if (parsed_content == NULL) { + purple_debug_error("jingle", "Error parsing content\n"); + /* XXX: send error */ + } else { + jingle_content_handle_action(parsed_content, content, + JINGLE_SESSION_ACCEPT); + } + } +} + +static void +jingle_handle_session_info(JingleSession *session, xmlnode *jingle) +{ + jabber_iq_send(jingle_session_create_ack(session, jingle)); + /* XXX: call signal */ +} + +static void +jingle_handle_session_initiate(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = xmlnode_get_child(jingle, "content"); + + for (; content; content = xmlnode_get_next_twin(content)) { + JingleContent *parsed_content = jingle_content_parse(content); + if (parsed_content == NULL) { + purple_debug_error("jingle", "Error parsing content\n"); + /* XXX: send error */ + } else { + jingle_session_add_content(session, parsed_content); + jingle_content_handle_action(parsed_content, content, + JINGLE_SESSION_INITIATE); + } + } + + jabber_iq_send(jingle_session_create_ack(session, jingle)); +} + +static void +jingle_handle_session_terminate(JingleSession *session, xmlnode *jingle) +{ + jabber_iq_send(jingle_session_create_ack(session, jingle)); + + jingle_session_handle_action(session, jingle, + JINGLE_SESSION_TERMINATE); + /* display reason? */ + g_object_unref(session); +} + +static void +jingle_handle_transport_accept(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = xmlnode_get_child(jingle, "content"); + + jabber_iq_send(jingle_session_create_ack(session, jingle)); + + for (; content; content = xmlnode_get_next_twin(content)) { + const gchar *name = xmlnode_get_attrib(content, "name"); + const gchar *creator = xmlnode_get_attrib(content, "creator"); + JingleContent *content = jingle_session_find_content(session, name, creator); + jingle_content_accept_transport(content); + } +} + +static void +jingle_handle_transport_info(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = xmlnode_get_child(jingle, "content"); + + jabber_iq_send(jingle_session_create_ack(session, jingle)); + + for (; content; content = xmlnode_get_next_twin(content)) { + const gchar *name = xmlnode_get_attrib(content, "name"); + const gchar *creator = xmlnode_get_attrib(content, "creator"); + JingleContent *parsed_content = + jingle_session_find_content(session, name, creator); + if (parsed_content == NULL) { + purple_debug_error("jingle", "Error parsing content\n"); + /* XXX: send error */ + } else { + jingle_content_handle_action(parsed_content, content, + JINGLE_TRANSPORT_INFO); + } + } +} + +static void +jingle_handle_transport_reject(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = xmlnode_get_child(jingle, "content"); + + jabber_iq_send(jingle_session_create_ack(session, jingle)); + + for (; content; content = xmlnode_get_next_twin(content)) { + const gchar *name = xmlnode_get_attrib(content, "name"); + const gchar *creator = xmlnode_get_attrib(content, "creator"); + JingleContent *content = jingle_session_find_content(session, name, creator); + jingle_content_remove_pending_transport(content); + } +} + +static void +jingle_handle_transport_replace(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = xmlnode_get_child(jingle, "content"); + + jabber_iq_send(jingle_session_create_ack(session, jingle)); + + for (; content; content = xmlnode_get_next_twin(content)) { + const gchar *name = xmlnode_get_attrib(content, "name"); + const gchar *creator = xmlnode_get_attrib(content, "creator"); + xmlnode *xmltransport = xmlnode_get_child(content, "transport"); + JingleTransport *transport = jingle_transport_parse(xmltransport); + JingleContent *content = jingle_session_find_content(session, name, creator); + + jingle_content_set_pending_transport(content, transport); + } +} + +typedef struct { + const char *name; + void (*handler)(JingleSession*, xmlnode*); +} JingleAction; + +static const JingleAction jingle_actions[] = { + {"unknown-type", jingle_handle_unknown_type}, + {"content-accept", jingle_handle_content_accept}, + {"content-add", jingle_handle_content_add}, + {"content-modify", jingle_handle_content_modify}, + {"content-reject", jingle_handle_content_reject}, + {"content-remove", jingle_handle_content_remove}, + {"description-info", jingle_handle_description_info}, + {"security-info", jingle_handle_security_info}, + {"session-accept", jingle_handle_session_accept}, + {"session-info", jingle_handle_session_info}, + {"session-initiate", jingle_handle_session_initiate}, + {"session-terminate", jingle_handle_session_terminate}, + {"transport-accept", jingle_handle_transport_accept}, + {"transport-info", jingle_handle_transport_info}, + {"transport-reject", jingle_handle_transport_reject}, + {"transport-replace", jingle_handle_transport_replace}, +}; + +const gchar * +jingle_get_action_name(JingleActionType action) +{ + return jingle_actions[action].name; +} + +JingleActionType +jingle_get_action_type(const gchar *action) +{ + static const int num_actions = + sizeof(jingle_actions)/sizeof(JingleAction); + /* Start at 1 to skip the unknown-action type */ + int i = 1; + for (; i < num_actions; ++i) { + if (!strcmp(action, jingle_actions[i].name)) + return i; + } + return JINGLE_UNKNOWN_TYPE; +} + +void +jingle_parse(JabberStream *js, xmlnode *packet) +{ + const gchar *type = xmlnode_get_attrib(packet, "type"); + xmlnode *jingle; + const gchar *action; + const gchar *sid; + JingleActionType action_type; + 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; + } + + action_type = jingle_get_action_type(action); + + 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 (action_type == JINGLE_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 */ + return; + } else { + session = jingle_session_create(js, sid, + xmlnode_get_attrib(packet, "to"), + xmlnode_get_attrib(packet, "from"), FALSE); + } + } + + jingle_actions[action_type].handler(session, jingle); +} + +static void +jingle_terminate_sessions_gh(gpointer key, gpointer value, gpointer user_data) +{ + g_object_unref(value); +} + +void +jingle_terminate_sessions(JabberStream *js) +{ + if (js->sessions) + g_hash_table_foreach(js->sessions, + jingle_terminate_sessions_gh, NULL); +} + +GParameter * +jingle_get_params(JabberStream *js, guint *num) +{ + /* don't set a STUN server if one is set globally in prefs, in that case + this will be handled in media.c */ + gboolean has_account_stun = js->stun_ip && !purple_network_get_stun_ip(); + guint num_params = has_account_stun ? 2 : 0; + GParameter *params = NULL; + + if (num_params > 0) { + params = g_new0(GParameter, num_params); + + purple_debug_info("jabber", + "setting param stun-ip for stream using Google auto-config: %s\n", + js->stun_ip); + params[0].name = "stun-ip"; + g_value_init(¶ms[0].value, G_TYPE_STRING); + g_value_set_string(¶ms[0].value, js->stun_ip); + purple_debug_info("jabber", + "setting param stun-port for stream using Google auto-config: %d\n", + js->stun_port); + params[1].name = "stun-port"; + g_value_init(¶ms[1].value, G_TYPE_UINT); + g_value_set_uint(¶ms[1].value, js->stun_port); + } + + *num = num_params; + return params; +} diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/jingle.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/jingle.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,86 @@ +/* + * @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 +#include + +G_BEGIN_DECLS + +#ifdef __cplusplus +extern "C" { +#endif + +#define JINGLE "urn:xmpp:jingle:1" +#define JINGLE_ERROR "urn:xmpp:jingle:errors:0" +#define JINGLE_APP_FT "urn:xmpp:jingle:apps:file-transfer:1" +#define JINGLE_APP_RTP "urn:xmpp:jingle:apps:rtp:1" +#define JINGLE_APP_RTP_ERROR "urn:xmpp:jingle:apps:rtp:errors:1" +#define JINGLE_APP_RTP_INFO "urn:xmpp:jingle:apps:rtp:info:1" +#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_S5B "urn:xmpp:jingle:transports:s5b:0" +#define JINGLE_TRANSPORT_IBB "urn:xmpp:jingle:transports:ibb:0" +#define JINGLE_TRANSPORT_ICEUDP "urn:xmpp:jingle:transports:ice-udp:1" +#define JINGLE_TRANSPORT_RAWUDP "urn:xmpp:jingle:transports:raw-udp:1" + +typedef enum { + JINGLE_UNKNOWN_TYPE, + JINGLE_CONTENT_ACCEPT, + JINGLE_CONTENT_ADD, + JINGLE_CONTENT_MODIFY, + JINGLE_CONTENT_REJECT, + JINGLE_CONTENT_REMOVE, + JINGLE_DESCRIPTION_INFO, + JINGLE_SECURITY_INFO, + JINGLE_SESSION_ACCEPT, + JINGLE_SESSION_INFO, + JINGLE_SESSION_INITIATE, + JINGLE_SESSION_TERMINATE, + JINGLE_TRANSPORT_ACCEPT, + JINGLE_TRANSPORT_INFO, + JINGLE_TRANSPORT_REJECT, + JINGLE_TRANSPORT_REPLACE, +} JingleActionType; + +const gchar *jingle_get_action_name(JingleActionType action); +JingleActionType jingle_get_action_type(const gchar *action); + +GType jingle_get_type(const gchar *type); + +void jingle_parse(JabberStream *js, xmlnode *packet); + +void jingle_terminate_sessions(JabberStream *js); + +/* create a GParam array given autoconfigured STUN (and later perhaps TURN). + if google_talk is TRUE, set compatability mode to GOOGLE_TALK */ +GParameter *jingle_get_params(JabberStream *js, guint *num_params); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* JINGLE_H */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/rawudp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/rawudp.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,342 @@ +/** + * @file rawudp.c + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" + +#include "rawudp.h" +#include "jingle.h" +#include "debug.h" + +#include + +struct _JingleRawUdpPrivate +{ + GList *local_candidates; + GList *remote_candidates; +}; + +#define JINGLE_RAWUDP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_RAWUDP, JingleRawUdpPrivate)) + +static void jingle_rawudp_class_init (JingleRawUdpClass *klass); +static void jingle_rawudp_init (JingleRawUdp *rawudp); +static void jingle_rawudp_finalize (GObject *object); +static void jingle_rawudp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void jingle_rawudp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static JingleTransport *jingle_rawudp_parse_internal(xmlnode *rawudp); +static xmlnode *jingle_rawudp_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action); + +static JingleTransportClass *parent_class = NULL; + +enum { + PROP_0, + PROP_LOCAL_CANDIDATES, + PROP_REMOTE_CANDIDATES, +}; + +static JingleRawUdpCandidate * +jingle_rawudp_candidate_copy(JingleRawUdpCandidate *candidate) +{ + JingleRawUdpCandidate *new_candidate = g_new0(JingleRawUdpCandidate, 1); + new_candidate->generation = candidate->generation; + new_candidate->component = candidate->component; + new_candidate->id = g_strdup(candidate->id); + new_candidate->ip = g_strdup(candidate->ip); + new_candidate->port = candidate->port; + + new_candidate->rem_known = candidate->rem_known; + return new_candidate; +} + +static void +jingle_rawudp_candidate_free(JingleRawUdpCandidate *candidate) +{ + g_free(candidate->id); + g_free(candidate->ip); +} + +GType +jingle_rawudp_candidate_get_type() +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static("JingleRawUdpCandidate", + (GBoxedCopyFunc)jingle_rawudp_candidate_copy, + (GBoxedFreeFunc)jingle_rawudp_candidate_free); + } + return type; +} + +JingleRawUdpCandidate * +jingle_rawudp_candidate_new(const gchar *id, guint generation, guint component, const gchar *ip, guint port) +{ + JingleRawUdpCandidate *candidate = g_new0(JingleRawUdpCandidate, 1); + candidate->generation = generation; + candidate->component = component; + candidate->id = g_strdup(id); + candidate->ip = g_strdup(ip); + candidate->port = port; + + candidate->rem_known = FALSE; + return candidate; +} + +GType +jingle_rawudp_get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(JingleRawUdpClass), + NULL, + NULL, + (GClassInitFunc) jingle_rawudp_class_init, + NULL, + NULL, + sizeof(JingleRawUdp), + 0, + (GInstanceInitFunc) jingle_rawudp_init, + NULL + }; + type = g_type_register_static(JINGLE_TYPE_TRANSPORT, "JingleRawUdp", &info, 0); + } + return type; +} + +static void +jingle_rawudp_class_init (JingleRawUdpClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = jingle_rawudp_finalize; + gobject_class->set_property = jingle_rawudp_set_property; + gobject_class->get_property = jingle_rawudp_get_property; + klass->parent_class.to_xml = jingle_rawudp_to_xml_internal; + klass->parent_class.parse = jingle_rawudp_parse_internal; + klass->parent_class.transport_type = JINGLE_TRANSPORT_RAWUDP; + + g_object_class_install_property(gobject_class, PROP_LOCAL_CANDIDATES, + g_param_spec_pointer("local-candidates", + "Local candidates", + "The local candidates for this transport.", + G_PARAM_READABLE)); + + g_object_class_install_property(gobject_class, PROP_REMOTE_CANDIDATES, + g_param_spec_pointer("remote-candidates", + "Remote candidates", + "The remote candidates for this transport.", + G_PARAM_READABLE)); + + g_type_class_add_private(klass, sizeof(JingleRawUdpPrivate)); +} + +static void +jingle_rawudp_init (JingleRawUdp *rawudp) +{ + rawudp->priv = JINGLE_RAWUDP_GET_PRIVATE(rawudp); + rawudp->priv->local_candidates = NULL; + rawudp->priv->remote_candidates = NULL; +} + +static void +jingle_rawudp_finalize (GObject *rawudp) +{ +/* JingleRawUdpPrivate *priv = JINGLE_RAWUDP_GET_PRIVATE(rawudp); */ + purple_debug_info("jingle","jingle_rawudp_finalize\n"); + + G_OBJECT_CLASS(parent_class)->finalize(rawudp); +} + +static void +jingle_rawudp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + JingleRawUdp *rawudp; + g_return_if_fail(JINGLE_IS_RAWUDP(object)); + + rawudp = JINGLE_RAWUDP(object); + + switch (prop_id) { + case PROP_LOCAL_CANDIDATES: + rawudp->priv->local_candidates = + g_value_get_pointer(value); + break; + case PROP_REMOTE_CANDIDATES: + rawudp->priv->remote_candidates = + g_value_get_pointer(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +jingle_rawudp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + JingleRawUdp *rawudp; + g_return_if_fail(JINGLE_IS_RAWUDP(object)); + + rawudp = JINGLE_RAWUDP(object); + + switch (prop_id) { + case PROP_LOCAL_CANDIDATES: + g_value_set_pointer(value, rawudp->priv->local_candidates); + break; + case PROP_REMOTE_CANDIDATES: + g_value_set_pointer(value, rawudp->priv->remote_candidates); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +void +jingle_rawudp_add_local_candidate(JingleRawUdp *rawudp, JingleRawUdpCandidate *candidate) +{ + GList *iter = rawudp->priv->local_candidates; + + for (; iter; iter = g_list_next(iter)) { + JingleRawUdpCandidate *c = iter->data; + if (!strcmp(c->id, candidate->id)) { + guint generation = c->generation + 1; + + g_boxed_free(JINGLE_TYPE_RAWUDP_CANDIDATE, c); + rawudp->priv->local_candidates = g_list_delete_link( + rawudp->priv->local_candidates, iter); + + candidate->generation = generation; + + rawudp->priv->local_candidates = g_list_append( + rawudp->priv->local_candidates, candidate); + return; + } + } + + rawudp->priv->local_candidates = g_list_append( + rawudp->priv->local_candidates, candidate); +} + +GList * +jingle_rawudp_get_remote_candidates(JingleRawUdp *rawudp) +{ + return g_list_copy(rawudp->priv->remote_candidates); +} + +static JingleRawUdpCandidate * +jingle_rawudp_get_remote_candidate_by_id(JingleRawUdp *rawudp, gchar *id) +{ + GList *iter = rawudp->priv->remote_candidates; + for (; iter; iter = g_list_next(iter)) { + JingleRawUdpCandidate *candidate = iter->data; + if (!strcmp(candidate->id, id)) { + return candidate; + } + } + return NULL; +} + +static void +jingle_rawudp_add_remote_candidate(JingleRawUdp *rawudp, JingleRawUdpCandidate *candidate) +{ + JingleRawUdpPrivate *priv = JINGLE_RAWUDP_GET_PRIVATE(rawudp); + JingleRawUdpCandidate *rawudp_candidate = + jingle_rawudp_get_remote_candidate_by_id(rawudp, candidate->id); + if (rawudp_candidate != NULL) { + priv->remote_candidates = g_list_remove( + priv->remote_candidates, rawudp_candidate); + g_boxed_free(JINGLE_TYPE_RAWUDP_CANDIDATE, rawudp_candidate); + } + priv->remote_candidates = g_list_append(priv->remote_candidates, candidate); +} + +static JingleTransport * +jingle_rawudp_parse_internal(xmlnode *rawudp) +{ + JingleTransport *transport = parent_class->parse(rawudp); + JingleRawUdpPrivate *priv = JINGLE_RAWUDP_GET_PRIVATE(transport); + xmlnode *candidate = xmlnode_get_child(rawudp, "candidate"); + JingleRawUdpCandidate *rawudp_candidate = NULL; + + for (; candidate; candidate = xmlnode_get_next_twin(candidate)) { + rawudp_candidate = jingle_rawudp_candidate_new( + xmlnode_get_attrib(candidate, "id"), + atoi(xmlnode_get_attrib(candidate, "generation")), + atoi(xmlnode_get_attrib(candidate, "component")), + xmlnode_get_attrib(candidate, "ip"), + atoi(xmlnode_get_attrib(candidate, "port"))); + rawudp_candidate->rem_known = TRUE; + jingle_rawudp_add_remote_candidate(JINGLE_RAWUDP(transport), rawudp_candidate); + } + + if (rawudp_candidate != NULL && + g_list_length(priv->remote_candidates) == 1) { + /* manufacture rtcp candidate */ + rawudp_candidate = g_boxed_copy(JINGLE_TYPE_RAWUDP_CANDIDATE, rawudp_candidate); + rawudp_candidate->component = 2; + rawudp_candidate->port = rawudp_candidate->port + 1; + rawudp_candidate->rem_known = TRUE; + jingle_rawudp_add_remote_candidate(JINGLE_RAWUDP(transport), rawudp_candidate); + } + + return transport; +} + +static xmlnode * +jingle_rawudp_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action) +{ + xmlnode *node = parent_class->to_xml(transport, content, action); + + if (action == JINGLE_SESSION_INITIATE || + action == JINGLE_TRANSPORT_INFO || + action == JINGLE_SESSION_ACCEPT) { + JingleRawUdpPrivate *priv = JINGLE_RAWUDP_GET_PRIVATE(transport); + GList *iter = priv->local_candidates; + + for (; iter; iter = g_list_next(iter)) { + JingleRawUdpCandidate *candidate = iter->data; + xmlnode *xmltransport; + gchar *generation, *component, *port; + + if (candidate->rem_known == TRUE) + continue; + candidate->rem_known = TRUE; + + xmltransport = xmlnode_new_child(node, "candidate"); + generation = g_strdup_printf("%d", candidate->generation); + component = g_strdup_printf("%d", candidate->component); + port = g_strdup_printf("%d", candidate->port); + + xmlnode_set_attrib(xmltransport, "generation", generation); + xmlnode_set_attrib(xmltransport, "component", component); + xmlnode_set_attrib(xmltransport, "id", candidate->id); + xmlnode_set_attrib(xmltransport, "ip", candidate->ip); + xmlnode_set_attrib(xmltransport, "port", port); + + g_free(port); + g_free(generation); + } + } + + return node; +} + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/rawudp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/rawudp.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,101 @@ +/** + * @file rawudp.h + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef JINGLE_RAWUDP_H +#define JINGLE_RAWUDP_H + +#include +#include + +#include "transport.h" + +G_BEGIN_DECLS + +#define JINGLE_TYPE_RAWUDP (jingle_rawudp_get_type()) +#define JINGLE_TYPE_RAWUDP_CANDIDATE (jingle_rawudp_candidate_get_type()) +#define JINGLE_RAWUDP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_RAWUDP, JingleRawUdp)) +#define JINGLE_RAWUDP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_RAWUDP, JingleRawUdpClass)) +#define JINGLE_IS_RAWUDP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_RAWUDP)) +#define JINGLE_IS_RAWUDP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_RAWUDP)) +#define JINGLE_RAWUDP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_RAWUDP, JingleRawUdpClass)) + +/** @copydoc _JingleRawUdp */ +typedef struct _JingleRawUdp JingleRawUdp; +/** @copydoc _JingleRawUdpClass */ +typedef struct _JingleRawUdpClass JingleRawUdpClass; +/** @copydoc _JingleRawUdpPrivate */ +typedef struct _JingleRawUdpPrivate JingleRawUdpPrivate; +/** @copydoc _JingleRawUdpCandidate */ +typedef struct _JingleRawUdpCandidate JingleRawUdpCandidate; + +/** The rawudp class */ +struct _JingleRawUdpClass +{ + JingleTransportClass parent_class; /**< The parent class. */ + + xmlnode *(*to_xml) (JingleTransport *transport, xmlnode *content, JingleActionType action); + JingleTransport *(*parse) (xmlnode *transport); +}; + +/** The rawudp class's private data */ +struct _JingleRawUdp +{ + JingleTransport parent; /**< The parent of this object. */ + JingleRawUdpPrivate *priv; /**< The private data of this object. */ +}; + +struct _JingleRawUdpCandidate +{ + guint generation; + guint component; + gchar *id; + gchar *ip; + guint port; + + gboolean rem_known; /* TRUE if the remote side knows + * about this candidate */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +GType jingle_rawudp_candidate_get_type(void); + +/** + * Gets the rawudp class's GType + * + * @return The rawudp class's GType. + */ +GType jingle_rawudp_get_type(void); + +JingleRawUdpCandidate *jingle_rawudp_candidate_new(const gchar *id, + guint generation, guint component, const gchar *ip, guint port); +void jingle_rawudp_add_local_candidate(JingleRawUdp *rawudp, JingleRawUdpCandidate *candidate); +GList *jingle_rawudp_get_remote_candidates(JingleRawUdp *rawudp); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* JINGLE_RAWUDP_H */ + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/rtp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/rtp.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,884 @@ +/** + * @file rtp.c + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "config.h" + +#ifdef USE_VV + +#include "jabber.h" +#include "jingle.h" +#include "media.h" +#include "mediamanager.h" +#include "iceudp.h" +#include "rawudp.h" +#include "rtp.h" +#include "session.h" +#include "debug.h" + +#include + +struct _JingleRtpPrivate +{ + gchar *media_type; + gchar *ssrc; +}; + +#define JINGLE_RTP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_RTP, JingleRtpPrivate)) + +static void jingle_rtp_class_init (JingleRtpClass *klass); +static void jingle_rtp_init (JingleRtp *rtp); +static void jingle_rtp_finalize (GObject *object); +static void jingle_rtp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void jingle_rtp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static JingleContent *jingle_rtp_parse_internal(xmlnode *rtp); +static xmlnode *jingle_rtp_to_xml_internal(JingleContent *rtp, xmlnode *content, JingleActionType action); +static void jingle_rtp_handle_action_internal(JingleContent *content, xmlnode *jingle, JingleActionType action); + +static PurpleMedia *jingle_rtp_get_media(JingleSession *session); + +static JingleContentClass *parent_class = NULL; +#if 0 +enum { + LAST_SIGNAL +}; +static guint jingle_rtp_signals[LAST_SIGNAL] = {0}; +#endif + +enum { + PROP_0, + PROP_MEDIA_TYPE, + PROP_SSRC, +}; + +GType +jingle_rtp_get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(JingleRtpClass), + NULL, + NULL, + (GClassInitFunc) jingle_rtp_class_init, + NULL, + NULL, + sizeof(JingleRtp), + 0, + (GInstanceInitFunc) jingle_rtp_init, + NULL + }; + type = g_type_register_static(JINGLE_TYPE_CONTENT, "JingleRtp", &info, 0); + } + return type; +} + +static void +jingle_rtp_class_init (JingleRtpClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = jingle_rtp_finalize; + gobject_class->set_property = jingle_rtp_set_property; + gobject_class->get_property = jingle_rtp_get_property; + klass->parent_class.to_xml = jingle_rtp_to_xml_internal; + klass->parent_class.parse = jingle_rtp_parse_internal; + klass->parent_class.description_type = JINGLE_APP_RTP; + klass->parent_class.handle_action = jingle_rtp_handle_action_internal; + + g_object_class_install_property(gobject_class, PROP_MEDIA_TYPE, + g_param_spec_string("media-type", + "Media Type", + "The media type (\"audio\" or \"video\") for this rtp session.", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_SSRC, + g_param_spec_string("ssrc", + "ssrc", + "The ssrc for this rtp session.", + NULL, + G_PARAM_READWRITE)); + + g_type_class_add_private(klass, sizeof(JingleRtpPrivate)); +} + +static void +jingle_rtp_init (JingleRtp *rtp) +{ + rtp->priv = JINGLE_RTP_GET_PRIVATE(rtp); + memset(rtp->priv, 0, sizeof(*rtp->priv)); +} + +static void +jingle_rtp_finalize (GObject *rtp) +{ + JingleRtpPrivate *priv = JINGLE_RTP_GET_PRIVATE(rtp); + purple_debug_info("jingle-rtp","jingle_rtp_finalize\n"); + + g_free(priv->media_type); + g_free(priv->ssrc); + + G_OBJECT_CLASS(parent_class)->finalize(rtp); +} + +static void +jingle_rtp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + JingleRtp *rtp; + g_return_if_fail(JINGLE_IS_RTP(object)); + + rtp = JINGLE_RTP(object); + + switch (prop_id) { + case PROP_MEDIA_TYPE: + g_free(rtp->priv->media_type); + rtp->priv->media_type = g_value_dup_string(value); + break; + case PROP_SSRC: + g_free(rtp->priv->ssrc); + rtp->priv->ssrc = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +jingle_rtp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + JingleRtp *rtp; + g_return_if_fail(JINGLE_IS_RTP(object)); + + rtp = JINGLE_RTP(object); + + switch (prop_id) { + case PROP_MEDIA_TYPE: + g_value_set_string(value, rtp->priv->media_type); + break; + case PROP_SSRC: + g_value_set_string(value, rtp->priv->ssrc); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +gchar * +jingle_rtp_get_media_type(JingleContent *content) +{ + gchar *media_type; + g_object_get(content, "media-type", &media_type, NULL); + return media_type; +} + +gchar * +jingle_rtp_get_ssrc(JingleContent *content) +{ + gchar *ssrc; + g_object_get(content, "ssrc", &ssrc, NULL); + return ssrc; +} + +static PurpleMedia * +jingle_rtp_get_media(JingleSession *session) +{ + JabberStream *js = jingle_session_get_js(session); + PurpleMedia *media = NULL; + GList *iter = purple_media_manager_get_media_by_connection( + purple_media_manager_get(), js->gc); + + for (; iter; iter = g_list_delete_link(iter, iter)) { + JingleSession *media_session = + purple_media_get_prpl_data(iter->data); + if (media_session == session) { + media = iter->data; + break; + } + } + if (iter != NULL) + g_list_free(iter); + + return media; +} + +static JingleRawUdpCandidate * +jingle_rtp_candidate_to_rawudp(JingleSession *session, guint generation, + PurpleMediaCandidate *candidate) +{ + gchar *id = jabber_get_next_id(jingle_session_get_js(session)); + JingleRawUdpCandidate *rawudp_candidate = + jingle_rawudp_candidate_new(id, + generation, candidate->component_id, + candidate->ip, candidate->port); + g_free(id); + return rawudp_candidate; +} + +static JingleIceUdpCandidate * +jingle_rtp_candidate_to_iceudp(JingleSession *session, guint generation, + PurpleMediaCandidate *candidate) +{ + gchar *id = jabber_get_next_id(jingle_session_get_js(session)); + JingleIceUdpCandidate *iceudp_candidate = jingle_iceudp_candidate_new( + candidate->component_id, candidate->foundation, + generation, id, candidate->ip, 0, + candidate->port, candidate->priority, "udp", + candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_HOST ? "host" : + candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX ? "srflx" : + candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX ? "prflx" : + candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_RELAY ? "relay" : "", + candidate->username, candidate->password); + iceudp_candidate->reladdr = g_strdup(candidate->base_ip); + iceudp_candidate->relport = candidate->base_port; + g_free(id); + return iceudp_candidate; +} + +static JingleTransport * +jingle_rtp_candidates_to_transport(JingleSession *session, GType type, guint generation, GList *candidates) +{ + if (type == JINGLE_TYPE_RAWUDP) { + JingleTransport *transport = jingle_transport_create(JINGLE_TRANSPORT_RAWUDP); + JingleRawUdpCandidate *rawudp_candidate; + for (; candidates; candidates = g_list_next(candidates)) { + PurpleMediaCandidate *candidate = candidates->data; + rawudp_candidate = jingle_rtp_candidate_to_rawudp( + session, generation, candidate); + jingle_rawudp_add_local_candidate( + JINGLE_RAWUDP(transport), + rawudp_candidate); + } + return transport; + } else if (type == JINGLE_TYPE_ICEUDP) { + JingleTransport *transport = jingle_transport_create(JINGLE_TRANSPORT_ICEUDP); + JingleIceUdpCandidate *iceudp_candidate; + for (; candidates; candidates = g_list_next(candidates)) { + PurpleMediaCandidate *candidate = candidates->data; + iceudp_candidate = jingle_rtp_candidate_to_iceudp( + session, generation, candidate); + jingle_iceudp_add_local_candidate( + JINGLE_ICEUDP(transport), + iceudp_candidate); + } + return transport; + } else { + return NULL; + } +} + +static GList * +jingle_rtp_transport_to_candidates(JingleTransport *transport) +{ + const gchar *type = jingle_transport_get_transport_type(transport); + GList *ret = NULL; + if (!strcmp(type, JINGLE_TRANSPORT_RAWUDP)) { + GList *candidates = jingle_rawudp_get_remote_candidates(JINGLE_RAWUDP(transport)); + + for (; candidates; candidates = g_list_delete_link(candidates, candidates)) { + JingleRawUdpCandidate *candidate = candidates->data; + ret = g_list_append(ret, purple_media_candidate_new( + "", candidate->component, + PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX, + PURPLE_MEDIA_NETWORK_PROTOCOL_UDP, + candidate->ip, candidate->port)); + } + + return ret; + } else if (!strcmp(type, JINGLE_TRANSPORT_ICEUDP)) { + GList *candidates = jingle_iceudp_get_remote_candidates(JINGLE_ICEUDP(transport)); + + for (; candidates; candidates = g_list_delete_link(candidates, candidates)) { + JingleIceUdpCandidate *candidate = candidates->data; + PurpleMediaCandidate *new_candidate = purple_media_candidate_new( + candidate->foundation, candidate->component, + !strcmp(candidate->type, "host") ? + PURPLE_MEDIA_CANDIDATE_TYPE_HOST : + !strcmp(candidate->type, "srflx") ? + PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX : + !strcmp(candidate->type, "prflx") ? + PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX : + !strcmp(candidate->type, "relay") ? + PURPLE_MEDIA_CANDIDATE_TYPE_RELAY : 0, + PURPLE_MEDIA_NETWORK_PROTOCOL_UDP, + candidate->ip, candidate->port); + new_candidate->base_ip = g_strdup(candidate->reladdr); + new_candidate->base_port = candidate->relport; + new_candidate->username = g_strdup(candidate->username); + new_candidate->password = g_strdup(candidate->password); + new_candidate->priority = candidate->priority; + ret = g_list_append(ret, new_candidate); + } + + return ret; + } else { + return NULL; + } +} + +static void jingle_rtp_ready(JingleSession *session); + +static void +jingle_rtp_accepted_cb(PurpleMedia *media, gchar *sid, gchar *name, + JingleSession *session) +{ + purple_debug_info("jingle-rtp", "jingle_rtp_accepted_cb\n"); + jingle_rtp_ready(session); +} + +static void +jingle_rtp_candidates_prepared_cb(PurpleMedia *media, + gchar *sid, gchar *name, JingleSession *session) +{ + JingleContent *content = jingle_session_find_content( + session, sid, NULL); + JingleTransport *oldtransport, *transport; + GList *candidates; + + purple_debug_info("jingle-rtp", "jingle_rtp_candidates_prepared_cb\n"); + + if (content == NULL) { + purple_debug_error("jingle-rtp", + "jingle_rtp_candidates_prepared_cb: " + "Can't find session %s\n", sid); + return; + } + + oldtransport = jingle_content_get_transport(content); + candidates = purple_media_get_local_candidates(media, sid, name); + transport = JINGLE_TRANSPORT(jingle_rtp_candidates_to_transport( + session, JINGLE_IS_RAWUDP(oldtransport) ? + JINGLE_TYPE_RAWUDP : JINGLE_TYPE_ICEUDP, + 0, candidates)); + + g_list_free(candidates); + g_object_unref(oldtransport); + + jingle_content_set_pending_transport(content, transport); + jingle_content_accept_transport(content); + + jingle_rtp_ready(session); +} + +static void +jingle_rtp_codecs_changed_cb(PurpleMedia *media, gchar *sid, + JingleSession *session) +{ + purple_debug_info("jingle-rtp", "jingle_rtp_codecs_changed_cb: " + "session_id: %s jingle_session: %p\n", sid, session); + jingle_rtp_ready(session); +} + +static void +jingle_rtp_new_candidate_cb(PurpleMedia *media, gchar *sid, gchar *name, PurpleMediaCandidate *candidate, JingleSession *session) +{ + JingleContent *content = jingle_session_find_content( + session, sid, NULL); + JingleTransport *transport; + + purple_debug_info("jingle-rtp", "jingle_rtp_new_candidate_cb\n"); + + if (content == NULL) { + purple_debug_error("jingle-rtp", + "jingle_rtp_new_candidate_cb: " + "Can't find session %s\n", sid); + return; + } + + transport = jingle_content_get_transport(content); + + if (JINGLE_IS_ICEUDP(transport)) + jingle_iceudp_add_local_candidate(JINGLE_ICEUDP(transport), + jingle_rtp_candidate_to_iceudp( + session, 1, candidate)); + else if (JINGLE_IS_RAWUDP(transport)) + jingle_rawudp_add_local_candidate(JINGLE_RAWUDP(transport), + jingle_rtp_candidate_to_rawudp( + session, 1, candidate)); + + g_object_unref(transport); + + jabber_iq_send(jingle_session_to_packet(session, + JINGLE_TRANSPORT_INFO)); +} + +static void +jingle_rtp_initiate_ack_cb(JabberStream *js, xmlnode *packet, gpointer data) +{ + JingleSession *session = data; + + if (!strcmp(xmlnode_get_attrib(packet, "type"), "error") || + xmlnode_get_child(packet, "error")) { + purple_media_end(jingle_rtp_get_media(session), NULL, NULL); + g_object_unref(session); + return; + } +} + +static void +jingle_rtp_state_changed_cb(PurpleMedia *media, PurpleMediaState state, + gchar *sid, gchar *name, JingleSession *session) +{ + purple_debug_info("jingle-rtp", "state-changed: state %d " + "id: %s name: %s\n", state, sid, name); +} + +static void +jingle_rtp_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, + gchar *sid, gchar *name, JingleSession *session) +{ + purple_debug_info("jingle-rtp", "stream-info: type %d " + "id: %s name: %s\n", type, sid, name); + if (type == PURPLE_MEDIA_INFO_HANGUP) { + jabber_iq_send(jingle_session_terminate_packet( + session, "success")); + g_object_unref(session); + } else if (type == PURPLE_MEDIA_INFO_REJECT) { + jabber_iq_send(jingle_session_terminate_packet( + session, "decline")); + g_object_unref(session); + } +} + +static void +jingle_rtp_ready(JingleSession *session) +{ + PurpleMedia *media = jingle_rtp_get_media(session); + + if (purple_media_candidates_prepared(media, NULL, NULL) && + purple_media_codecs_ready(media, NULL) && + (jingle_session_is_initiator(session) == TRUE || + purple_media_accepted(media, NULL, NULL))) { + if (jingle_session_is_initiator(session)) { + JabberIq *iq = jingle_session_to_packet( + session, JINGLE_SESSION_INITIATE); + jabber_iq_set_callback(iq, + jingle_rtp_initiate_ack_cb, session); + jabber_iq_send(iq); + } else { + jabber_iq_send(jingle_session_to_packet(session, + JINGLE_SESSION_ACCEPT)); + } + + g_signal_handlers_disconnect_by_func(G_OBJECT(media), + G_CALLBACK(jingle_rtp_accepted_cb), session); + g_signal_handlers_disconnect_by_func(G_OBJECT(media), + G_CALLBACK(jingle_rtp_candidates_prepared_cb), + session); + g_signal_handlers_disconnect_by_func(G_OBJECT(media), + G_CALLBACK(jingle_rtp_codecs_changed_cb), + session); + g_signal_connect(G_OBJECT(media), "new-candidate", + G_CALLBACK(jingle_rtp_new_candidate_cb), + session); + } +} + +static PurpleMedia * +jingle_rtp_create_media(JingleContent *content) +{ + JingleSession *session = jingle_content_get_session(content); + JabberStream *js = jingle_session_get_js(session); + gchar *remote_jid = jingle_session_get_remote_jid(session); + + PurpleMedia *media = purple_media_manager_create_media(purple_media_manager_get(), + js->gc, "fsrtpconference", remote_jid, + jingle_session_is_initiator(session)); + g_free(remote_jid); + + if (!media) { + purple_debug_error("jingle-rtp", "Couldn't create media session\n"); + return NULL; + } + + purple_media_set_prpl_data(media, session); + + /* connect callbacks */ + if (jingle_session_is_initiator(session) == FALSE) + g_signal_connect(G_OBJECT(media), "accepted", + G_CALLBACK(jingle_rtp_accepted_cb), session); + g_signal_connect(G_OBJECT(media), "candidates-prepared", + G_CALLBACK(jingle_rtp_candidates_prepared_cb), session); + g_signal_connect(G_OBJECT(media), "codecs-changed", + G_CALLBACK(jingle_rtp_codecs_changed_cb), session); + g_signal_connect(G_OBJECT(media), "state-changed", + G_CALLBACK(jingle_rtp_state_changed_cb), session); + g_signal_connect(G_OBJECT(media), "stream-info", + G_CALLBACK(jingle_rtp_stream_info_cb), session); + + g_object_unref(session); + return media; +} + +static gboolean +jingle_rtp_init_media(JingleContent *content) +{ + JingleSession *session = jingle_content_get_session(content); + PurpleMedia *media = jingle_rtp_get_media(session); + gchar *media_type; + gchar *remote_jid; + gchar *senders; + gchar *name; + const gchar *transmitter; + gboolean is_audio; + PurpleMediaSessionType type; + JingleTransport *transport; + GParameter *params = NULL; + guint num_params; + + /* maybe this create ought to just be in initiate and handle initiate */ + if (media == NULL) + media = jingle_rtp_create_media(content); + + if (media == NULL) + return FALSE; + + name = jingle_content_get_name(content); + media_type = jingle_rtp_get_media_type(content); + remote_jid = jingle_session_get_remote_jid(session); + senders = jingle_content_get_senders(content); + transport = jingle_content_get_transport(content); + + if (JINGLE_IS_RAWUDP(transport)) + transmitter = "rawudp"; + else if (JINGLE_IS_ICEUDP(transport)) + transmitter = "nice"; + else + transmitter = "notransmitter"; + g_object_unref(transport); + + is_audio = !strcmp(media_type, "audio"); + + if (!strcmp(senders, "both")) + type = is_audio == TRUE ? PURPLE_MEDIA_AUDIO + : PURPLE_MEDIA_VIDEO; + else if (!strcmp(senders, "initiator") + && jingle_session_is_initiator(session)) + type = is_audio == TRUE ? PURPLE_MEDIA_SEND_AUDIO + : PURPLE_MEDIA_SEND_VIDEO; + else + type = is_audio == TRUE ? PURPLE_MEDIA_RECV_AUDIO + : PURPLE_MEDIA_RECV_VIDEO; + + params = + jingle_get_params(jingle_session_get_js(session), &num_params); + purple_media_add_stream(media, name, remote_jid, + type, transmitter, num_params, params); + + g_free(name); + g_free(media_type); + g_free(remote_jid); + g_free(senders); + g_free(params); + g_object_unref(session); + + return TRUE; +} + +static GList * +jingle_rtp_parse_codecs(xmlnode *description) +{ + GList *codecs = NULL; + xmlnode *codec_element = NULL; + const char *encoding_name,*id, *clock_rate; + PurpleMediaCodec *codec; + const gchar *media = xmlnode_get_attrib(description, "media"); + PurpleMediaSessionType type = + !strcmp(media, "video") ? PURPLE_MEDIA_VIDEO : + !strcmp(media, "audio") ? PURPLE_MEDIA_AUDIO : 0; + + for (codec_element = xmlnode_get_child(description, "payload-type") ; + codec_element ; + codec_element = xmlnode_get_next_twin(codec_element)) { + xmlnode *param; + gchar *codec_str; + encoding_name = xmlnode_get_attrib(codec_element, "name"); + + id = xmlnode_get_attrib(codec_element, "id"); + clock_rate = xmlnode_get_attrib(codec_element, "clockrate"); + + codec = purple_media_codec_new(atoi(id), encoding_name, + type, + clock_rate ? atoi(clock_rate) : 0); + + for (param = xmlnode_get_child(codec_element, "parameter"); + param; param = xmlnode_get_next_twin(param)) { + purple_media_codec_add_optional_parameter(codec, + xmlnode_get_attrib(param, "name"), + xmlnode_get_attrib(param, "value")); + } + + codec_str = purple_media_codec_to_string(codec); + purple_debug_info("jingle-rtp", "received codec: %s\n", codec_str); + g_free(codec_str); + + codecs = g_list_append(codecs, codec); + } + return codecs; +} + +static JingleContent * +jingle_rtp_parse_internal(xmlnode *rtp) +{ + JingleContent *content = parent_class->parse(rtp); + xmlnode *description = xmlnode_get_child(rtp, "description"); + const gchar *media_type = xmlnode_get_attrib(description, "media"); + const gchar *ssrc = xmlnode_get_attrib(description, "ssrc"); + purple_debug_info("jingle-rtp", "rtp parse\n"); + g_object_set(content, "media-type", media_type, NULL); + if (ssrc != NULL) + g_object_set(content, "ssrc", ssrc, NULL); + return content; +} + +static void +jingle_rtp_add_payloads(xmlnode *description, GList *codecs) +{ + for (; codecs ; codecs = codecs->next) { + PurpleMediaCodec *codec = (PurpleMediaCodec*)codecs->data; + GList *iter = codec->optional_params; + char id[8], clockrate[10], channels[10]; + gchar *codec_str; + xmlnode *payload = xmlnode_new_child(description, "payload-type"); + + g_snprintf(id, sizeof(id), "%d", codec->id); + g_snprintf(clockrate, sizeof(clockrate), "%d", codec->clock_rate); + g_snprintf(channels, sizeof(channels), "%d", codec->channels); + + xmlnode_set_attrib(payload, "name", codec->encoding_name); + xmlnode_set_attrib(payload, "id", id); + xmlnode_set_attrib(payload, "clockrate", clockrate); + xmlnode_set_attrib(payload, "channels", channels); + + for (; iter; iter = g_list_next(iter)) { + PurpleMediaCodecParameter *mparam = iter->data; + xmlnode *param = xmlnode_new_child(payload, "parameter"); + xmlnode_set_attrib(param, "name", mparam->name); + xmlnode_set_attrib(param, "value", mparam->value); + } + + codec_str = purple_media_codec_to_string(codec); + purple_debug_info("jingle", "adding codec: %s\n", codec_str); + g_free(codec_str); + } +} + +static xmlnode * +jingle_rtp_to_xml_internal(JingleContent *rtp, xmlnode *content, JingleActionType action) +{ + xmlnode *node = parent_class->to_xml(rtp, content, action); + xmlnode *description = xmlnode_get_child(node, "description"); + if (description != NULL) { + JingleSession *session = jingle_content_get_session(rtp); + PurpleMedia *media = jingle_rtp_get_media(session); + gchar *media_type = jingle_rtp_get_media_type(rtp); + gchar *ssrc = jingle_rtp_get_ssrc(rtp); + gchar *name = jingle_content_get_name(rtp); + GList *codecs = purple_media_get_codecs(media, name); + + xmlnode_set_attrib(description, "media", media_type); + + if (ssrc != NULL) + xmlnode_set_attrib(description, "ssrc", ssrc); + + g_free(media_type); + g_free(name); + g_object_unref(session); + + jingle_rtp_add_payloads(description, codecs); + purple_media_codec_list_free(codecs); + } + return node; +} + +static void +jingle_rtp_handle_action_internal(JingleContent *content, xmlnode *xmlcontent, JingleActionType action) +{ + switch (action) { + case JINGLE_SESSION_ACCEPT: + case JINGLE_SESSION_INITIATE: { + JingleSession *session = jingle_content_get_session(content); + JingleTransport *transport = jingle_transport_parse( + xmlnode_get_child(xmlcontent, "transport")); + xmlnode *description = xmlnode_get_child(xmlcontent, "description"); + GList *candidates = jingle_rtp_transport_to_candidates(transport); + GList *codecs = jingle_rtp_parse_codecs(description); + gchar *name = jingle_content_get_name(content); + gchar *remote_jid = + jingle_session_get_remote_jid(session); + PurpleMedia *media; + + if (action == JINGLE_SESSION_INITIATE && + jingle_rtp_init_media(content) == FALSE) { + /* XXX: send error */ + jabber_iq_send(jingle_session_terminate_packet( + session, "general-error")); + g_object_unref(session); + break; + } + + media = jingle_rtp_get_media(session); + purple_media_set_remote_codecs(media, + name, remote_jid, codecs); + purple_media_add_remote_candidates(media, + name, remote_jid, candidates); + + /* This needs to be for the entire session, not a single content */ + /* very hacky */ + if (action == JINGLE_SESSION_ACCEPT && + xmlnode_get_next_twin(xmlcontent) == NULL) + purple_media_accept(media); + + g_free(remote_jid); + g_free(name); + g_object_unref(session); + break; + } + case JINGLE_SESSION_TERMINATE: { + JingleSession *session = jingle_content_get_session(content); + PurpleMedia *media = jingle_rtp_get_media(session); + + if (media != NULL) { + purple_media_end(media, NULL, NULL); + } + + g_object_unref(session); + break; + } + case JINGLE_TRANSPORT_INFO: { + JingleSession *session = jingle_content_get_session(content); + JingleTransport *transport = jingle_transport_parse( + xmlnode_get_child(xmlcontent, "transport")); + GList *candidates = jingle_rtp_transport_to_candidates(transport); + gchar *name = jingle_content_get_name(content); + gchar *remote_jid = + jingle_session_get_remote_jid(session); + + purple_media_add_remote_candidates( + jingle_rtp_get_media(session), + name, remote_jid, candidates); + + g_free(remote_jid); + g_free(name); + g_object_unref(session); + break; + } + default: + break; + } +} + +PurpleMedia * +jingle_rtp_initiate_media(JabberStream *js, const gchar *who, + PurpleMediaSessionType type) +{ + /* create content negotiation */ + JingleSession *session; + JingleContent *content; + JingleTransport *transport; + JabberBuddy *jb; + JabberBuddyResource *jbr; + PurpleMedia *media; + const gchar *transport_type; + + gchar *jid = NULL, *me = NULL, *sid = NULL; + + /* construct JID to send to */ + jb = jabber_buddy_find(js, who, FALSE); + if (!jb) { + purple_debug_error("jingle-rtp", "Could not find Jabber buddy\n"); + return NULL; + } + jbr = jabber_buddy_find_resource(jb, NULL); + if (!jbr) { + purple_debug_error("jingle-rtp", "Could not find buddy's resource\n"); + } + + if (jabber_resource_has_capability(jbr, JINGLE_TRANSPORT_ICEUDP)) { + transport_type = JINGLE_TRANSPORT_ICEUDP; + } else if (jabber_resource_has_capability(jbr, JINGLE_TRANSPORT_RAWUDP)) { + transport_type = JINGLE_TRANSPORT_RAWUDP; + } else { + purple_debug_error("jingle-rtp", "Resource doesn't support " + "the same transport types\n"); + return NULL; + } + + if ((strchr(who, '/') == NULL) && jbr && (jbr->name != NULL)) { + jid = g_strdup_printf("%s/%s", who, jbr->name); + } else { + jid = g_strdup(who); + } + + /* set ourselves as initiator */ + me = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, js->user->resource); + + sid = jabber_get_next_id(js); + session = jingle_session_create(js, sid, me, jid, TRUE); + g_free(sid); + + + if (type & PURPLE_MEDIA_AUDIO) { + transport = jingle_transport_create(transport_type); + content = jingle_content_create(JINGLE_APP_RTP, "initiator", + "session", "audio-session", "both", transport); + jingle_session_add_content(session, content); + JINGLE_RTP(content)->priv->media_type = g_strdup("audio"); + jingle_rtp_init_media(content); + } + if (type & PURPLE_MEDIA_VIDEO) { + transport = jingle_transport_create(transport_type); + content = jingle_content_create(JINGLE_APP_RTP, "initiator", + "session", "video-session", "both", transport); + jingle_session_add_content(session, content); + JINGLE_RTP(content)->priv->media_type = g_strdup("video"); + jingle_rtp_init_media(content); + } + + if ((media = jingle_rtp_get_media(session)) == NULL) { + return NULL; + } + + g_free(jid); + g_free(me); + + return media; +} + +void +jingle_rtp_terminate_session(JabberStream *js, const gchar *who) +{ + JingleSession *session; +/* XXX: This may cause file transfers and xml sessions to stop as well */ + session = jingle_session_find_by_jid(js, who); + + if (session) { + PurpleMedia *media = jingle_rtp_get_media(session); + if (media) { + purple_debug_info("jingle-rtp", "hanging up media\n"); + purple_media_hangup(media); + } + } +} + +#endif /* USE_VV */ + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/rtp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/rtp.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,92 @@ +/** + * @file rtp.h + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef JINGLE_RTP_H +#define JINGLE_RTP_H + +#include "config.h" + +#ifdef USE_VV + +#include +#include + +#include "content.h" +#include "media.h" +#include "xmlnode.h" + +G_BEGIN_DECLS + +#define JINGLE_TYPE_RTP (jingle_rtp_get_type()) +#define JINGLE_RTP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_RTP, JingleRtp)) +#define JINGLE_RTP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_RTP, JingleRtpClass)) +#define JINGLE_IS_RTP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_RTP)) +#define JINGLE_IS_RTP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_RTP)) +#define JINGLE_RTP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_RTP, JingleRtpClass)) + +/** @copydoc _JingleRtp */ +typedef struct _JingleRtp JingleRtp; +/** @copydoc _JingleRtpClass */ +typedef struct _JingleRtpClass JingleRtpClass; +/** @copydoc _JingleRtpPrivate */ +typedef struct _JingleRtpPrivate JingleRtpPrivate; + +/** The rtp class */ +struct _JingleRtpClass +{ + JingleContentClass parent_class; /**< The parent class. */ +}; + +/** The rtp class's private data */ +struct _JingleRtp +{ + JingleContent parent; /**< The parent of this object. */ + JingleRtpPrivate *priv; /**< The private data of this object. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets the rtp class's GType + * + * @return The rtp class's GType. + */ +GType jingle_rtp_get_type(void); + +gchar *jingle_rtp_get_media_type(JingleContent *content); +gchar *jingle_rtp_get_ssrc(JingleContent *content); + +PurpleMedia *jingle_rtp_initiate_media(JabberStream *js, + const gchar *who, + PurpleMediaSessionType type); +void jingle_rtp_terminate_session(JabberStream *js, const gchar *who); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* USE_VV */ + +#endif /* JINGLE_RTP_H */ + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/session.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/session.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,633 @@ +/** + * @file session.c + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" + +#include "content.h" +#include "debug.h" +#include "session.h" +#include "jingle.h" + +#include + +struct _JingleSessionPrivate +{ + gchar *sid; + JabberStream *js; + gchar *remote_jid; + gchar *local_jid; + gboolean is_initiator; + gboolean state; + GList *contents; + GList *pending_contents; +}; + +#define JINGLE_SESSION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_SESSION, JingleSessionPrivate)) + +static void jingle_session_class_init (JingleSessionClass *klass); +static void jingle_session_init (JingleSession *session); +static void jingle_session_finalize (GObject *object); +static void jingle_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void jingle_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); + +static GObjectClass *parent_class = NULL; + +enum { + PROP_0, + PROP_SID, + PROP_JS, + PROP_REMOTE_JID, + PROP_LOCAL_JID, + PROP_IS_INITIATOR, + PROP_STATE, + PROP_CONTENTS, + PROP_PENDING_CONTENTS, +}; + +GType +jingle_session_get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(JingleSessionClass), + NULL, + NULL, + (GClassInitFunc) jingle_session_class_init, + NULL, + NULL, + sizeof(JingleSession), + 0, + (GInstanceInitFunc) jingle_session_init, + NULL + }; + type = g_type_register_static(G_TYPE_OBJECT, "JingleSession", &info, 0); + } + return type; +} + +static void +jingle_session_class_init (JingleSessionClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = jingle_session_finalize; + gobject_class->set_property = jingle_session_set_property; + gobject_class->get_property = jingle_session_get_property; + + g_object_class_install_property(gobject_class, PROP_SID, + g_param_spec_string("sid", + "Session ID", + "The unique session ID of the Jingle Session.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_JS, + g_param_spec_pointer("js", + "JabberStream", + "The Jabber stream associated with this session.", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_REMOTE_JID, + g_param_spec_string("remote-jid", + "Remote JID", + "The JID of the remote participant.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_LOCAL_JID, + g_param_spec_string("local-jid", + "Local JID", + "The JID of the local participant.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_IS_INITIATOR, + g_param_spec_boolean("is-initiator", + "Is Initiator", + "Whether or not the local JID is the initiator of the session.", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_STATE, + g_param_spec_boolean("state", + "State", + "The state of the session (PENDING=FALSE, ACTIVE=TRUE).", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property(gobject_class, PROP_CONTENTS, + g_param_spec_pointer("contents", + "Contents", + "The active contents contained within this session", + G_PARAM_READABLE)); + + g_object_class_install_property(gobject_class, PROP_PENDING_CONTENTS, + g_param_spec_pointer("pending-contents", + "Pending contents", + "The pending contents contained within this session", + G_PARAM_READABLE)); + + g_type_class_add_private(klass, sizeof(JingleSessionPrivate)); +} + +static void +jingle_session_init (JingleSession *session) +{ + session->priv = JINGLE_SESSION_GET_PRIVATE(session); + memset(session->priv, 0, sizeof(*session->priv)); +} + +static void +jingle_session_finalize (GObject *session) +{ + JingleSessionPrivate *priv = JINGLE_SESSION_GET_PRIVATE(session); + purple_debug_info("jingle","jingle_session_finalize\n"); + + g_hash_table_remove(priv->js->sessions, priv->sid); + + g_free(priv->sid); + g_free(priv->remote_jid); + g_free(priv->local_jid); + + for (; priv->contents; priv->contents = + g_list_delete_link(priv->contents, priv->contents)) { + g_object_unref(priv->contents->data); + } + for (; priv->pending_contents; priv->pending_contents = + g_list_delete_link(priv->pending_contents, priv->pending_contents)) { + g_object_unref(priv->pending_contents->data); + } + + parent_class->finalize(session); +} + +static void +jingle_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + JingleSession *session; + g_return_if_fail(JINGLE_IS_SESSION(object)); + + session = JINGLE_SESSION(object); + + switch (prop_id) { + case PROP_SID: + g_free(session->priv->sid); + session->priv->sid = g_value_dup_string(value); + break; + case PROP_JS: + session->priv->js = g_value_get_pointer(value); + break; + case PROP_REMOTE_JID: + g_free(session->priv->remote_jid); + session->priv->remote_jid = g_value_dup_string(value); + break; + case PROP_LOCAL_JID: + g_free(session->priv->local_jid); + session->priv->local_jid = g_value_dup_string(value); + break; + case PROP_IS_INITIATOR: + session->priv->is_initiator = g_value_get_boolean(value); + break; + case PROP_STATE: + session->priv->state = g_value_get_boolean(value); + break; + case PROP_CONTENTS: + session->priv->contents = g_value_get_pointer(value); + break; + case PROP_PENDING_CONTENTS: + session->priv->pending_contents = g_value_get_pointer(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +jingle_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + JingleSession *session; + g_return_if_fail(JINGLE_IS_SESSION(object)); + + session = JINGLE_SESSION(object); + + switch (prop_id) { + case PROP_SID: + g_value_set_string(value, session->priv->sid); + break; + case PROP_JS: + g_value_set_pointer(value, session->priv->js); + break; + case PROP_REMOTE_JID: + g_value_set_string(value, session->priv->remote_jid); + break; + case PROP_LOCAL_JID: + g_value_set_string(value, session->priv->local_jid); + break; + case PROP_IS_INITIATOR: + g_value_set_boolean(value, session->priv->is_initiator); + break; + case PROP_STATE: + g_value_set_boolean(value, session->priv->state); + break; + case PROP_CONTENTS: + g_value_set_pointer(value, session->priv->contents); + break; + case PROP_PENDING_CONTENTS: + g_value_set_pointer(value, session->priv->pending_contents); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +JingleSession * +jingle_session_create(JabberStream *js, const gchar *sid, + const gchar *local_jid, const gchar *remote_jid, + gboolean is_initiator) +{ + JingleSession *session = g_object_new(jingle_session_get_type(), + "js", js, + "sid", sid, + "local-jid", local_jid, + "remote-jid", remote_jid, + "is_initiator", is_initiator, + NULL); + + /* insert it into the hash table */ + if (!js->sessions) { + purple_debug_info("jingle", + "Creating hash table for sessions\n"); + js->sessions = g_hash_table_new(g_str_hash, g_str_equal); + } + purple_debug_info("jingle", + "inserting session with key: %s into table\n", sid); + g_hash_table_insert(js->sessions, g_strdup(sid), session); + + return session; +} + +JabberStream * +jingle_session_get_js(JingleSession *session) +{ + JabberStream *js; + g_object_get(session, "js", &js, NULL); + return js; +} + +gchar * +jingle_session_get_sid(JingleSession *session) +{ + gchar *sid; + g_object_get(session, "sid", &sid, NULL); + return sid; +} + +gchar * +jingle_session_get_local_jid(JingleSession *session) +{ + gchar *local_jid; + g_object_get(session, "local-jid", &local_jid, NULL); + return local_jid; +} + +gchar * +jingle_session_get_remote_jid(JingleSession *session) +{ + gchar *remote_jid; + g_object_get(session, "remote-jid", &remote_jid, NULL); + return remote_jid; +} + +gboolean +jingle_session_is_initiator(JingleSession *session) +{ + gboolean is_initiator; + g_object_get(session, "is-initiator", &is_initiator, NULL); + return is_initiator; +} + +gboolean +jingle_session_get_state(JingleSession *session) +{ + gboolean state; + g_object_get(session, "state", &state, NULL); + return state; +} + +GList * +jingle_session_get_contents(JingleSession *session) +{ + GList *contents; + g_object_get(session, "contents", &contents, NULL); + return contents; +} + +GList * +jingle_session_get_pending_contents(JingleSession *session) +{ + GList *pending_contents; + g_object_get(session, "pending-contents", &pending_contents, NULL); + return pending_contents; +} + +JingleSession * +jingle_session_find_by_sid(JabberStream *js, const gchar *sid) +{ + purple_debug_info("jingle", "find_by_id %s\n", sid); + purple_debug_info("jingle", "lookup: %p\n", (js->sessions) ? + g_hash_table_lookup(js->sessions, sid) : NULL); + return (JingleSession *) (js->sessions) ? + g_hash_table_lookup(js->sessions, sid) : NULL; +} + +static gboolean find_by_jid_ghr(gpointer key, + gpointer value, gpointer user_data) +{ + JingleSession *session = (JingleSession *)value; + const gchar *jid = user_data; + gboolean use_bare = strchr(jid, '/') == NULL; + gchar *remote_jid = jingle_session_get_remote_jid(session); + gchar *cmp_jid = use_bare ? jabber_get_bare_jid(remote_jid) + : g_strdup(remote_jid); + g_free(remote_jid); + if (!strcmp(jid, cmp_jid)) { + g_free(cmp_jid); + return TRUE; + } + g_free(cmp_jid); + + return FALSE; +} + +JingleSession * +jingle_session_find_by_jid(JabberStream *js, const gchar *jid) +{ + return js->sessions != NULL ? + g_hash_table_find(js->sessions, + find_by_jid_ghr, (gpointer)jid) : NULL; +} + +static xmlnode * +jingle_add_jingle_packet(JingleSession *session, + JabberIq *iq, JingleActionType action) +{ + xmlnode *jingle = iq ? + xmlnode_new_child(iq->node, "jingle") : + xmlnode_new("jingle"); + gchar *local_jid = jingle_session_get_local_jid(session); + gchar *remote_jid = jingle_session_get_remote_jid(session); + + xmlnode_set_namespace(jingle, JINGLE); + xmlnode_set_attrib(jingle, "action", jingle_get_action_name(action)); + + if (jingle_session_is_initiator(session)) { + xmlnode_set_attrib(jingle, "initiator", + jingle_session_get_local_jid(session)); + xmlnode_set_attrib(jingle, "responder", + jingle_session_get_remote_jid(session)); + } else { + xmlnode_set_attrib(jingle, "initiator", + jingle_session_get_remote_jid(session)); + xmlnode_set_attrib(jingle, "responder", + jingle_session_get_local_jid(session)); + } + + g_free(local_jid); + g_free(remote_jid); + + xmlnode_set_attrib(jingle, "sid", jingle_session_get_sid(session)); + + return jingle; +} + +JabberIq * +jingle_session_create_ack(JingleSession *session, const xmlnode *jingle) +{ + JabberIq *result = jabber_iq_new( + jingle_session_get_js(session), + JABBER_IQ_RESULT); + xmlnode *packet = xmlnode_get_parent(jingle); + jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "from", xmlnode_get_attrib(packet, "to")); + xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from")); + return result; +} + +static JabberIq * +jingle_create_iq(JingleSession *session) +{ + JabberStream *js = jingle_session_get_js(session); + JabberIq *result = jabber_iq_new(js, JABBER_IQ_SET); + gchar *from = jingle_session_get_local_jid(session); + gchar *to = jingle_session_get_remote_jid(session); + + xmlnode_set_attrib(result->node, "from", from); + xmlnode_set_attrib(result->node, "to", to); + + g_free(from); + g_free(to); + return result; +} + +xmlnode * +jingle_session_to_xml(JingleSession *session, xmlnode *jingle, JingleActionType action) +{ + if (action != JINGLE_SESSION_INFO && action != JINGLE_SESSION_TERMINATE) { + GList *iter; + if (action == JINGLE_CONTENT_ACCEPT + || action == JINGLE_CONTENT_ADD + || action == JINGLE_CONTENT_REMOVE) + iter = jingle_session_get_pending_contents(session); + else + iter = jingle_session_get_contents(session); + + for (; iter; iter = g_list_next(iter)) { + jingle_content_to_xml(iter->data, jingle, action); + } + } + return jingle; +} + +JabberIq * +jingle_session_to_packet(JingleSession *session, JingleActionType action) +{ + JabberIq *iq = jingle_create_iq(session); + xmlnode *jingle = jingle_add_jingle_packet(session, iq, action); + jingle_session_to_xml(session, jingle, action); + return iq; +} + +void jingle_session_handle_action(JingleSession *session, xmlnode *jingle, JingleActionType action) +{ + GList *iter; + if (action == JINGLE_CONTENT_ADD || action == JINGLE_CONTENT_REMOVE) + iter = jingle_session_get_pending_contents(session); + else + iter = jingle_session_get_contents(session); + + for (; iter; iter = g_list_next(iter)) { + jingle_content_handle_action(iter->data, jingle, action); + } +} + +JingleContent * +jingle_session_find_content(JingleSession *session, const gchar *name, const gchar *creator) +{ + GList *iter = session->priv->contents; + for (; iter; iter = g_list_next(iter)) { + JingleContent *content = iter->data; + gchar *cname = jingle_content_get_name(content); + gboolean result = !strcmp(name, cname); + g_free(cname); + + if (creator != NULL) { + gchar *ccreator = jingle_content_get_creator(content); + result = (result && !strcmp(creator, ccreator)); + 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); + gboolean result = !strcmp(name, cname); + g_free(cname); + + if (creator != NULL) { + gchar *ccreator = jingle_content_get_creator(content); + result = (result && !strcmp(creator, ccreator)); + 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; +} + +JabberIq * +jingle_session_terminate_packet(JingleSession *session, const gchar *reason) +{ + JabberIq *iq = jingle_session_to_packet(session, + JINGLE_SESSION_TERMINATE); + xmlnode *jingle = xmlnode_get_child(iq->node, "jingle"); + + if (reason != NULL) { + xmlnode *reason_node; + reason_node = xmlnode_new_child(jingle, "reason"); + xmlnode_new_child(reason_node, reason); + } + return iq; +} + +JabberIq * +jingle_session_redirect_packet(JingleSession *session, const gchar *sid) +{ + JabberIq *iq = jingle_session_terminate_packet(session, + "alternative-session"); + xmlnode *alt_session; + + if (sid == NULL) + return iq; + + alt_session = xmlnode_get_child(iq->node, + "jingle/reason/alternative-session"); + + if (alt_session != NULL) { + xmlnode *sid_node = xmlnode_new_child(alt_session, "sid"); + xmlnode_insert_data(sid_node, sid, -1); + } + return iq; +} + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/session.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/session.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,115 @@ +/** + * @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 +#include + +G_BEGIN_DECLS + +#define JINGLE_TYPE_SESSION (jingle_session_get_type()) +#define JINGLE_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_SESSION, JingleSession)) +#define JINGLE_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_SESSION, JingleSessionClass)) +#define JINGLE_IS_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_SESSION)) +#define JINGLE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_SESSION)) +#define JINGLE_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_SESSION, JingleSessionClass)) + +/** @copydoc _JingleSession */ +typedef struct _JingleSession JingleSession; +/** @copydoc _JingleSessionClass */ +typedef struct _JingleSessionClass JingleSessionClass; +/** @copydoc _JingleSessionPrivate */ +typedef struct _JingleSessionPrivate JingleSessionPrivate; + +/** The session class */ +struct _JingleSessionClass +{ + GObjectClass parent_class; /**< The parent class. */ +}; + +/** The session class's private data */ +struct _JingleSession +{ + GObject parent; /**< The parent of this object. */ + JingleSessionPrivate *priv; /**< The private data of this object. */ +}; + +struct _JingleContent; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets the session class's GType + * + * @return The session class's GType. + */ +GType jingle_session_get_type(void); + +JingleSession *jingle_session_create(JabberStream *js, const gchar *sid, + const gchar *local_jid, const gchar *remote_jid, + gboolean is_initiator); +JabberStream *jingle_session_get_js(JingleSession *session); +gchar *jingle_session_get_sid(JingleSession *session); +gchar *jingle_session_get_local_jid(JingleSession *session); +gchar *jingle_session_get_remote_jid(JingleSession *session); +gboolean jingle_session_is_initiator(JingleSession *session); +gboolean jingle_session_get_state(JingleSession *session); + +GList *jingle_session_get_contents(JingleSession *session); +GList *jingle_session_get_pending_contents(JingleSession *session); + +JingleSession *jingle_session_find_by_sid(JabberStream *js, const gchar *sid); +JingleSession *jingle_session_find_by_jid(JabberStream *js, const gchar *jid); + +JabberIq *jingle_session_create_ack(JingleSession *session, const xmlnode *jingle); +xmlnode *jingle_session_to_xml(JingleSession *session, xmlnode *parent, JingleActionType action); +JabberIq *jingle_session_to_packet(JingleSession *session, JingleActionType action); + +void jingle_session_handle_action(JingleSession *session, xmlnode *jingle, JingleActionType action); + +struct _JingleContent *jingle_session_find_content(JingleSession *session, + const gchar *name, const gchar *creator); +struct _JingleContent *jingle_session_find_pending_content(JingleSession *session, + const gchar *name, const gchar *creator); + +void jingle_session_add_content(JingleSession *session, struct _JingleContent* content); +void jingle_session_remove_content(JingleSession *session, const gchar *name, const gchar *creator); +void jingle_session_add_pending_content(JingleSession *session, struct _JingleContent* content); +void jingle_session_remove_pending_content(JingleSession *session, const gchar *name, const gchar *creator); +void jingle_session_accept_content(JingleSession *session, const gchar *name, const gchar *creator); +void jingle_session_accept_session(JingleSession *session); +JabberIq *jingle_session_terminate_packet(JingleSession *session, const gchar *reason); +JabberIq *jingle_session_redirect_packet(JingleSession *session, const gchar *sid); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* JINGLE_SESSION_H */ + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/transport.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/transport.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,174 @@ +/** + * @file transport.c + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" + +#include "transport.h" +#include "jingle.h" +#include "debug.h" + +#include + +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); + transport->priv->dummy = NULL; +} + +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 -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/jingle/transport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/transport.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,88 @@ +/** + * @file transport.h + * + * purple + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef JINGLE_TRANSPORT_H +#define JINGLE_TRANSPORT_H + +#include +#include + +#include "jingle.h" +#include "xmlnode.h" + +G_BEGIN_DECLS + +#define JINGLE_TYPE_TRANSPORT (jingle_transport_get_type()) +#define JINGLE_TRANSPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_TRANSPORT, JingleTransport)) +#define JINGLE_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_TRANSPORT, JingleTransportClass)) +#define JINGLE_IS_TRANSPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_TRANSPORT)) +#define JINGLE_IS_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_TRANSPORT)) +#define JINGLE_TRANSPORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_TRANSPORT, JingleTransportClass)) + +/** @copydoc _JingleTransport */ +typedef struct _JingleTransport JingleTransport; +/** @copydoc _JingleTransportClass */ +typedef struct _JingleTransportClass JingleTransportClass; +/** @copydoc _JingleTransportPrivate */ +typedef struct _JingleTransportPrivate JingleTransportPrivate; + +/** The transport class */ +struct _JingleTransportClass +{ + GObjectClass parent_class; /**< The parent class. */ + + const gchar *transport_type; + xmlnode *(*to_xml) (JingleTransport *transport, xmlnode *content, JingleActionType action); + JingleTransport *(*parse) (xmlnode *transport); +}; + +/** The transport class's private data */ +struct _JingleTransport +{ + GObject parent; /**< The parent of this object. */ + JingleTransportPrivate *priv; /**< The private data of this object. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets the transport class's GType + * + * @return The transport class's GType. + */ +GType jingle_transport_get_type(void); + +JingleTransport *jingle_transport_create(const gchar *type); +const gchar *jingle_transport_get_transport_type(JingleTransport *transport); +void jingle_transport_add_candidate(); + +JingleTransport *jingle_transport_parse(xmlnode *transport); +xmlnode *jingle_transport_to_xml(JingleTransport *transport, xmlnode *content, JingleActionType action); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* JINGLE_TRANSPORT_H */ + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Sun Mar 22 23:33:42 2009 +0000 @@ -117,9 +117,10 @@ 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 */ + jabber_initiate_media, /* initiate_media */ + jabber_get_media_caps, /* get_media_caps */ }; static gboolean load_plugin(PurplePlugin *plugin) @@ -297,6 +298,9 @@ jabber_add_feature("ibb", XEP_0047_NAMESPACE, NULL); 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 -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/jabber/presence.c Sun Mar 22 23:33:42 2009 +0000 @@ -264,6 +264,10 @@ xmlnode_set_namespace(c, "http://jabber.org/protocol/caps"); xmlnode_set_attrib(c, "node", CAPS0115_NODE); xmlnode_set_attrib(c, "ver", VERSION); +#ifdef USE_VV + /* Make sure this is 'voice-v1', or you won't be able to talk to Google Talk */ + xmlnode_set_attrib(c, "ext", "voice-v1"); +#endif if(js != NULL) { /* add the extensions */ diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/msn/Makefile.am --- a/libpurple/protocols/msn/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/msn/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -91,4 +91,7 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/msn/msn.c Sun Mar 22 23:33:42 2009 +0000 @@ -2607,9 +2607,10 @@ NULL, /* unregister_user */ msn_send_attention, /* send_attention */ msn_attention_types, /* attention_types */ - sizeof(PurplePluginProtocolInfo), /* struct_size */ msn_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = @@ -2688,3 +2689,4 @@ } PURPLE_INIT_PLUGIN(msn, init_plugin, info); + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/msnp9/Makefile.am --- a/libpurple/protocols/msnp9/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/msnp9/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -87,4 +87,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/msnp9/msn.c --- a/libpurple/protocols/msnp9/msn.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/msnp9/msn.c Sun Mar 22 23:33:42 2009 +0000 @@ -2276,9 +2276,10 @@ NULL, /* unregister_user */ msn_send_attention, /* send_attention */ msn_attention_types, /* attention_types */ - sizeof(PurplePluginProtocolInfo), /* struct_size */ msn_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = @@ -2359,3 +2360,4 @@ } PURPLE_INIT_PLUGIN(msnp9, init_plugin, info); + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/myspace/Makefile.am --- a/libpurple/protocols/myspace/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/myspace/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -41,4 +41,7 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/myspace/myspace.c --- a/libpurple/protocols/myspace/myspace.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/myspace/myspace.c Sun Mar 22 23:33:42 2009 +0000 @@ -3085,9 +3085,10 @@ NULL, /* unregister_user */ msim_send_attention, /* send_attention */ msim_attention_types, /* attention_types */ - - sizeof(PurplePluginProtocolInfo), /* struct_size */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ msim_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; /** diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/novell/Makefile.am --- a/libpurple/protocols/novell/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/novell/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -50,4 +50,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(DEBUG_CFLAGS) \ - $(GLIB_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/novell/novell.c --- a/libpurple/protocols/novell/novell.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/novell/novell.c Sun Mar 22 23:33:42 2009 +0000 @@ -3520,13 +3520,13 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = { @@ -3581,3 +3581,4 @@ } PURPLE_INIT_PLUGIN(novell, init_plugin, info); + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/null/nullprpl.c --- a/libpurple/protocols/null/nullprpl.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/null/nullprpl.c Sun Mar 22 23:33:42 2009 +0000 @@ -1112,11 +1112,13 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - NULL, /* unregister_user */ + NULL, /* unregister_user */ NULL, /* send_attention */ - NULL, /* attention_types */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL, /* get_account_text_table */ + NULL, + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static void nullprpl_init(PurplePlugin *plugin) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/oscar/Makefile.am --- a/libpurple/protocols/oscar/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/oscar/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -75,4 +75,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/oscar/libaim.c --- a/libpurple/protocols/oscar/libaim.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/oscar/libaim.c Sun Mar 22 23:33:42 2009 +0000 @@ -95,9 +95,10 @@ NULL, /* unregister_user */ NULL, /* send_attention */ NULL, /* get_attention_types */ - sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/oscar/libicq.c --- a/libpurple/protocols/oscar/libicq.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/oscar/libicq.c Sun Mar 22 23:33:42 2009 +0000 @@ -26,7 +26,6 @@ #include "oscarcommon.h" - static GHashTable * icq_get_account_text_table(PurpleAccount *account) { @@ -107,6 +106,8 @@ sizeof(PurplePluginProtocolInfo), /* struct_size */ icq_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/qq/Makefile.am --- a/libpurple/protocols/qq/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/qq/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -76,4 +76,8 @@ -I$(top_builddir)/libpurple \ -DQQ_BUDDY_ICON_DIR=\"$(datadir)/pixmaps/purple/buddy_icons/qq\" \ $(DEBUG_CFLAGS) \ - $(GLIB_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/qq/qq.c --- a/libpurple/protocols/qq/qq.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/qq/qq.c Sun Mar 22 23:33:42 2009 +0000 @@ -1035,7 +1035,9 @@ NULL, /* get attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = { diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/sametime/Makefile.am --- a/libpurple/protocols/sametime/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/sametime/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -33,5 +33,6 @@ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(MEANWHILE_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ -DG_LOG_DOMAIN=\"sametime\" diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/sametime/sametime.c --- a/libpurple/protocols/sametime/sametime.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/sametime/sametime.c Sun Mar 22 23:33:42 2009 +0000 @@ -5209,7 +5209,8 @@ .new_xfer = mw_prpl_new_xfer, .offline_message = NULL, .whiteboard_prpl_ops = NULL, - .send_raw = NULL + .send_raw = NULL, + .struct_size = sizeof(PurplePluginProtocolInfo) }; diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/silc/Makefile.am --- a/libpurple/protocols/silc/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/silc/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -34,7 +34,7 @@ st = $(SILC_CFLAGS) pkg_LTLIBRARIES = libsilcpurple.la libsilcpurple_la_SOURCES = $(SILCSOURCES) -libsilcpurple_la_LIBADD = $(GLIB_LIBS) $(SILC_LIBS) +libsilcpurple_la_LIBADD = $(GLIB_LIBS) $(SILC_LIBS) $(FARSIGHT_LIBS) endif @@ -43,4 +43,7 @@ -I$(top_builddir)/libpurple \ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ - $(SILC_CFLAGS) + $(SILC_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/silc/silc.c --- a/libpurple/protocols/silc/silc.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/silc/silc.c Sun Mar 22 23:33:42 2009 +0000 @@ -2109,13 +2109,13 @@ &silcpurple_wb_ops, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = @@ -2249,3 +2249,4 @@ } PURPLE_INIT_PLUGIN(silc, init_plugin, info); + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/silc10/silc.c --- a/libpurple/protocols/silc10/silc.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/silc10/silc.c Sun Mar 22 23:33:42 2009 +0000 @@ -1836,12 +1836,13 @@ &silcpurple_wb_ops, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/simple/Makefile.am --- a/libpurple/protocols/simple/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/simple/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -33,4 +33,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/simple/simple.c --- a/libpurple/protocols/simple/simple.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/simple/simple.c Sun Mar 22 23:33:42 2009 +0000 @@ -2097,13 +2097,13 @@ NULL, /* whiteboard_prpl_ops */ simple_send_raw, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; @@ -2169,3 +2169,4 @@ } PURPLE_INIT_PLUGIN(simple, _init_plugin, info); + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/yahoo/Makefile.am --- a/libpurple/protocols/yahoo/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/yahoo/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -53,4 +53,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) \ No newline at end of file + $(FARSIGHT_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/yahoo/yahoo.c --- a/libpurple/protocols/yahoo/yahoo.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Sun Mar 22 23:33:42 2009 +0000 @@ -5412,6 +5412,8 @@ sizeof(PurplePluginProtocolInfo), /* struct_size */ yahoo_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/zephyr/Makefile.am --- a/libpurple/protocols/zephyr/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/zephyr/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -105,5 +105,9 @@ -I$(top_srcdir)/libpurple/protocols \ -DCONFDIR=\"$(sysconfdir)\" \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(KRB4_CFLAGS) \ - $(DEBUG_CFLAGS) + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/protocols/zephyr/zephyr.c --- a/libpurple/protocols/zephyr/zephyr.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/protocols/zephyr/zephyr.c Sun Mar 22 23:33:42 2009 +0000 @@ -2953,7 +2953,9 @@ NULL, NULL, sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = { diff -r 399776a9ad98 -r 7b63af454f26 libpurple/prpl.c --- a/libpurple/prpl.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/prpl.c Sun Mar 22 23:33:42 2009 +0000 @@ -496,6 +496,57 @@ 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 +} + +PurpleMediaCaps +purple_prpl_get_media_caps(PurpleAccount *account, const char *who) +{ +#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, + get_media_caps)) { + return prpl_info->get_media_caps(gc, who); + } +#endif + return PURPLE_MEDIA_CAPS_NONE; +} + /************************************************************************** * Protocol Plugin Subsystem API **************************************************************************/ @@ -517,3 +568,4 @@ return NULL; } + diff -r 399776a9ad98 -r 7b63af454f26 libpurple/prpl.h --- a/libpurple/prpl.h Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/prpl.h Sun Mar 22 23:33:42 2009 +0000 @@ -65,6 +65,7 @@ #include "conversation.h" #include "ft.h" #include "imgstore.h" +#include "media.h" #include "notify.h" #include "proxy.h" #include "plugin.h" @@ -72,7 +73,6 @@ #include "status.h" #include "whiteboard.h" - /** @copydoc PurpleBuddyIconSpec */ struct _PurpleBuddyIconSpec { /** This is a comma-delimited list of image formats or @c NULL if icons @@ -451,6 +451,27 @@ * 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. + * @return The media caps the contact supports. + */ + PurpleMediaCaps (*get_media_caps)(PurpleConnection *gc, + const char *who); }; #define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \ @@ -747,6 +768,30 @@ */ 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. + * + * @return The media caps the contact supports. + */ +PurpleMediaCaps purple_prpl_get_media_caps(PurpleAccount *account, + const char *who); + +/** + * 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 -r 399776a9ad98 -r 7b63af454f26 libpurple/xmlnode.c --- a/libpurple/xmlnode.c Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/xmlnode.c Sun Mar 22 23:33:42 2009 +0000 @@ -288,6 +288,12 @@ return node->prefix; } +xmlnode *xmlnode_get_parent(const xmlnode *child) +{ + g_return_val_if_fail(child != NULL, NULL); + return child->parent; +} + void xmlnode_free(xmlnode *node) { diff -r 399776a9ad98 -r 7b63af454f26 libpurple/xmlnode.h --- a/libpurple/xmlnode.h Sat Mar 21 02:20:52 2009 +0000 +++ b/libpurple/xmlnode.h Sun Mar 22 23:33:42 2009 +0000 @@ -268,6 +268,15 @@ const char *xmlnode_get_prefix(const xmlnode *node); /** + * Gets the parent node. + * + * @param child The child node. + * + * @return The parent or NULL. + */ +xmlnode *xmlnode_get_parent(const xmlnode *child); + +/** * Returns the node in a string of xml. * * @param node The starting node to output. diff -r 399776a9ad98 -r 7b63af454f26 pidgin/Makefile.am --- a/pidgin/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -103,6 +103,7 @@ gtkimhtmltoolbar.c \ gtklog.c \ gtkmain.c \ + gtkmedia.c \ gtkmenutray.c \ gtknotify.c \ gtkplugin.c \ @@ -160,6 +161,7 @@ gtkimhtml.h \ gtkimhtmltoolbar.h \ gtklog.h \ + gtkmedia.h \ gtkmenutray.h \ gtknickcolors.h \ gtknotify.h \ @@ -207,6 +209,8 @@ $(STARTUP_NOTIFICATION_LIBS) \ $(LIBXML_LIBS) \ $(GTK_LIBS) \ + $(FARSIGHT_LIBS) \ + $(GSTPROPS_LIBS) \ $(top_builddir)/libpurple/libpurple.la if USE_INTERNAL_LIBGADU @@ -231,5 +235,7 @@ $(GTKSPELL_CFLAGS) \ $(STARTUP_NOTIFICATION_CFLAGS) \ $(LIBXML_CFLAGS) \ - $(INTGG_CFLAGS) + $(INTGG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTPROPS_CFLAGS) endif # ENABLE_GTK diff -r 399776a9ad98 -r 7b63af454f26 pidgin/Makefile.mingw --- a/pidgin/Makefile.mingw Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/Makefile.mingw Sun Mar 22 23:33:42 2009 +0000 @@ -77,6 +77,7 @@ gtkimhtmltoolbar.c \ gtklog.c \ gtkmain.c \ + gtkmedia.c \ gtkmenutray.c \ gtknotify.c \ gtkplugin.c \ diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkblist.c Sun Mar 22 23:33:42 2009 +0000 @@ -338,6 +338,30 @@ purple_buddy_get_name(b)); } +#ifdef USE_VV +static void gtk_blist_menu_audio_call_cb(GtkWidget *w, PurpleBuddy *b) +{ + purple_prpl_initiate_media(purple_buddy_get_account(b), + purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO); +} + +static void gtk_blist_menu_video_call_cb(GtkWidget *w, PurpleBuddy *b) +{ + /* if the buddy supports both audio and video, start a combined call, + otherwise start a pure video session */ + if (purple_prpl_get_media_caps(purple_buddy_get_account(b), + purple_buddy_get_name(b)) & + PURPLE_MEDIA_CAPS_AUDIO_VIDEO) { + purple_prpl_initiate_media(purple_buddy_get_account(b), + purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO); + } else { + purple_prpl_initiate_media(purple_buddy_get_account(b), + purple_buddy_get_name(b), PURPLE_MEDIA_VIDEO); + } +} + +#endif + static void gtk_blist_menu_send_file_cb(GtkWidget *w, PurpleBuddy *b) { PurpleAccount *account = purple_buddy_get_account(b); @@ -1476,6 +1500,30 @@ } pidgin_new_item_from_stock(menu, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, G_CALLBACK(gtk_blist_menu_im_cb), buddy, 0, 0, NULL); + +#ifdef USE_VV + if (prpl_info && prpl_info->get_media_caps) { + PurpleAccount *account = purple_buddy_get_account(buddy); + const gchar *who = purple_buddy_get_name(buddy); + PurpleMediaCaps caps = purple_prpl_get_media_caps(account, who); + if (caps & PURPLE_MEDIA_CAPS_AUDIO) { + pidgin_new_item_from_stock(menu, _("_Audio Call"), + PIDGIN_STOCK_TOOLBAR_AUDIO_CALL, + G_CALLBACK(gtk_blist_menu_audio_call_cb), buddy, 0, 0, NULL); + } + if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) { + pidgin_new_item_from_stock(menu, _("Audio/_Video Call"), + PIDGIN_STOCK_TOOLBAR_VIDEO_CALL, + G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL); + } else if (caps & PURPLE_MEDIA_CAPS_VIDEO) { + pidgin_new_item_from_stock(menu, _("_Video Call"), + PIDGIN_STOCK_TOOLBAR_VIDEO_CALL, + G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL); + } + } + +#endif + if (prpl_info && prpl_info->send_file) { if (!prpl_info->can_receive_file || prpl_info->can_receive_file(buddy->account->gc, buddy->name)) diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkconv.c --- a/pidgin/gtkconv.c Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkconv.c Sun Mar 22 23:33:42 2009 +0000 @@ -1201,6 +1201,23 @@ gtk_widget_grab_focus(s->entry); } +#ifdef USE_VV +static void +menu_initiate_media_call_cb(gpointer data, guint action, GtkWidget *widget) +{ + PidginWindow *win = (PidginWindow *)data; + PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); + PurpleAccount *account = purple_conversation_get_account(conv); + + purple_prpl_initiate_media(account, + purple_conversation_get_name(conv), + action == 0 ? PURPLE_MEDIA_AUDIO : + action == 1 ? PURPLE_MEDIA_VIDEO : + action == 2 ? PURPLE_MEDIA_AUDIO | + PURPLE_MEDIA_VIDEO : PURPLE_MEDIA_NONE); +} +#endif + static void menu_send_file_cb(gpointer data, guint action, GtkWidget *widget) { @@ -3114,6 +3131,17 @@ { "/Conversation/sep1", NULL, NULL, 0, "", NULL }, +#ifdef USE_VV + { N_("/Conversation/M_edia"), NULL, NULL, 0, "", NULL }, + + { N_("/Conversation/Media/_Audio Call"), NULL, menu_initiate_media_call_cb, 0, + "", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL }, + { N_("/Conversation/Media/_Video Call"), NULL, menu_initiate_media_call_cb, 1, + "", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL }, + { N_("/Conversation/Media/Audio\\/Video _Call"), NULL, menu_initiate_media_call_cb, 2, + "", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL }, +#endif + { N_("/Conversation/Se_nd File..."), NULL, menu_send_file_cb, 0, "", PIDGIN_STOCK_TOOLBAR_SEND_FILE }, { N_("/Conversation/Add Buddy _Pounce..."), NULL, menu_add_pounce_cb, 0, "", NULL }, @@ -3424,6 +3452,18 @@ gtk_item_factory_get_widget(win->menu.item_factory, N_("/Conversation/View Log")); +#ifdef USE_VV + win->menu.audio_call = + gtk_item_factory_get_widget(win->menu.item_factory, + N_("/Conversation/Media/Audio Call")); + win->menu.video_call = + gtk_item_factory_get_widget(win->menu.item_factory, + N_("/Conversation/Media/Video Call")); + win->menu.audio_video_call = + gtk_item_factory_get_widget(win->menu.item_factory, + N_("/Conversation/Media/Audio\\/Video Call")); +#endif + /* --- */ win->menu.send_file = @@ -6407,6 +6447,36 @@ else buttons &= ~GTK_IMHTML_CUSTOM_SMILEY; +#ifdef USE_VV + /* check if account support voice calls, and if the current buddy + supports it */ + if (account != NULL && purple_conversation_get_type(conv) + == PURPLE_CONV_TYPE_IM) { + PurpleMediaCaps caps = + purple_prpl_get_media_caps(account, + purple_conversation_get_name(conv)); + + gtk_widget_set_sensitive(win->menu.audio_call, + caps & PURPLE_MEDIA_CAPS_AUDIO + ? TRUE : FALSE); + gtk_widget_set_sensitive(win->menu.video_call, + caps & PURPLE_MEDIA_CAPS_VIDEO + ? TRUE : FALSE); + gtk_widget_set_sensitive(win->menu.audio_video_call, + caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO + ? 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)); @@ -6944,7 +7014,7 @@ gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), FALSE); #endif gtk_widget_add_events(event, - GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); + GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); g_signal_connect(G_OBJECT(event), "button-press-event", G_CALLBACK(icon_menu), gtkconv); @@ -7785,7 +7855,6 @@ hide_new_pref_cb, NULL); - /********************************************************************** * Register signals **********************************************************************/ diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkconvwin.h --- a/pidgin/gtkconvwin.h Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkconvwin.h Sun Mar 22 23:33:42 2009 +0000 @@ -49,7 +49,11 @@ GtkWidget *menubar; GtkWidget *view_log; - +#ifdef USE_VV + GtkWidget *audio_call; + GtkWidget *video_call; + GtkWidget *audio_video_call; +#endif GtkWidget *send_file; GtkWidget *add_pounce; GtkWidget *get_info; diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkdebug.c --- a/pidgin/gtkdebug.c Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkdebug.c Sun Mar 22 23:33:42 2009 +0000 @@ -985,6 +985,13 @@ #ifdef USE_GSTREAMER REGISTER_G_LOG_HANDLER("GStreamer"); #endif +#ifdef USE_VV +#ifdef USE_FARSIGHT + REGISTER_G_LOG_HANDLER("farsight"); + REGISTER_G_LOG_HANDLER("farsight-transmitter"); + REGISTER_G_LOG_HANDLER("farsight-rtp"); +#endif +#endif #ifdef _WIN32 if (!purple_debug_is_enabled()) diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkdialogs.c --- a/pidgin/gtkdialogs.c Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkdialogs.c Sun Mar 22 23:33:42 2009 +0000 @@ -644,6 +644,12 @@ g_string_append(str, " Tk: Disabled
"); } +#ifdef USE_VV + g_string_append(str, " Voice and Video: Enabled
"); +#else + g_string_append(str, " Voice and Video: Disabled
"); +#endif + #ifndef _WIN32 #ifdef USE_SM g_string_append(str, " X Session Management: Enabled
"); diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkimhtmltoolbar.c --- a/pidgin/gtkimhtmltoolbar.c Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Sun Mar 22 23:33:42 2009 +0000 @@ -40,6 +40,8 @@ #include "gtkthemes.h" #include "gtkutils.h" +#include "debug.h" + #include static GtkHBoxClass *parent_class = NULL; @@ -449,12 +451,12 @@ static void insert_hr_cb(GtkWidget *widget, GtkIMHtmlToolbar *toolbar) { - GtkTextIter iter; - GtkTextMark *ins; + GtkTextIter iter; + GtkTextMark *ins; GtkIMHtmlScalable *hr; - ins = gtk_text_buffer_get_insert(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml))); - gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)), &iter, ins); + ins = gtk_text_buffer_get_insert(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml))); + gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)), &iter, ins); hr = gtk_imhtml_hr_new(); gtk_imhtml_hr_add_to(hr, GTK_IMHTML(toolbar->imhtml), &iter); } @@ -1297,6 +1299,7 @@ GtkWidget *insert_button; GtkWidget *font_button; GtkWidget *smiley_button; + GtkWidget *font_menu; GtkWidget *insert_menu; GtkWidget *menuitem; diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkimhtmltoolbar.h --- a/pidgin/gtkimhtmltoolbar.h Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkimhtmltoolbar.h Sun Mar 22 23:33:42 2009 +0000 @@ -76,6 +76,7 @@ char *sml; GtkWidget *strikethrough; GtkWidget *insert_hr; + GtkWidget *call; }; struct _GtkIMHtmlToolbarClass { diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkmain.c --- a/pidgin/gtkmain.c Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkmain.c Sun Mar 22 23:33:42 2009 +0000 @@ -53,6 +53,7 @@ #include "gtkft.h" #include "gtkidle.h" #include "gtklog.h" +#include "gtkmedia.h" #include "gtknotify.h" #include "gtkplugin.h" #include "gtkpounce.h" @@ -311,6 +312,9 @@ pidgin_docklet_init(); pidgin_smileys_init(); pidgin_utils_init(); +#ifdef USE_VV + pidgin_medias_init(); +#endif } static GHashTable *ui_info = NULL; @@ -465,6 +469,7 @@ gboolean opt_help = FALSE; gboolean opt_login = FALSE; gboolean opt_nologin = FALSE; + gboolean opt_nocrash = FALSE; gboolean opt_version = FALSE; gboolean opt_si = TRUE; /* Check for single instance? */ char *opt_config_dir_arg = NULL; @@ -496,6 +501,7 @@ {"login", optional_argument, NULL, 'l'}, {"multiple", no_argument, NULL, 'm'}, {"nologin", no_argument, NULL, 'n'}, + {"nocrash", no_argument, NULL, 'x'}, {"session", required_argument, NULL, 's'}, {"version", no_argument, NULL, 'v'}, {"display", required_argument, NULL, 'D'}, @@ -644,6 +650,9 @@ case 'm': /* do not ensure single instance. */ opt_si = FALSE; break; + case 'x': /* --nocrash */ + opt_nocrash = TRUE; + break; case 'D': /* --display */ case 'S': /* --sync */ /* handled by gtk_init_check below */ diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkmedia.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkmedia.c Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,992 @@ +/** + * @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 +#include "debug.h" +#include "internal.h" +#include "connection.h" +#include "media.h" +#include "mediamanager.h" +#include "pidgin.h" +#include "request.h" + +#include "gtkmedia.h" +#include "gtkutils.h" + +#ifdef USE_VV + +#include + +typedef enum +{ + /* Waiting for response */ + PIDGIN_MEDIA_WAITING = 1, + /* Got request */ + PIDGIN_MEDIA_REQUESTED, + /* Accepted call */ + PIDGIN_MEDIA_ACCEPTED, + /* Rejected call */ + PIDGIN_MEDIA_REJECTED, +} PidginMediaState; + +struct _PidginMediaPrivate +{ + PurpleMedia *media; + gchar *screenname; + GstElement *send_level; + GstElement *recv_level; + + GtkItemFactory *item_factory; + GtkWidget *menubar; + GtkWidget *statusbar; + + GtkWidget *mute; + + GtkWidget *send_progress; + GtkWidget *recv_progress; + + PidginMediaState state; + + GtkWidget *display; + GtkWidget *send_widget; + GtkWidget *recv_widget; + GtkWidget *local_video; + GtkWidget *remote_video; + PurpleConnection *pc; +}; + +#define PIDGIN_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PIDGIN_TYPE_MEDIA, PidginMediaPrivate)) + +static void pidgin_media_class_init (PidginMediaClass *klass); +static void pidgin_media_init (PidginMedia *media); +static void pidgin_media_dispose (GObject *object); +static void pidgin_media_finalize (GObject *object); +static void pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static void pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state); + +static GtkWindowClass *parent_class = NULL; + + +#if 0 +enum { + LAST_SIGNAL +}; +static guint pidgin_media_signals[LAST_SIGNAL] = {0}; +#endif + +enum { + PROP_0, + PROP_MEDIA, + PROP_SCREENNAME, + PROP_SEND_LEVEL, + PROP_RECV_LEVEL +}; + +GType +pidgin_media_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(PidginMediaClass), + NULL, + NULL, + (GClassInitFunc) pidgin_media_class_init, + NULL, + NULL, + sizeof(PidginMedia), + 0, + (GInstanceInitFunc) pidgin_media_init, + NULL + }; + type = g_type_register_static(GTK_TYPE_WINDOW, "PidginMedia", &info, 0); + } + return type; +} + + +static void +pidgin_media_class_init (PidginMediaClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; +/* GtkContainerClass *container_class = (GtkContainerClass*)klass; */ + parent_class = g_type_class_peek_parent(klass); + + gobject_class->dispose = pidgin_media_dispose; + gobject_class->finalize = pidgin_media_finalize; + gobject_class->set_property = pidgin_media_set_property; + gobject_class->get_property = pidgin_media_get_property; + + g_object_class_install_property(gobject_class, PROP_MEDIA, + g_param_spec_object("media", + "PurpleMedia", + "The PurpleMedia associated with this media.", + PURPLE_TYPE_MEDIA, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_SCREENNAME, + g_param_spec_string("screenname", + "Screenname", + "The screenname of the user this session is with.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_SEND_LEVEL, + g_param_spec_object("send-level", + "Send level", + "The GstElement of this media's send 'level'", + GST_TYPE_ELEMENT, + G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_RECV_LEVEL, + g_param_spec_object("recv-level", + "Receive level", + "The GstElement of this media's recv 'level'", + GST_TYPE_ELEMENT, + G_PARAM_READWRITE)); + + g_type_class_add_private(klass, sizeof(PidginMediaPrivate)); +} + +static void +pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media) +{ + purple_media_mute(media->priv->media, + gtk_toggle_button_get_active(toggle)); +} + +static gboolean +pidgin_media_delete_event_cb(GtkWidget *widget, + GdkEvent *event, PidginMedia *media) +{ + if (media->priv->media) + purple_media_hangup(media->priv->media); + return FALSE; +} + +static int +pidgin_x_error_handler(Display *display, XErrorEvent *event) +{ + const gchar *error_type; + switch (event->error_code) { +#define XERRORCASE(type) case type: error_type = #type; break + XERRORCASE(BadAccess); + XERRORCASE(BadAlloc); + XERRORCASE(BadAtom); + XERRORCASE(BadColor); + XERRORCASE(BadCursor); + XERRORCASE(BadDrawable); + XERRORCASE(BadFont); + XERRORCASE(BadGC); + XERRORCASE(BadIDChoice); + XERRORCASE(BadImplementation); + XERRORCASE(BadLength); + XERRORCASE(BadMatch); + XERRORCASE(BadName); + XERRORCASE(BadPixmap); + XERRORCASE(BadRequest); + XERRORCASE(BadValue); + XERRORCASE(BadWindow); +#undef XERRORCASE + default: + error_type = "unknown"; + break; + } + purple_debug_error("media", "A %s Xlib error has occurred. " + "The program would normally crash now.\n", + error_type); + return 0; +} + +static void +menu_hangup(gpointer data, guint action, GtkWidget *item) +{ + PidginMedia *gtkmedia = PIDGIN_MEDIA(data); + purple_media_hangup(gtkmedia->priv->media); +} + +static GtkItemFactoryEntry menu_items[] = { + { N_("/_Media"), NULL, NULL, 0, "", NULL }, + { N_("/Media/_Hangup"), NULL, menu_hangup, 0, "", NULL }, +}; + +static gint menu_item_count = sizeof(menu_items) / sizeof(menu_items[0]); + +static const char * +item_factory_translate_func (const char *path, gpointer func_data) +{ + return _(path); +} + +static GtkWidget * +setup_menubar(PidginMedia *window) +{ + GtkAccelGroup *accel_group; + GtkWidget *menu; + + accel_group = gtk_accel_group_new (); + gtk_window_add_accel_group(GTK_WINDOW(window), accel_group); + g_object_unref(accel_group); + + window->priv->item_factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, + "
", accel_group); + + gtk_item_factory_set_translate_func(window->priv->item_factory, + (GtkTranslateFunc)item_factory_translate_func, + NULL, NULL); + + gtk_item_factory_create_items(window->priv->item_factory, + menu_item_count, menu_items, window); + g_signal_connect(G_OBJECT(accel_group), "accel-changed", + G_CALLBACK(pidgin_save_accels_cb), NULL); + + menu = gtk_item_factory_get_widget( + window->priv->item_factory, "
"); + + gtk_widget_show(menu); + return menu; +} + +static void +pidgin_media_init (PidginMedia *media) +{ + GtkWidget *vbox, *hbox; + media->priv = PIDGIN_MEDIA_GET_PRIVATE(media); + + XSetErrorHandler(pidgin_x_error_handler); + + vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_container_add(GTK_CONTAINER(media), vbox); + + media->priv->statusbar = gtk_statusbar_new(); + gtk_box_pack_end(GTK_BOX(vbox), media->priv->statusbar, + FALSE, FALSE, 0); + gtk_statusbar_push(GTK_STATUSBAR(media->priv->statusbar), + 0, _("Calling...")); + gtk_widget_show(media->priv->statusbar); + + media->priv->menubar = setup_menubar(media); + gtk_box_pack_start(GTK_BOX(vbox), media->priv->menubar, + FALSE, TRUE, 0); + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show(GTK_WIDGET(hbox)); + + media->priv->mute = gtk_toggle_button_new_with_mnemonic("_Mute"); + + g_signal_connect(media->priv->mute, "toggled", + G_CALLBACK(pidgin_media_mute_toggled), media); + + gtk_box_pack_end(GTK_BOX(hbox), media->priv->mute, FALSE, FALSE, 0); + + media->priv->display = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_box_pack_start(GTK_BOX(vbox), media->priv->display, + TRUE, TRUE, PIDGIN_HIG_BOX_SPACE); + gtk_widget_show(vbox); + + g_signal_connect(G_OBJECT(media), "delete-event", + G_CALLBACK(pidgin_media_delete_event_cb), media); +} + +static gboolean +level_message_cb(GstBus *bus, GstMessage *message, PidginMedia *gtkmedia) +{ + gdouble rms_db; + gdouble percent; + const GValue *list; + const GValue *value; + + GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(message)); + GtkWidget *progress; + + if (message->type != GST_MESSAGE_ELEMENT) + return TRUE; + + if (!gst_structure_has_name( + gst_message_get_structure(message), "level")) + return TRUE; + + if (src == gtkmedia->priv->send_level) + progress = gtkmedia->priv->send_progress; + else if (src == gtkmedia->priv->recv_level) + progress = gtkmedia->priv->recv_progress; + else + return TRUE; + + list = gst_structure_get_value( + gst_message_get_structure(message), "rms"); + + /* Only bother with the first channel. */ + value = gst_value_list_get_value(list, 0); + rms_db = g_value_get_double(value); + + percent = pow(10, rms_db / 20) * 5; + + if(percent > 1.0) + percent = 1.0; + + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), percent); + return TRUE; +} + + +static void +pidgin_media_disconnect_levels(PurpleMedia *media, PidginMedia *gtkmedia) +{ + GstElement *element = purple_media_get_pipeline(media); + gulong handler_id = g_signal_handler_find(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))), + G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, + NULL, G_CALLBACK(level_message_cb), gtkmedia); + if (handler_id) + g_signal_handler_disconnect(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))), + handler_id); +} + +static void +pidgin_media_dispose(GObject *media) +{ + PidginMedia *gtkmedia = PIDGIN_MEDIA(media); + purple_debug_info("gtkmedia", "pidgin_media_dispose\n"); + + if (gtkmedia->priv->media) { + purple_request_close_with_handle(gtkmedia); + purple_media_remove_output_windows(gtkmedia->priv->media); + pidgin_media_disconnect_levels(gtkmedia->priv->media, gtkmedia); + g_object_unref(gtkmedia->priv->media); + gtkmedia->priv->media = NULL; + } + + if (gtkmedia->priv->item_factory) { + g_object_unref(gtkmedia->priv->item_factory); + gtkmedia->priv->item_factory = NULL; + } + + if (gtkmedia->priv->send_level) { + gst_object_unref(gtkmedia->priv->send_level); + gtkmedia->priv->send_level = NULL; + } + + if (gtkmedia->priv->recv_level) { + gst_object_unref(gtkmedia->priv->recv_level); + gtkmedia->priv->recv_level = NULL; + } + + G_OBJECT_CLASS(parent_class)->dispose(media); +} + +static void +pidgin_media_finalize(GObject *media) +{ + /* PidginMedia *gtkmedia = PIDGIN_MEDIA(media); */ + purple_debug_info("gtkmedia", "pidgin_media_finalize\n"); + + G_OBJECT_CLASS(parent_class)->finalize(media); +} + +static void +pidgin_media_emit_message(PidginMedia *gtkmedia, const char *msg) +{ + PurpleConversation *conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_ANY, gtkmedia->priv->screenname, + purple_connection_get_account(gtkmedia->priv->pc)); + if (conv != NULL) + purple_conversation_write(conv, NULL, msg, + PURPLE_MESSAGE_SYSTEM, time(NULL)); +} + +typedef struct +{ + PidginMedia *gtkmedia; + gchar *session_id; + gchar *participant; +} PidginMediaRealizeData; + +static gboolean +realize_cb_cb(PidginMediaRealizeData *data) +{ + PidginMediaPrivate *priv = data->gtkmedia->priv; + gulong window_id; + + if (data->participant == NULL) + window_id = GDK_WINDOW_XWINDOW(priv->local_video->window); + else + window_id = GDK_WINDOW_XWINDOW(priv->remote_video->window); + + purple_media_set_output_window(priv->media, data->session_id, + data->participant, window_id); + + g_free(data->session_id); + g_free(data->participant); + g_free(data); + return FALSE; +} + +static void +realize_cb(GtkWidget *widget, PidginMediaRealizeData *data) +{ + g_timeout_add(0, (GSourceFunc)realize_cb_cb, data); +} + +static void +pidgin_media_error_cb(PidginMedia *media, const char *error, PidginMedia *gtkmedia) +{ + PurpleConversation *conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_ANY, gtkmedia->priv->screenname, + purple_connection_get_account(gtkmedia->priv->pc)); + if (conv != NULL) + purple_conversation_write(conv, NULL, error, + PURPLE_MESSAGE_ERROR, time(NULL)); + gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar), + 0, error); +} + +static void +pidgin_media_accepted_cb(PurpleMedia *media, const gchar *session_id, + const gchar *participant, PidginMedia *gtkmedia) +{ + pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED); + pidgin_media_emit_message(gtkmedia, _("Call in progress.")); + gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar), + 0, _("Call in progress.")); + gtk_widget_show(GTK_WIDGET(gtkmedia)); +} + +static gboolean +plug_delete_event_cb(GtkWidget *widget, gpointer data) +{ + return TRUE; +} + +static gboolean +plug_removed_cb(GtkWidget *widget, gpointer data) +{ + return TRUE; +} + +static void +socket_realize_cb(GtkWidget *widget, gpointer data) +{ + gtk_socket_add_id(GTK_SOCKET(widget), + gtk_plug_get_id(GTK_PLUG(data))); +} + +static void +pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia, const gchar *sid) +{ + GstElement *pipeline = purple_media_get_pipeline(media); + GtkWidget *send_widget = NULL, *recv_widget = NULL; + gboolean is_initiator; + PurpleMediaSessionType type = + purple_media_get_session_type(media, sid); + + if (gtkmedia->priv->recv_widget == NULL + && type & (PURPLE_MEDIA_RECV_VIDEO | + PURPLE_MEDIA_RECV_AUDIO)) { + recv_widget = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display), + recv_widget, TRUE, TRUE, 0); + gtk_widget_show(recv_widget); + } else + recv_widget = gtkmedia->priv->recv_widget; + if (gtkmedia->priv->send_widget == NULL + && type & (PURPLE_MEDIA_SEND_VIDEO | + PURPLE_MEDIA_SEND_AUDIO)) { + send_widget = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display), + send_widget, TRUE, TRUE, 0); + gtk_widget_show(send_widget); + } else + send_widget = gtkmedia->priv->send_widget; + + if (type & PURPLE_MEDIA_RECV_VIDEO) { + PidginMediaRealizeData *data; + GtkWidget *aspect; + GtkWidget *remote_video; + GtkWidget *plug; + GtkWidget *socket; + GdkColor color = {0, 0, 0, 0}; + + aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE); + gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN); + gtk_box_pack_start(GTK_BOX(recv_widget), aspect, TRUE, TRUE, 0); + + plug = gtk_plug_new(0); + g_signal_connect(G_OBJECT(plug), "delete-event", + G_CALLBACK(plug_delete_event_cb), plug); + gtk_widget_show(plug); + + socket = gtk_socket_new(); + g_signal_connect(G_OBJECT(socket), "realize", + G_CALLBACK(socket_realize_cb), plug); + g_signal_connect(G_OBJECT(socket), "plug-removed", + G_CALLBACK(plug_removed_cb), NULL); + gtk_container_add(GTK_CONTAINER(aspect), socket); + gtk_widget_show(socket); + + data = g_new0(PidginMediaRealizeData, 1); + data->gtkmedia = gtkmedia; + data->session_id = g_strdup(sid); + data->participant = g_strdup(gtkmedia->priv->screenname); + + remote_video = gtk_drawing_area_new(); + gtk_widget_modify_bg(remote_video, GTK_STATE_NORMAL, &color); + g_signal_connect(G_OBJECT(remote_video), "realize", + G_CALLBACK(realize_cb), data); + gtk_container_add(GTK_CONTAINER(plug), remote_video); + gtk_widget_set_size_request (GTK_WIDGET(remote_video), 320, 240); + gtk_widget_show(remote_video); + gtk_widget_show(aspect); + + gtkmedia->priv->remote_video = remote_video; + } + if (type & PURPLE_MEDIA_SEND_VIDEO) { + PidginMediaRealizeData *data; + GtkWidget *aspect; + GtkWidget *local_video; + GtkWidget *plug; + GtkWidget *socket; + GdkColor color = {0, 0, 0, 0}; + + aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE); + gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN); + gtk_box_pack_start(GTK_BOX(send_widget), aspect, TRUE, TRUE, 0); + + plug = gtk_plug_new(0); + g_signal_connect(G_OBJECT(plug), "delete-event", + G_CALLBACK(plug_delete_event_cb), plug); + gtk_widget_show(plug); + + socket = gtk_socket_new(); + g_signal_connect(G_OBJECT(socket), "realize", + G_CALLBACK(socket_realize_cb), plug); + g_signal_connect(G_OBJECT(socket), "plug-removed", + G_CALLBACK(plug_removed_cb), NULL); + gtk_container_add(GTK_CONTAINER(aspect), socket); + gtk_widget_show(socket); + + data = g_new0(PidginMediaRealizeData, 1); + data->gtkmedia = gtkmedia; + data->session_id = g_strdup(sid); + data->participant = NULL; + + local_video = gtk_drawing_area_new(); + gtk_widget_modify_bg(local_video, GTK_STATE_NORMAL, &color); + g_signal_connect(G_OBJECT(local_video), "realize", + G_CALLBACK(realize_cb), data); + gtk_container_add(GTK_CONTAINER(plug), local_video); + gtk_widget_set_size_request (GTK_WIDGET(local_video), 160, 120); + + gtk_widget_show(local_video); + gtk_widget_show(aspect); + + gtkmedia->priv->local_video = local_video; + } + + if (type & PURPLE_MEDIA_RECV_AUDIO) { + gtkmedia->priv->recv_progress = gtk_progress_bar_new(); + gtk_widget_set_size_request(gtkmedia->priv->recv_progress, 320, 10); + gtk_box_pack_end(GTK_BOX(recv_widget), + gtkmedia->priv->recv_progress, FALSE, FALSE, 0); + gtk_widget_show(gtkmedia->priv->recv_progress); + } + if (type & PURPLE_MEDIA_SEND_AUDIO) { + GstElement *media_src = purple_media_get_src(media, sid); + gtkmedia->priv->send_level = gst_bin_get_by_name( + GST_BIN(media_src), "sendlevel"); + + gtkmedia->priv->send_progress = gtk_progress_bar_new(); + gtk_widget_set_size_request(gtkmedia->priv->send_progress, 320, 10); + gtk_box_pack_end(GTK_BOX(send_widget), + gtkmedia->priv->send_progress, FALSE, FALSE, 0); + gtk_widget_show(gtkmedia->priv->send_progress); + + gtk_widget_show(gtkmedia->priv->mute); + } + + + if (type & PURPLE_MEDIA_AUDIO) { + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + g_signal_connect(G_OBJECT(bus), "message::element", + G_CALLBACK(level_message_cb), gtkmedia); + gst_object_unref(bus); + } + + if (send_widget != NULL) + gtkmedia->priv->send_widget = send_widget; + if (recv_widget != NULL) + gtkmedia->priv->recv_widget = recv_widget; + + g_object_get(G_OBJECT(media), "initiator", &is_initiator, NULL); + + if (is_initiator == FALSE) { + gchar *message; + if (type & PURPLE_MEDIA_AUDIO && type & PURPLE_MEDIA_VIDEO) { + message = g_strdup_printf(_("%s wishes to start an audio/video session with you."), + gtkmedia->priv->screenname); + } else if (type & PURPLE_MEDIA_AUDIO) { + message = g_strdup_printf(_("%s wishes to start an audio session with you."), + gtkmedia->priv->screenname); + } else if (type & PURPLE_MEDIA_VIDEO) { + message = g_strdup_printf(_("%s wishes to start a video session with you."), + gtkmedia->priv->screenname); + } + pidgin_media_emit_message(gtkmedia, message); + g_free(message); + } + + gtk_widget_show(gtkmedia->priv->display); +} + +static void +pidgin_media_state_changed_cb(PurpleMedia *media, PurpleMediaState state, + gchar *sid, gchar *name, PidginMedia *gtkmedia) +{ + purple_debug_info("gtkmedia", "state: %d sid: %s name: %s\n", + state, sid, name); + if (sid == NULL && name == NULL) { + if (state == PURPLE_MEDIA_STATE_END) { + pidgin_media_emit_message(gtkmedia, + _("The call has been terminated.")); + gtk_widget_destroy(GTK_WIDGET(gtkmedia)); + } + } else if (state == PURPLE_MEDIA_STATE_NEW && + sid != NULL && name != NULL) { + pidgin_media_ready_cb(media, gtkmedia, sid); + } else if (state == PURPLE_MEDIA_STATE_CONNECTED && + purple_media_get_session_type(media, sid) & + PURPLE_MEDIA_RECV_AUDIO) { + GstElement *tee = purple_media_get_tee(media, sid, name); + GstIterator *iter = gst_element_iterate_src_pads(tee); + GstPad *sinkpad; + if (gst_iterator_next(iter, (gpointer)&sinkpad) + == GST_ITERATOR_OK) { + GstPad *peer = gst_pad_get_peer(sinkpad); + if (peer != NULL) { + gtkmedia->priv->recv_level = + gst_bin_get_by_name( + GST_BIN(GST_OBJECT_PARENT( + peer)), "recvlevel"); + gst_object_unref(peer); + } + gst_object_unref(sinkpad); + } + gst_iterator_free(iter); + } +} + +static void +pidgin_media_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, + gchar *sid, gchar *name, PidginMedia *gtkmedia) +{ + if (type == PURPLE_MEDIA_INFO_REJECT) { + pidgin_media_emit_message(gtkmedia, + _("You have rejected the call.")); + } +} + +static void +pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + PidginMedia *media; + g_return_if_fail(PIDGIN_IS_MEDIA(object)); + + media = PIDGIN_MEDIA(object); + switch (prop_id) { + case PROP_MEDIA: + { + gboolean initiator; + if (media->priv->media) + g_object_unref(media->priv->media); + media->priv->media = g_value_get_object(value); + g_object_ref(media->priv->media); + + g_object_get(G_OBJECT(media->priv->media), + "initiator", &initiator, NULL); + if (initiator == TRUE) + pidgin_media_set_state(media, PIDGIN_MEDIA_WAITING); + else + pidgin_media_set_state(media, PIDGIN_MEDIA_REQUESTED); + + g_signal_connect(G_OBJECT(media->priv->media), "error", + G_CALLBACK(pidgin_media_error_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "accepted", + G_CALLBACK(pidgin_media_accepted_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "state-changed", + G_CALLBACK(pidgin_media_state_changed_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "stream-info", + G_CALLBACK(pidgin_media_stream_info_cb), media); + break; + } + case PROP_SCREENNAME: + if (media->priv->screenname) + g_free(media->priv->screenname); + media->priv->screenname = g_value_dup_string(value); + break; + case PROP_SEND_LEVEL: + if (media->priv->send_level) + gst_object_unref(media->priv->send_level); + media->priv->send_level = g_value_get_object(value); + g_object_ref(media->priv->send_level); + break; + case PROP_RECV_LEVEL: + if (media->priv->recv_level) + gst_object_unref(media->priv->recv_level); + media->priv->recv_level = g_value_get_object(value); + g_object_ref(media->priv->recv_level); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + PidginMedia *media; + g_return_if_fail(PIDGIN_IS_MEDIA(object)); + + media = PIDGIN_MEDIA(object); + + switch (prop_id) { + case PROP_MEDIA: + g_value_set_object(value, media->priv->media); + break; + case PROP_SCREENNAME: + g_value_set_string(value, media->priv->screenname); + break; + case PROP_SEND_LEVEL: + g_value_set_object(value, media->priv->send_level); + break; + case PROP_RECV_LEVEL: + g_value_set_object(value, media->priv->recv_level); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +GtkWidget * +pidgin_media_new(PurpleMedia *media, const gchar *screenname) +{ + PidginMedia *gtkmedia = g_object_new(pidgin_media_get_type(), + "media", media, + "screenname", screenname, NULL); + return GTK_WIDGET(gtkmedia); +} + +static void +pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state) +{ + gtkmedia->priv->state = state; +} + +static gboolean +pidgin_media_new_cb(PurpleMediaManager *manager, PurpleMedia *media, + PurpleConnection *pc, gchar *screenname, gpointer nul) +{ + PidginMedia *gtkmedia = PIDGIN_MEDIA( + pidgin_media_new(media, screenname)); + gboolean initiator; + PurpleBuddy *buddy = purple_find_buddy( + purple_connection_get_account(pc), screenname); + const gchar *alias = buddy ? + purple_buddy_get_contact_alias(buddy) : screenname; + gtkmedia->priv->pc = pc; + gtk_window_set_title(GTK_WINDOW(gtkmedia), alias); + + g_object_get(G_OBJECT(media), "initiator", &initiator, NULL); + if (initiator == FALSE) { + gchar *message = g_strdup_printf("%s wishes to start a " + "media session with you\n", alias); + purple_request_accept_cancel(gtkmedia, "Media invitation", + message, NULL, PURPLE_DEFAULT_ACTION_NONE, + (void*)pc, screenname, NULL, media, + purple_media_accept, purple_media_reject); + g_free(message); + } else + gtk_widget_show(GTK_WIDGET(gtkmedia)); + + return TRUE; +} + +static GstElement * +create_default_video_src(void) +{ + GstElement *sendbin, *src, *videoscale, *capsfilter; + GstPad *pad; + GstPad *ghost; + GstCaps *caps; + const gchar *video_plugin = purple_prefs_get_string( + "/purple/media/video/plugin"); + const gchar *video_device = purple_prefs_get_string( + "/purple/media/video/device"); + + sendbin = gst_bin_new("purplesendvideobin"); + src = gst_element_factory_make(video_plugin, "purplevideosource"); + videoscale = gst_element_factory_make("videoscale", NULL); + capsfilter = gst_element_factory_make("capsfilter", NULL); + + /* It was recommended to set the size < 352x288 and framerate < 20 */ + caps = gst_caps_from_string("video/x-raw-yuv , width=[250,350] , " + "height=[200,275] , framerate=[10/1,20/1]"); + g_object_set(G_OBJECT(capsfilter), "caps", caps, NULL); + + gst_bin_add_many(GST_BIN(sendbin), src, + videoscale, capsfilter, NULL); + gst_element_link_many(src, videoscale, capsfilter, NULL); + + if (!strcmp(video_plugin, "videotestsrc")) { + /* Set is-live to true to throttle videotestsrc */ + g_object_set (G_OBJECT(src), "is-live", TRUE, NULL); + } + + pad = gst_element_get_static_pad(capsfilter, "src"); + ghost = gst_ghost_pad_new("ghostsrc", pad); + gst_object_unref(pad); + gst_element_add_pad(sendbin, ghost); + + if (video_device != NULL && strcmp(video_device, "")) + g_object_set(G_OBJECT(src), "device", video_device, NULL); + + return sendbin; +} + +static GstElement * +create_default_video_sink(void) +{ + return gst_element_factory_make("autovideosink", NULL); +} + +static GstElement * +create_default_audio_src(void) +{ + GstElement *bin, *src, *volume, *level; + GstPad *pad, *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; + + bin = 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); + level = gst_element_factory_make("level", "sendlevel"); + gst_bin_add_many(GST_BIN(bin), src, volume, level, NULL); + gst_element_link(src, volume); + gst_element_link(volume, level); + pad = gst_element_get_pad(level, "src"); + ghost = gst_ghost_pad_new("ghostsrc", pad); + gst_element_add_pad(bin, ghost); + g_object_set(G_OBJECT(level), "message", TRUE, NULL); + + if (audio_device != NULL && strcmp(audio_device, "")) + g_object_set(G_OBJECT(src), "device", audio_device, NULL); + + return bin; +} + +static GstElement * +create_default_audio_sink(void) +{ + GstElement *bin, *sink, *volume, *level, *queue; + GstPad *pad, *ghost; + double output_volume = purple_prefs_get_int( + "/purple/media/audio/volume/output")/10.0; + + bin = gst_bin_new("pidginrecvaudiobin"); + sink = gst_element_factory_make("alsasink", "asink"); + g_object_set(G_OBJECT(sink), "async", FALSE, "sync", FALSE, NULL); + volume = gst_element_factory_make("volume", "purpleaudiooutputvolume"); + g_object_set(volume, "volume", output_volume, NULL); + level = gst_element_factory_make("level", "recvlevel"); + queue = gst_element_factory_make("queue", NULL); + gst_bin_add_many(GST_BIN(bin), sink, volume, level, queue, NULL); + gst_element_link(level, sink); + gst_element_link(volume, level); + gst_element_link(queue, volume); + pad = gst_element_get_pad(queue, "sink"); + ghost = gst_ghost_pad_new("ghostsink", pad); + gst_element_add_pad(bin, ghost); + g_object_set(G_OBJECT(level), "message", TRUE, NULL); + + return bin; +} + +static PurpleMediaElementInfo default_video_src = +{ + "pidgindefaultvideosrc", /* id */ + PURPLE_MEDIA_ELEMENT_VIDEO /* type */ + | PURPLE_MEDIA_ELEMENT_SRC + | PURPLE_MEDIA_ELEMENT_ONE_SRC + | PURPLE_MEDIA_ELEMENT_UNIQUE, + create_default_video_src, /* create */ +}; + +static PurpleMediaElementInfo default_video_sink = +{ + "pidgindefaultvideosink", /* id */ + PURPLE_MEDIA_ELEMENT_VIDEO /* type */ + | PURPLE_MEDIA_ELEMENT_SINK + | PURPLE_MEDIA_ELEMENT_ONE_SINK, + create_default_video_sink, /* create */ +}; + +static PurpleMediaElementInfo default_audio_src = +{ + "pidgindefaultaudiosrc", /* id */ + PURPLE_MEDIA_ELEMENT_AUDIO /* type */ + | PURPLE_MEDIA_ELEMENT_SRC + | PURPLE_MEDIA_ELEMENT_ONE_SRC + | PURPLE_MEDIA_ELEMENT_UNIQUE, + create_default_audio_src, /* create */ +}; + +static PurpleMediaElementInfo default_audio_sink = +{ + "pidgindefaultaudiosink", /* id */ + PURPLE_MEDIA_ELEMENT_AUDIO /* type */ + | PURPLE_MEDIA_ELEMENT_SINK + | PURPLE_MEDIA_ELEMENT_ONE_SINK, + create_default_audio_sink, /* create */ +}; + +void +pidgin_medias_init(void) +{ + PurpleMediaManager *manager = purple_media_manager_get(); + g_signal_connect(G_OBJECT(manager), "init-media", + G_CALLBACK(pidgin_media_new_cb), NULL); + + purple_debug_info("gtkmedia", "Registering media element types\n"); + purple_media_manager_set_active_element(manager, &default_video_src); + purple_media_manager_set_active_element(manager, &default_video_sink); + purple_media_manager_set_active_element(manager, &default_audio_src); + purple_media_manager_set_active_element(manager, &default_audio_sink); +} + +#endif /* USE_VV */ diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkmedia.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkmedia.h Sun Mar 22 23:33:42 2009 +0000 @@ -0,0 +1,71 @@ +/** + * @file media.h Account API + * @ingroup core + * + * Pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __GTKMEDIA_H_ +#define __GTKMEDIA_H_ + +#ifdef USE_VV + +#include +#include + +#include "connection.h" + +G_BEGIN_DECLS + +#define PIDGIN_TYPE_MEDIA (pidgin_media_get_type()) +#define PIDGIN_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PIDGIN_TYPE_MEDIA, PidginMedia)) +#define PIDGIN_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PIDGIN_TYPE_MEDIA, PidginMediaClass)) +#define PIDGIN_IS_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PIDGIN_TYPE_MEDIA)) +#define PIDGIN_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PIDGIN_TYPE_MEDIA)) +#define PIDGIN_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PIDGIN_TYPE_MEDIA, PidginMediaClass)) + +typedef struct _PidginMedia PidginMedia; +typedef struct _PidginMediaClass PidginMediaClass; +typedef struct _PidginMediaPrivate PidginMediaPrivate; + +struct _PidginMediaClass +{ + GtkWindowClass parent_class; +}; + +struct _PidginMedia +{ + GtkWindow parent; + PidginMediaPrivate *priv; +}; + +GType pidgin_media_get_type(void); + +void pidgin_medias_init(void); + +GtkWidget *pidgin_media_new(PurpleMedia *media, const gchar *screenname); + +G_END_DECLS + +#endif /* USE_VV */ + + +#endif /* __GTKMEDIA_H_ */ diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkprefs.c --- a/pidgin/gtkprefs.c Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkprefs.c Sun Mar 22 23:33:42 2009 +0000 @@ -28,6 +28,9 @@ #include "pidgin.h" #include "debug.h" +#ifdef USE_VV +#include "mediamanager.h" +#endif #include "notify.h" #include "prefs.h" #include "proxy.h" @@ -54,6 +57,10 @@ #include "gtkutils.h" #include "pidginstock.h" +#ifdef USE_VV +#include +#endif + #define PROXYHOST 0 #define PROXYPORT 1 #define PROXYUSER 2 @@ -145,6 +152,26 @@ return pidgin_add_widget_to_vbox(GTK_BOX(page), title, sg, entry, TRUE, NULL); } +GtkWidget * +pidgin_prefs_labeled_password(GtkWidget *page, const gchar *title, + const char *key, GtkSizeGroup *sg) +{ + GtkWidget *entry; + const gchar *value; + + value = purple_prefs_get_string(key); + + entry = gtk_entry_new(); + gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); + gtk_entry_set_text(GTK_ENTRY(entry), value); + g_signal_connect(G_OBJECT(entry), "changed", + G_CALLBACK(entry_set), (char*)key); + gtk_widget_show(entry); + + return pidgin_add_widget_to_vbox(GTK_BOX(page), title, sg, entry, TRUE, NULL); +} + + static void dropdown_set(GObject *w, const char *key) { @@ -156,12 +183,10 @@ if (type == PURPLE_PREF_INT) { int_value = GPOINTER_TO_INT(g_object_get_data(w, "value")); - purple_prefs_set_int(key, int_value); } else if (type == PURPLE_PREF_STRING) { str_value = (const char *)g_object_get_data(w, "value"); - purple_prefs_set_string(key, str_value); } else if (type == PURPLE_PREF_BOOLEAN) { @@ -1210,7 +1235,7 @@ _("Never"), "never", NULL); gtk_size_group_add_widget(sg, label); - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); vbox = pidgin_make_frame(ret, _("Conversation Window Hiding")); label = pidgin_prefs_dropdown(vbox, _("_Hide new IM conversations:"), @@ -1220,7 +1245,7 @@ _("Always"), "always", NULL); gtk_size_group_add_widget(sg, label); - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); /* All the tab options! */ @@ -1255,7 +1280,7 @@ #endif NULL); gtk_size_group_add_widget(sg, label); - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); names = pidgin_conv_placement_get_options(); label = pidgin_prefs_dropdown_from_list(vbox2, _("N_ew conversations:"), @@ -1406,6 +1431,28 @@ purple_network_set_public_ip(gtk_entry_get_text(entry)); } +static gboolean network_stun_server_changed_cb(GtkWidget *widget, + GdkEventFocus *event, gpointer data) +{ + GtkEntry *entry = GTK_ENTRY(widget); + purple_prefs_set_string("/purple/network/stun_server", + gtk_entry_get_text(entry)); + purple_network_set_stun_server(gtk_entry_get_text(entry)); + + return FALSE; +} + +static gboolean network_turn_server_changed_cb(GtkWidget *widget, + GdkEventFocus *event, gpointer data) +{ + GtkEntry *entry = GTK_ENTRY(widget); + purple_prefs_set_string("/purple/network/turn_server", + gtk_entry_get_text(entry)); + purple_network_set_turn_server(gtk_entry_get_text(entry)); + + return FALSE; +} + static void proxy_changed_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data) @@ -1471,8 +1518,16 @@ vbox = pidgin_make_frame (ret, _("IP Address")); sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - pidgin_prefs_labeled_entry(vbox,_("ST_UN server:"), - "/purple/network/stun_server", sg); + + entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(entry), purple_prefs_get_string( + "/purple/network/stun_server")); + g_signal_connect(G_OBJECT(entry), "focus-out-event", + G_CALLBACK(network_stun_server_changed_cb), NULL); + gtk_widget_show(entry); + + pidgin_add_widget_to_vbox(GTK_BOX(vbox), "ST_UN server:", + sg, entry, TRUE, NULL); hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_container_add(GTK_CONTAINER(vbox), hbox); @@ -1550,6 +1605,29 @@ g_signal_connect(G_OBJECT(ports_checkbox), "clicked", G_CALLBACK(pidgin_toggle_sensitive), spin_button); + g_object_unref(sg); + + /* TURN server */ + vbox = pidgin_make_frame(ret, _("Relay Server (TURN)")); + sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(entry), purple_prefs_get_string( + "/purple/network/turn_server")); + g_signal_connect(G_OBJECT(entry), "focus-out-event", + G_CALLBACK(network_turn_server_changed_cb), NULL); + gtk_widget_show(entry); + + hbox = pidgin_add_widget_to_vbox(GTK_BOX(vbox), "_TURN server:", + sg, entry, TRUE, NULL); + + pidgin_prefs_labeled_spin_button(hbox, _("_Port:"), + "/purple/network/turn_port", 0, 65535, NULL); + hbox = pidgin_prefs_labeled_entry(vbox, "_Username:", + "/purple/network/turn_username", sg); + pidgin_prefs_labeled_password(hbox, "_Password:", + "/purple/network/turn_password", NULL); + if (purple_running_gnome()) { vbox = pidgin_make_frame(ret, _("Proxy Server & Browser")); prefs_proxy_frame = gtk_vbox_new(FALSE, 0); @@ -2326,6 +2404,377 @@ return ret; } +#ifdef USE_VV +static GList* +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; +} + +/* get a GList of pairs name / device */ +static GList * +get_device_items(const gchar *plugin) +{ + GList *ret = NULL; + GList *devices = get_devices(plugin); + GstElement *element = gst_element_factory_make(plugin, NULL); + + if (element == NULL) + return NULL; + + for(; devices ; devices = g_list_delete_link(devices, devices)) { + gchar *name; + g_object_set(G_OBJECT(element), "device", devices->data, NULL); + g_object_get(G_OBJECT(element), "device-name", &name, NULL); + ret = g_list_append(ret, name); + ret = g_list_append(ret, devices->data); + } + + gst_object_unref(element); + return ret; +} + +/* + * Test functions to run video preview + */ +static gboolean +preview_video_bus_call(GstBus *bus, GstMessage *msg, gpointer pipeline) +{ + switch(GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + purple_debug_info("preview-video", "End of Stream\n"); + break; + case GST_MESSAGE_ERROR: { + gchar *debug = NULL; + GError *err = NULL; + + gst_message_parse_error(msg, &err, &debug); + + purple_debug_error("preview-video", "Error: %s\n", err->message); + g_error_free(err); + + if (debug) { + purple_debug_error("preview-video", "details: %s\n", debug); + g_free (debug); + } + break; + } + default: + return TRUE; + } + + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(GST_PIPELINE(pipeline)); + return FALSE; +} + +static void +preview_button_clicked(GtkWidget *widget, gpointer *data) +{ + const char *plugin = purple_prefs_get_string("/purple/media/video/plugin"); + const char *device = purple_prefs_get_string("/purple/media/video/device"); + GstBus *bus; + + /* create a preview window... */ + GstElement *pipeline = NULL; + GError *p_err = NULL; + + gchar *test_pipeline_str = NULL; + + if (strlen(device) > 0) + test_pipeline_str = g_strdup_printf("%s device=\"%s\" !" \ + " ffmpegcolorspace !" \ + " autovideosink", + plugin, device); + else + test_pipeline_str = g_strdup_printf("%s ! ffmpegcolorspace !" \ + " autovideosink", plugin); + + pipeline = gst_parse_launch (test_pipeline_str, &p_err); + + g_free(test_pipeline_str); + + if (pipeline == NULL) { + purple_debug_error("gtkprefs", + "Error starting preview: %s\n", p_err->message); + g_error_free(p_err); + return; + } + + bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + gst_bus_add_watch(bus, preview_video_bus_call, pipeline); + gst_object_unref(bus); + + gst_element_set_state(pipeline, GST_STATE_PLAYING); +} + +static void +media_plugin_changed_cb(const gchar *name, PurplePrefType type, + gconstpointer value, gpointer data) +{ + GtkWidget *hbox = data; + GtkWidget *dd = NULL; + GtkWidget *preview_button = NULL; + const char *plugin = value; + const char *device = purple_prefs_get_string("/purple/media/video/device"); + GList *video_items = get_device_items(plugin); + GList *list; + + if (video_items == NULL) { + video_items = g_list_prepend(video_items, g_strdup("")); + video_items = g_list_prepend(video_items, g_strdup("Default")); + } + + if (g_list_find(video_items, device) == NULL) + { + purple_prefs_set_string("/purple/media/video/device", + g_list_next(video_items)->data); + } + + list = gtk_container_get_children(GTK_CONTAINER(hbox)); + + while (list) { + gtk_widget_destroy(list->data); + list = g_list_delete_link(list, list); + } + + dd = pidgin_prefs_dropdown_from_list(hbox, _("_Device:"), PURPLE_PREF_STRING, + "/purple/media/video/device", + video_items); + + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5); + + preview_button = gtk_button_new_with_mnemonic(_("_Preview")); + g_signal_connect(G_OBJECT(preview_button), "clicked", + G_CALLBACK(preview_button_clicked), NULL); + + gtk_container_add(GTK_CONTAINER(hbox), preview_button); + + gtk_widget_show_all(hbox); +} + +static void +prefs_media_input_volume_changed(GtkRange *range) +{ + double val = (double)gtk_range_get_value(GTK_RANGE(range)); + GList *medias = purple_media_manager_get_media(purple_media_manager_get()); + purple_prefs_set_int("/purple/media/audio/volume/input", val); + + val /= 10.0; + for (; medias; medias = g_list_next(medias)) { + PurpleMedia *media = PURPLE_MEDIA(medias->data); + purple_media_set_input_volume(media, NULL, val); + } +} + +static void +prefs_media_output_volume_changed(GtkRange *range) +{ + double val = (double)gtk_range_get_value(GTK_RANGE(range)); + GList *medias = purple_media_manager_get_media(purple_media_manager_get()); + purple_prefs_set_int("/purple/media/audio/volume/output", val); + + val /= 10.0; + for (; medias; medias = g_list_next(medias)) { + PurpleMedia *media = PURPLE_MEDIA(medias->data); + purple_media_set_output_volume(media, NULL, NULL, val); + } +} + +static GtkWidget * +media_page() +{ + GtkWidget *ret; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *dd; + GtkWidget *preview_button; + GtkWidget *sw; + GtkSizeGroup *sg, *sg2; + const char *plugin = purple_prefs_get_string("/purple/media/video/plugin"); + const char *device = purple_prefs_get_string("/purple/media/video/device"); + GList *video_items = get_device_items(plugin); + GList *audio_items = get_device_items("alsasrc"); + + if (video_items == NULL) { + video_items = g_list_prepend(video_items, ""); + video_items = g_list_prepend(video_items, "Default"); + } + + if (g_list_find(video_items, device) == NULL) + { + purple_prefs_set_string("/purple/media/video/device", + g_list_next(video_items)->data); + } + + ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER); + + sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + sg2 = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + vbox = pidgin_make_frame (ret, _("Video Input")); + gtk_size_group_add_widget(sg2, vbox); + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + dd = pidgin_prefs_dropdown(vbox, _("_Plugin:"), PURPLE_PREF_STRING, + "/purple/media/video/plugin", + _("Default"), "gconfvideosrc", + _("Video4Linux"), "v4lsrc", + _("Video4Linux2"), "v4l2src", + _("Video Test Source"), "videotestsrc", + NULL); + + gtk_size_group_add_widget(sg, dd); + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5); + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + dd = pidgin_prefs_dropdown_from_list(hbox, _("Device:"), PURPLE_PREF_STRING, + "/purple/media/video/device", + video_items); + + purple_prefs_connect_callback(prefs, "/purple/media/video/plugin", + media_plugin_changed_cb, hbox); + + g_signal_connect_swapped(hbox, "destroy", + G_CALLBACK(purple_prefs_disconnect_by_handle), hbox); + + gtk_size_group_add_widget(sg, dd); + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5); + + preview_button = gtk_button_new_with_mnemonic(_("_Preview")); + g_signal_connect(G_OBJECT(preview_button), "clicked", + G_CALLBACK(preview_button_clicked), NULL); + + gtk_container_add(GTK_CONTAINER(hbox), preview_button); + gtk_container_add(GTK_CONTAINER(vbox), hbox); + + vbox = pidgin_make_frame (ret, _("Audio Input")); + gtk_size_group_add_widget(sg2, vbox); + dd = pidgin_prefs_dropdown_from_list(vbox, _("Device:"), PURPLE_PREF_STRING, + "/purple/media/audio/device", + audio_items); + + gtk_size_group_add_widget(sg, dd); + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5); + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + /* Input Volume */ + sw = gtk_hscale_new_with_range(0.0, 100.0, 5.0); + gtk_range_set_increments(GTK_RANGE(sw), 5.0, 25.0); + gtk_range_set_value(GTK_RANGE(sw), + purple_prefs_get_int("/purple/media/audio/volume/input")); + g_signal_connect (G_OBJECT (sw), "format-value", + G_CALLBACK (prefs_sound_volume_format), + NULL); + g_signal_connect (G_OBJECT (sw), "value-changed", + G_CALLBACK (prefs_media_input_volume_changed), + NULL); + pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("Volume:"), sg, sw, TRUE, NULL); + + vbox = pidgin_make_frame (ret, _("Audio Output")); + gtk_size_group_add_widget(sg2, vbox); + + /* Output Volume */ + sw = gtk_hscale_new_with_range(0.0, 100.0, 5.0); + gtk_range_set_increments(GTK_RANGE(sw), 5.0, 25.0); + gtk_range_set_value(GTK_RANGE(sw), + purple_prefs_get_int("/purple/media/audio/volume/output")); + g_signal_connect (G_OBJECT (sw), "format-value", + G_CALLBACK (prefs_sound_volume_format), + NULL); + g_signal_connect (G_OBJECT (sw), "value-changed", + G_CALLBACK (prefs_media_output_volume_changed), + NULL); + pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("Volume:"), sg, sw, TRUE, NULL); + + gtk_widget_show_all(ret); + + return ret; +} + +#endif /* USE_VV */ static void set_idle_away(PurpleSavedStatus *status) @@ -2451,6 +2900,10 @@ prefs_notebook_add_page(_("Conversations"), conv_page(), notebook_page++); prefs_notebook_add_page(_("Smiley Themes"), theme_page(), notebook_page++); prefs_notebook_add_page(_("Sounds"), sound_page(), notebook_page++); + +#ifdef USE_VV + prefs_notebook_add_page(_("Media"), media_page(), notebook_page++); +#endif prefs_notebook_add_page(_("Network"), network_page(), notebook_page++); #ifndef _WIN32 /* We use the registered default browser in windows */ @@ -2587,6 +3040,18 @@ purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/smileys/theme", smiley_theme_pref_cb, NULL); +#ifdef USE_VV + purple_prefs_add_none("/purple/media"); + purple_prefs_add_none("/purple/media/video"); + purple_prefs_add_string("/purple/media/video/plugin", "gconfvideosrc"); + purple_prefs_add_string("/purple/media/video/device", ""); + purple_prefs_add_none("/purple/media/audio"); + purple_prefs_add_string("/purple/media/audio/device", ""); + purple_prefs_add_none("/purple/media/audio/volume"); + purple_prefs_add_int("/purple/media/audio/volume/input", 10); + purple_prefs_add_int("/purple/media/audio/volume/output", 10); +#endif /* USE_VV */ + pidgin_prefs_update_old(); } diff -r 399776a9ad98 -r 7b63af454f26 pidgin/gtkprefs.h --- a/pidgin/gtkprefs.h Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/gtkprefs.h Sun Mar 22 23:33:42 2009 +0000 @@ -29,6 +29,7 @@ #include "prefs.h" + /** * Initializes all UI-specific preferences. */ @@ -81,6 +82,22 @@ const char *key, GtkSizeGroup *sg); /** + * Add a new entry representing a password (string) preference + * The entry will use a password-style text entry (the text is substituded) + * + * @param page The page to which the entry will be added + * @param title The text to be displayed as the entry label + * @param key The key of the string pref that will be represented by the entry + * @param sg If not NULL, the size group to which the entry will be added + * + * @return An hbox containing both the label and the entry. Can be used to set + * the widgets to sensitive or insensitive based on the value of a + * checkbox. + */ +GtkWidget *pidgin_prefs_labeled_password(GtkWidget *page, const gchar *title, + const char *key, GtkSizeGroup *sg); + +/** * Add a new dropdown representing a preference of the specified type * * @param page The page to which the dropdown will be added diff -r 399776a9ad98 -r 7b63af454f26 pidgin/pidginstock.c --- a/pidgin/pidginstock.c Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/pidginstock.c Sun Mar 22 23:33:42 2009 +0000 @@ -197,7 +197,12 @@ { PIDGIN_STOCK_TOOLBAR_UNBLOCK, "toolbar", "unblock.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR, "toolbar", "select-avatar.png", FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, NULL }, { 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 } + { 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 }; const SizedStockIcon sized_status_icons [] = { @@ -277,7 +282,6 @@ return ret; } - /* Altered from do_colorshift in gnome-panel */ static void do_alphashift(GdkPixbuf *dest, GdkPixbuf *src) diff -r 399776a9ad98 -r 7b63af454f26 pidgin/pidginstock.h --- a/pidgin/pidginstock.h Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/pidginstock.h Sun Mar 22 23:33:42 2009 +0000 @@ -154,6 +154,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 -r 399776a9ad98 -r 7b63af454f26 pidgin/pixmaps/Makefile.am --- a/pidgin/pixmaps/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/pixmaps/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -429,6 +429,7 @@ toolbar/16/scalable/font-size-up.svg TOOLBAR_16 = \ + toolbar/16/audio-call.png \ toolbar/16/change-bgcolor.png \ toolbar/16/change-fgcolor.png \ toolbar/16/emote-select.png \ @@ -442,7 +443,8 @@ toolbar/16/plugins.png \ toolbar/16/send-file.png \ toolbar/16/transfer.png \ - toolbar/16/unblock.png + toolbar/16/unblock.png \ + toolbar/16/video-call.png TOOLBAR_22_SCALABLE = \ toolbar/22/scalable/select-avatar.svg diff -r 399776a9ad98 -r 7b63af454f26 pidgin/pixmaps/toolbar/16/audio-call.png Binary file pidgin/pixmaps/toolbar/16/audio-call.png has changed diff -r 399776a9ad98 -r 7b63af454f26 pidgin/pixmaps/toolbar/16/video-call.png Binary file pidgin/pixmaps/toolbar/16/video-call.png has changed diff -r 399776a9ad98 -r 7b63af454f26 pidgin/plugins/Makefile.am --- a/pidgin/plugins/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/plugins/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -123,6 +123,7 @@ -I$(top_srcdir)/libpurple \ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GTK_CFLAGS) \ $(PLUGIN_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 pidgin/plugins/cap/Makefile.am --- a/pidgin/plugins/cap/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/plugins/cap/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -15,7 +15,7 @@ endif -cap_la_LIBADD = $(GTK_LIBS) $(SQLITE3_LIBS) +cap_la_LIBADD = $(GTK_LIBS) $(SQLITE3_LIBS) $(FARSIGHT_LIBS) $(GSTPROPS_LIBS) AM_CPPFLAGS = \ -DDATADIR=\"$(datadir)\" \ @@ -24,6 +24,8 @@ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ $(GTK_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTPROPS_CFLAGS) \ $(SQLITE3_CFLAGS) EXTRA_DIST = Makefile.mingw diff -r 399776a9ad98 -r 7b63af454f26 pidgin/plugins/gestures/Makefile.am --- a/pidgin/plugins/gestures/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/plugins/gestures/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -23,4 +23,5 @@ -I$(top_builddir)/libpurple \ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GTK_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 pidgin/plugins/gevolution/Makefile.am --- a/pidgin/plugins/gevolution/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/plugins/gevolution/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -26,4 +26,5 @@ -I$(top_srcdir)/pidgin \ $(EVOLUTION_ADDRESSBOOK_CFLAGS) \ $(DEBUG_CFLAGS) \ - $(GTK_CFLAGS) + $(GTK_CFLAGS) \ + $(FARSIGHT_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 pidgin/plugins/musicmessaging/Makefile.am --- a/pidgin/plugins/musicmessaging/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/plugins/musicmessaging/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -40,5 +40,6 @@ -I$(top_srcdir)/libpurple \ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GTK_CFLAGS) \ $(DBUS_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 pidgin/plugins/ticker/Makefile.am --- a/pidgin/plugins/ticker/Makefile.am Sat Mar 21 02:20:52 2009 +0000 +++ b/pidgin/plugins/ticker/Makefile.am Sun Mar 22 23:33:42 2009 +0000 @@ -24,4 +24,5 @@ -I$(top_builddir)/libpurple \ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GTK_CFLAGS) diff -r 399776a9ad98 -r 7b63af454f26 po/POTFILES.in --- a/po/POTFILES.in Sat Mar 21 02:20:52 2009 +0000 +++ b/po/POTFILES.in Sun Mar 22 23:33:42 2009 +0000 @@ -8,6 +8,7 @@ finch/gntdebug.c finch/gntft.c finch/gntlog.c +finch/gntmedia.c finch/gntnotify.c finch/gntplugin.c finch/gntpounce.c @@ -86,6 +87,7 @@ libpurple/protocols/jabber/buddy.c libpurple/protocols/jabber/chat.c libpurple/protocols/jabber/jabber.c +libpurple/protocols/jabber/jingle.c libpurple/protocols/jabber/libxmpp.c libpurple/protocols/jabber/message.c libpurple/protocols/jabber/parser.c