# HG changeset patch # User Paul Aurich # Date 1240186251 0 # Node ID 872d30754311ccfb53a3ce802c9914d2696513d5 # Parent 657ccc44e7633f5287c057012e193a531f68c10e# Parent 78ef235513558d3910b3e2f41d15065a2576f76e propagate from branch 'im.pidgin.pidgin' (head 1ae2b55502a0afd8f28918fc4726683c52e998e9) to branch 'im.pidgin.cpw.darkrain42.docs' (head 3dd6aa6f6c394f0be53f01c2decd3f15ff229ff5) diff -r 78ef23551355 -r 872d30754311 COPYRIGHT --- a/COPYRIGHT Mon Apr 20 00:05:54 2009 +0000 +++ b/COPYRIGHT Mon Apr 20 00:10:51 2009 +0000 @@ -19,6 +19,7 @@ Christopher Ayoup Alex Badea John Bailey +Arunan Balasubramaniam R. Tyler Ballance Chris Banal Luca Barbato @@ -29,6 +30,7 @@ Derek Battams Martin Bayard Curtis Beattie +Carlos Bederian Dave Bell Igor Belyi Brian Bernas @@ -332,10 +334,9 @@ Joao Luís Marques Pinto Aleksander Piotrowski Julien Pivotto +Robey Pointer Eric Polino Ari Pollak -Robey Pointer -Eric Polino Stephen Pope Nathan Poznick Jory A. Pratt diff -r 78ef23551355 -r 872d30754311 ChangeLog --- a/ChangeLog Mon Apr 20 00:05:54 2009 +0000 +++ b/ChangeLog Mon Apr 20 00:10:51 2009 +0000 @@ -8,15 +8,27 @@ in a group on the buddy list. * Removed the unmaintained and unneeded toc protocol plugin. * Fixed NTLM authentication on big-endian systems. + * Dragging a buddy onto a chat pops up a chat-invitation dialog. + (Carlos Bederian) + + libpurple: + * Various memory cleanups when unloading libpurple. (Nick Hebner) XMPP: + * Add voice & video support with Jingle (XEP-0166, 0167, 0176, & 0177), + and voice support with GTalk and GMail. (Mike "Maiku" Ruprecht) * Add support for in-band bytestreams for file transfers (XEP-0047). - * Add support for sending attentions (equivalent to "buzz" and "nudge") - using the command /buzz (XEP-0224). + * Add support for sending and receiving attentions (equivalent to "buzz" + and "nudge") using the command /buzz (XEP-0224). + * A buddy's local time is displayed in the Get Info dialog if the remote + client supports it. IRC: * Correctly handle WHOIS for users who are joined to a large number of channels. + * Notify the user if a /nick command fails, rather than trying + fallback nicks. + Pidgin: * Added -f command line option to tell Pidgin to ignore NetworkManager @@ -33,6 +45,11 @@ * The New Account dialog is now broken into three tabs. Proxy configuration has been moved from the Advanced tab to the new tab. + Finch: + * The hardware cursor is updated correctly. This will be useful + especially for users of braille terminals, screen readers etc. + * Added a TinyURL plugin, which aids copying longer URLs. + version 2.5.5 (03/01/2009): libpurple: * Fix a crash when removing an account with an unknown protocol id. diff -r 78ef23551355 -r 872d30754311 ChangeLog.API --- a/ChangeLog.API Mon Apr 20 00:05:54 2009 +0000 +++ b/ChangeLog.API Mon Apr 20 00:10:51 2009 +0000 @@ -3,11 +3,13 @@ version 2.6.0 (??/??/2009): libpurple: Added: + * PurpleMedia and PurpleMediaManager API * PURPLE_BLIST_NODE * PURPLE_GROUP * PURPLE_CONTACT * PURPLE_BUDDY * PURPLE_CHAT + * purple_buddy_destroy * purple_buddy_get_protocol_data * purple_buddy_set_protocol_data * purple_buddy_get_local_buddy_alias @@ -16,16 +18,27 @@ * purple_blist_set_ui_data * purple_blist_node_get_ui_data * purple_blist_node_set_ui_data + * purple_chat_destroy * purple_connection_get_protocol_data * purple_connection_set_protocol_data + * purple_contact_destroy + * purple_conv_chat_invite_user * purple_global_proxy_set_info + * purple_group_destroy * purple_log_get_activity_score * purple_network_force_online + * purple_network_set_stun_server + * purple_network_set_turn_server + * purple_network_get_stun_ip + * purple_network_get_turn_ip + * purple_prpl_get_media_caps + * purple_prpl_initiate_media * purple_request_field_get_group * purple_request_field_get_ui_data * purple_request_field_set_ui_data * purple_strequal * xmlnode_from_file + * xmlnode_get_parent * xmlnode_set_attrib_full Changed: @@ -60,10 +73,15 @@ * gtk_imhtml_set_return_inserts_newline * pidgin_blist_set_theme * pidgin_blist_get_theme + * pidgin_prefs_labeled_password * pidgin_sound_is_customized * pidgin_utils_init, pidgin_utils_uninit * pidgin_notify_pounce_add + libgnt: + Added: + * GntProgressBar and functions (Saleem Abdulrasool) + perl: Changed: * Made a bunch of functions act more perl-like. Call the new() diff -r 78ef23551355 -r 872d30754311 Doxyfile.in diff -r 78ef23551355 -r 872d30754311 configure.ac --- a/configure.ac Mon Apr 20 00:05:54 2009 +0000 +++ b/configure.ac Mon Apr 20 00:10:51 2009 +0000 @@ -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)]), @@ -703,9 +706,13 @@ PKG_CHECK_MODULES(LIBXML, [libxml-2.0 >= 2.6.0], , [ AC_MSG_RESULT(no) AC_MSG_ERROR([ - You must have libxml2 >= 2.6.0 development headers installed to build. ])]) +PKG_CHECK_EXISTS([libxml-2.0 >= 2.6.18], , [ + AC_MSG_WARN([ +Versions of libxml2 < 2.6.18 may contain bugs that could cause XMPP messages to be discarded. +])]) + AC_SUBST(LIBXML_CFLAGS) AC_SUBST(LIBXML_LIBS) @@ -745,6 +752,63 @@ fi dnl ####################################################################### +dnl # Check for GStreamer Interfaces +dnl ####################################################################### +if test "x$enable_gst" != "xno"; then + AC_ARG_ENABLE(gstreamer-interfaces, + [AC_HELP_STRING([--disable-gstreamer-interfaces], [compile without GStreamer interface support])], + enable_gstinterfaces="$enableval", enable_gstinterfaces="yes") + if test "x$enable_gstinterfaces" != "xno"; then + PKG_CHECK_MODULES(GSTINTERFACES, [gstreamer-interfaces-0.10], [ + AC_DEFINE(USE_GSTINTERFACES, 1, [Use GStreamer interfaces for X overlay support]) + AC_SUBST(GSTINTERFACES_CFLAGS) + AC_SUBST(GSTINTERFACES_LIBS) + ], [ + enable_gstinterfaces="no" + ]) + fi +else + enable_gstinterfaces="no" +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.9], [ + 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 Voice and Video support +dnl ####################################################################### +AC_ARG_ENABLE(vv, + [AC_HELP_STRING([--disable-vv], [compile without voice and video support])], + [enable_vv="$enableval" force_vv=$enableval], [enable_vv="yes" force_vv=no]) +if test "x$enable_vv" != "xno"; then + if test "x$enable_gstreamer" != "xno" -a "x$enable_gstinterfaces" != "xno" -a "x$enable_farsight" != "xno"; then + AC_DEFINE(USE_VV, 1, [Use voice and video]) + else + enable_vv="no" + if test "x$force_vv" = "xyes"; then + AC_MSG_ERROR([ +Dependencies for voice/video were not met. +Install the necessary gstreamer and farsight packages first. +Or use --disable-vv if you do not need voice/video support. + ]) + fi + fi +fi + +dnl ####################################################################### dnl # Check for Meanwhile headers (for Sametime) dnl ####################################################################### AC_ARG_ENABLE(meanwhile, @@ -922,7 +986,7 @@ gadu_includes="yes" gadu_libs="yes" ], [ - AC_MSG_RESULT(no) + gadu_includes="no" ]) else if test "$ac_gadu_includes" != "no"; then @@ -2478,6 +2542,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 78ef23551355 -r 872d30754311 doc/TCL-HOWTO.dox --- a/doc/TCL-HOWTO.dox Mon Apr 20 00:05:54 2009 +0000 +++ b/doc/TCL-HOWTO.dox Mon Apr 20 00:10:51 2009 +0000 @@ -173,6 +173,7 @@ purple::connection displayname gc purple::connection handle purple::connection list +purple::connection state @endcode @c purple::connection is a collection of subcommands pertaining to @@ -192,6 +193,9 @@ this list are appropriate as @c gc arguments to the other @c purple::connection subcommands or other commands requiring a gc. + @c state returns the PurpleConnectionState of this account as one of + the strings "connected", "disconnected", or "connecting". + @code purple::conv_send account who text @endcode diff -r 78ef23551355 -r 872d30754311 doc/finch.1.in --- a/doc/finch.1.in Mon Apr 20 00:05:54 2009 +0000 +++ b/doc/finch.1.in Mon Apr 20 00:10:51 2009 +0000 @@ -59,7 +59,7 @@ Display the version information window. .SH GNT Shortcuts -You can use the following shortcuts: +You can use the following shortcuts (see the "\*QWidget Actions\*U" section for a more complete list): .TP .B Alt \+ a Bring up a list of available actions. You can use this list to access the @@ -378,6 +378,8 @@ [GntWidget::binding] .br f11 = context-menu +.br +c-x = context-menu [GntWindow::binding] .br diff -r 78ef23551355 -r 872d30754311 finch/Makefile.am --- a/finch/Makefile.am Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/Makefile.am Mon Apr 20 00:10:51 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 \ diff -r 78ef23551355 -r 872d30754311 finch/gntaccount.c --- a/finch/gntaccount.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/gntaccount.c Mon Apr 20 00:10:51 2009 +0000 @@ -669,8 +669,13 @@ account_toggled(GntWidget *widget, void *key, gpointer null) { PurpleAccount *account = key; + gboolean enabled = gnt_tree_get_choice(GNT_TREE(widget), key); - purple_account_set_enabled(account, FINCH_UI, gnt_tree_get_choice(GNT_TREE(widget), key)); + if (enabled) + purple_savedstatus_activate_for_account(purple_savedstatus_get_current(), + account); + + purple_account_set_enabled(account, FINCH_UI, enabled); } static gboolean diff -r 78ef23551355 -r 872d30754311 finch/gntblist.c --- a/finch/gntblist.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/gntblist.c Mon Apr 20 00:10:51 2009 +0000 @@ -61,7 +61,7 @@ #include #define PREF_ROOT "/finch/blist" -#define TYPING_TIMEOUT 4000 +#define TYPING_TIMEOUT_S 4 #define SHOW_EMPTY_GROUP_TIMEOUT 60 @@ -2016,7 +2016,7 @@ } if (ggblist->typing) - g_source_remove(ggblist->typing); + purple_timeout_remove(ggblist->typing); remove_peripherals(ggblist); if (ggblist->tagged) g_list_free(ggblist->tagged); @@ -2253,7 +2253,7 @@ end: g_free(escnewmessage); if (ggblist->typing) - g_source_remove(ggblist->typing); + purple_timeout_remove(ggblist->typing); ggblist->typing = 0; return FALSE; } @@ -2272,7 +2272,7 @@ /* Move the focus to the entry box */ /* XXX: Make sure the selected status can have a message */ gnt_box_move_focus(GNT_BOX(ggblist->window), 1); - ggblist->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, NULL); + ggblist->typing = purple_timeout_add_seconds(TYPING_TIMEOUT_S, (GSourceFunc)remove_typing_cb, NULL); } else if (now->type == STATUS_SAVED_ALL) { @@ -2298,7 +2298,7 @@ return FALSE; if (ggblist->typing) - g_source_remove(ggblist->typing); + purple_timeout_remove(ggblist->typing); ggblist->typing = 0; if (text[0] == '\r' && text[1] == 0) @@ -2308,7 +2308,7 @@ return TRUE; } - ggblist->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, NULL); + ggblist->typing = purple_timeout_add_seconds(TYPING_TIMEOUT_S, (GSourceFunc)remove_typing_cb, NULL); return FALSE; } diff -r 78ef23551355 -r 872d30754311 finch/gntconv.c --- a/finch/gntconv.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/gntconv.c Mon Apr 20 00:10:51 2009 +0000 @@ -559,44 +559,11 @@ } static void -invite_select_cb(FinchConv *fc, PurpleRequestFields *fields) -{ - PurpleConversation *conv = fc->active_conv; - const char *buddy = purple_request_fields_get_string(fields, "screenname"); - const char *message = purple_request_fields_get_string(fields, "message"); - serv_chat_invite(purple_conversation_get_gc(conv), - purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), - message, buddy); - -} - -static void invite_cb(GntMenuItem *item, gpointer ggconv) { - PurpleRequestFields *fields; - PurpleRequestFieldGroup *group; - PurpleRequestField *field; - - fields = purple_request_fields_new(); - - group = purple_request_field_group_new(NULL); - purple_request_fields_add_group(fields, group); - - field = purple_request_field_string_new("screenname", _("Name"), NULL, FALSE); - purple_request_field_set_type_hint(field, "screenname"); - purple_request_field_set_required(field, TRUE); - purple_request_field_group_add_field(group, field); - field = purple_request_field_string_new("message", _("Invite message"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - purple_request_fields(finch_conv_get_handle(), _("Invite"), - NULL, - _("Please enter the name of the user " - "you wish to invite,\nalong with an optional invite message."), - fields, - _("OK"), G_CALLBACK(invite_select_cb), - _("Cancel"), NULL, - NULL, NULL, NULL, - ggconv); + FinchConv *fc = ggconv; + PurpleConversation *conv = fc->active_conv; + purple_conv_chat_invite_user(PURPLE_CONV_CHAT(conv), NULL, NULL, TRUE); } static void diff -r 78ef23551355 -r 872d30754311 finch/gntdebug.c diff -r 78ef23551355 -r 872d30754311 finch/gntft.c diff -r 78ef23551355 -r 872d30754311 finch/gntmedia.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntmedia.c Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,537 @@ +/** + * @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 "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" +#include "mediamanager.h" + +/* An incredibly large part of the following is from gtkmedia.c */ +#ifdef USE_VV +#include "media-gst.h" + +#undef hangup + +#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; +}; + +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, +}; + +static 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_connected_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_connected_cb(media, gntmedia); + } else if (state == PURPLE_MEDIA_STATE_NEW && + sid != NULL && name != NULL && + purple_media_is_initiator(media, sid, name) == FALSE) { + PurpleAccount *account; + PurpleBuddy *buddy; + const gchar *alias; + PurpleMediaSessionType type = + purple_media_get_session_type(media, sid); + gchar *message = NULL; + + account = purple_media_get_account(gntmedia->priv->media); + buddy = purple_find_buddy(account, name); + alias = buddy ? purple_buddy_get_contact_alias(buddy) : name; + + if (type & PURPLE_MEDIA_AUDIO) { + message = g_strdup_printf( + _("%s wishes to start an audio session with you."), + alias); + } else { + message = g_strdup_printf( + _("%s is trying to start an unsupported media session type with you."), + alias); + } + finch_media_emit_message(gntmedia, message); + g_free(message); + } +} + +static void +finch_media_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, + gchar *sid, gchar *name, gboolean local, FinchMedia *gntmedia) +{ + if (type == PURPLE_MEDIA_INFO_REJECT) { + finch_media_emit_message(gntmedia, + _("You have rejected the call.")); + } +} + +static void +finch_media_accept_cb(PurpleMedia *media, GntWidget *widget) +{ + purple_media_stream_info(media, PURPLE_MEDIA_INFO_ACCEPT, + NULL, NULL, TRUE); +} + +static void +finch_media_hangup_cb(PurpleMedia *media, GntWidget *widget) +{ + purple_media_stream_info(media, PURPLE_MEDIA_INFO_HANGUP, + NULL, NULL, TRUE); +} + +static void +finch_media_reject_cb(PurpleMedia *media, GntWidget *widget) +{ + purple_media_stream_info(media, PURPLE_MEDIA_INFO_REJECT, + NULL, NULL, TRUE); +} + +static void +finch_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + FinchMedia *media; + g_return_if_fail(FINCH_IS_MEDIA(object)); + + media = FINCH_MEDIA(object); + switch (prop_id) { + case PROP_MEDIA: + { + if (media->priv->media) + g_object_unref(media->priv->media); + media->priv->media = g_value_get_object(value); + g_object_ref(media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->accept), "activate", + G_CALLBACK(finch_media_accept_cb), media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->reject), "activate", + G_CALLBACK(finch_media_reject_cb), media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->hangup), "activate", + G_CALLBACK(finch_media_hangup_cb), media->priv->media); + + if (purple_media_is_initiator(media->priv->media, + NULL, NULL) == 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; + } +} + +static 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, + PurpleAccount *account, gchar *name, gpointer null) +{ + GntWidget *gntmedia; + PurpleConversation *conv; + + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, 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); + + if (!purple_prpl_initiate_media(account, + purple_conversation_get_name(conv), + PURPLE_MEDIA_AUDIO)) + return PURPLE_CMD_STATUS_FAILED; + + return PURPLE_CMD_STATUS_OK; +} + +static GstElement * +create_default_audio_src(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ + GstElement *bin, *src, *volume; + GstPad *pad, *ghost; + double input_volume = purple_prefs_get_int( + "/finch/media/audio/volume/input")/10.0; + + src = gst_element_factory_make("gconfaudiosrc", NULL); + if (src == NULL) + src = gst_element_factory_make("autoaudiosrc", NULL); + if (src == NULL) + src = gst_element_factory_make("alsasrc", NULL); + if (src == NULL) + src = gst_element_factory_make("osssrc", NULL); + if (src == NULL) + src = gst_element_factory_make("dshowaudiosrc", NULL); + if (src == NULL) { + purple_debug_error("gntmedia", "Unable to find a suitable " + "element for the default audio source.\n"); + return NULL; + } + + bin = gst_bin_new("finchdefaultaudiosrc"); + 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); + + return bin; +} + +static GstElement * +create_default_audio_sink(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ + GstElement *bin, *sink, *volume, *queue; + GstPad *pad, *ghost; + double output_volume = purple_prefs_get_int( + "/finch/media/audio/volume/output")/10.0; + + sink = gst_element_factory_make("gconfaudiosink", NULL); + if (sink == NULL) + sink = gst_element_factory_make("autoaudiosink",NULL); + if (sink == NULL) { + purple_debug_error("gntmedia", "Unable to find a suitable " + "element for the default audio sink.\n"); + return NULL; + } + + bin = gst_bin_new("finchdefaultaudiosink"); + 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; +} +#endif /* USE_VV */ + +void finch_media_manager_init(void) +{ +#ifdef USE_VV + PurpleMediaManager *manager = purple_media_manager_get(); + PurpleMediaElementInfo *default_audio_src = + g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, + "id", "finchdefaultaudiosrc", + "name", "Finch Default Audio Source", + "type", PURPLE_MEDIA_ELEMENT_AUDIO + | PURPLE_MEDIA_ELEMENT_SRC + | PURPLE_MEDIA_ELEMENT_ONE_SRC + | PURPLE_MEDIA_ELEMENT_UNIQUE, + "create-cb", create_default_audio_src, NULL); + PurpleMediaElementInfo *default_audio_sink = + g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, + "id", "finchdefaultaudiosink", + "name", "Finch Default Audio Sink", + "type", PURPLE_MEDIA_ELEMENT_AUDIO + | PURPLE_MEDIA_ELEMENT_SINK + | PURPLE_MEDIA_ELEMENT_ONE_SINK, + "create-cb", create_default_audio_sink, NULL); + + 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_media_manager_set_ui_caps(manager, + PURPLE_MEDIA_CAPS_AUDIO | + PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION); + + 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); + + purple_prefs_add_none("/finch/media"); + purple_prefs_add_none("/finch/media/audio"); + purple_prefs_add_none("/finch/media/audio/volume"); + purple_prefs_add_int("/finch/media/audio/volume/input", 10); + purple_prefs_add_int("/finch/media/audio/volume/output", 10); +#endif +} + +void finch_media_manager_uninit(void) +{ +#ifdef USE_VV + PurpleMediaManager *manager = purple_media_manager_get(); + g_signal_handlers_disconnect_by_func(G_OBJECT(manager), + G_CALLBACK(finch_new_media), NULL); +#endif +} + + diff -r 78ef23551355 -r 872d30754311 finch/gntmedia.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntmedia.h Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,42 @@ +/** + * @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 + +G_BEGIN_DECLS + +void finch_media_manager_init(void); +void finch_media_manager_uninit(void); + +G_END_DECLS + +#endif /* GNT_MEDIA_H */ + diff -r 78ef23551355 -r 872d30754311 finch/gntnotify.c diff -r 78ef23551355 -r 872d30754311 finch/gntplugin.c diff -r 78ef23551355 -r 872d30754311 finch/gntpounce.c diff -r 78ef23551355 -r 872d30754311 finch/gntrequest.c diff -r 78ef23551355 -r 872d30754311 finch/gntstatus.c diff -r 78ef23551355 -r 872d30754311 finch/gntui.c --- a/finch/gntui.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/gntui.c Mon Apr 20 00:10:51 2009 +0000 @@ -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" @@ -91,6 +92,9 @@ finch_roomlist_init(); purple_roomlist_set_ui_ops(finch_roomlist_get_ui_ops()); + /* Media */ + finch_media_manager_init(); + 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 +140,10 @@ finch_roomlist_uninit(); purple_roomlist_set_ui_ops(NULL); +#ifdef USE_VV + finch_media_manager_uninit(); +#endif + gnt_quit(); #endif } diff -r 78ef23551355 -r 872d30754311 finch/libgnt/Makefile.am --- a/finch/libgnt/Makefile.am Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/Makefile.am Mon Apr 20 00:10:51 2009 +0000 @@ -28,6 +28,7 @@ gntmenu.c \ gntmenuitem.c \ gntmenuitemcheck.c \ + gntprogressbar.c \ gntslider.c \ gntstyle.c \ gnttextview.c \ @@ -56,6 +57,7 @@ gntmenu.h \ gntmenuitem.h \ gntmenuitemcheck.h \ + gntprogressbar.h \ gntslider.h \ gntstyle.h \ gnttextview.h \ diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gnt.h --- a/finch/libgnt/gnt.h Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/gnt.h Mon Apr 20 00:10:51 2009 +0000 @@ -48,6 +48,10 @@ #define G_PARAM_STATIC_BLURB G_PARAM_PRIVATE #endif +#if !GLIB_CHECK_VERSION(2,14,0) + #define g_timeout_add_seconds(time, callback, data) g_timeout_add(time * 1000, callback, data) +#endif + /** * Initialize GNT. */ diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gntbox.c --- a/finch/libgnt/gntbox.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/gntbox.c Mon Apr 20 00:10:51 2009 +0000 @@ -78,13 +78,11 @@ g_list_foreach(box->list, (GFunc)gnt_widget_draw, NULL); - gnt_box_sync_children(box); - if (box->title && !GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER)) { int pos, right; char *title = g_strdup(box->title); - + get_title_thingies(box, title, &pos, &right); if (gnt_widget_has_focus(widget)) @@ -96,8 +94,8 @@ mvwaddch(widget->window, 0, right, ACS_LTEE | gnt_color_pair(GNT_COLOR_NORMAL)); g_free(title); } - - GNTDEBUG; + + gnt_box_sync_children(box); } static void @@ -723,6 +721,9 @@ if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER)) pos = 0; + if (!box->active) + find_focusable_widget(box); + for (iter = box->list; iter; iter = iter->next) { GntWidget *w = GNT_WIDGET(iter->data); @@ -764,6 +765,9 @@ copywin(w->window, widget->window, 0, 0, y, x, y + height - 1, x + width - 1, FALSE); gnt_widget_set_position(w, x + widget->priv.x, y + widget->priv.y); + if (w == box->active) { + wmove(widget->window, y + getcury(w->window), x + getcurx(w->window)); + } } } diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gntcheckbox.c --- a/finch/libgnt/gntcheckbox.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/gntcheckbox.c Mon Apr 20 00:10:51 2009 +0000 @@ -42,7 +42,7 @@ type = GNT_COLOR_HIGHLIGHT; else type = GNT_COLOR_NORMAL; - + wbkgdset(widget->window, '\0' | gnt_color_pair(type)); text = g_strdup_printf("[%c]", cb->checked ? 'X' : ' '); @@ -51,7 +51,8 @@ wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_NORMAL)); mvwaddstr(widget->window, 0, 4, GNT_BUTTON(cb)->priv->text); - + wmove(widget->window, 0, 1); + GNTDEBUG; } diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gntcombobox.c --- a/finch/libgnt/gntcombobox.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/gntcombobox.c Mon Apr 20 00:10:51 2009 +0000 @@ -73,7 +73,7 @@ char *text = NULL, *s; GntColorType type; int len; - + if (box->dropdown && box->selected) text = gnt_tree_get_selection_text(GNT_TREE(box->dropdown)); @@ -94,6 +94,7 @@ whline(widget->window, ' ' | gnt_color_pair(type), widget->priv.width - 4 - len); mvwaddch(widget->window, 1, widget->priv.width - 3, ACS_VLINE | gnt_color_pair(GNT_COLOR_NORMAL)); mvwaddch(widget->window, 1, widget->priv.width - 2, ACS_DARROW | gnt_color_pair(GNT_COLOR_NORMAL)); + wmove(widget->window, 1, 1); g_free(text); GNTDEBUG; diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gntentry.c --- a/finch/libgnt/gntentry.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/gntentry.c Mon Apr 20 00:10:51 2009 +0000 @@ -271,6 +271,7 @@ GntEntry *entry = GNT_ENTRY(widget); int stop; gboolean focus; + int curpos; if ((focus = gnt_widget_has_focus(widget))) wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_TEXT_NORMAL)); @@ -289,9 +290,10 @@ if (stop < widget->priv.width) mvwhline(widget->window, 0, stop, ENTRY_CHAR, widget->priv.width - stop); + curpos = gnt_util_onscreen_width(entry->scroll, entry->cursor); if (focus) - mvwchgat(widget->window, 0, gnt_util_onscreen_width(entry->scroll, entry->cursor), - 1, A_REVERSE, GNT_COLOR_TEXT_NORMAL, NULL); + mvwchgat(widget->window, 0, curpos, 1, A_REVERSE, GNT_COLOR_TEXT_NORMAL, NULL); + wmove(widget->window, 0, curpos); GNTDEBUG; } diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gntkeys.h --- a/finch/libgnt/gntkeys.h Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/gntkeys.h Mon Apr 20 00:10:51 2009 +0000 @@ -165,5 +165,6 @@ #undef lines #undef buttons #undef newline +#undef set_clock #endif diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gntprogressbar.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/gntprogressbar.c Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,253 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT 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 library 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 "gntprogressbar.h" +#include "gntutils.h" + +#include + +typedef struct _GntProgressBarPrivate +{ + gdouble fraction; + gboolean show_value; + GntProgressBarOrientation orientation; +} GntProgressBarPrivate; + +struct _GntProgressBar +{ + GntWidget parent; +#if !GLIB_CHECK_VERSION(2,4,0) + GntProgressBarPrivate priv; +#endif +}; + +#if GLIB_CHECK_VERSION(2,4,0) +#define GNT_PROGRESS_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNT_TYPE_PROGRESS_BAR, GntProgressBarPrivate)) +#else +#define GNT_PROGRESS_BAR_GET_PRIVATE(o) &(GNT_PROGRESS_BAR(o)->priv) +#endif + +static GntWidgetClass *parent_class = NULL; + + +static void +gnt_progress_bar_draw (GntWidget *widget) +{ + GntProgressBarPrivate *priv = GNT_PROGRESS_BAR_GET_PRIVATE (GNT_PROGRESS_BAR (widget)); + gchar progress[8]; + gint start, end, i, pos; + int color; + + g_snprintf (progress, sizeof (progress), "%.1f%%", priv->fraction * 100); + color = gnt_color_pair(GNT_COLOR_NORMAL); + + switch (priv->orientation) { + case GNT_PROGRESS_LEFT_TO_RIGHT: + case GNT_PROGRESS_RIGHT_TO_LEFT: + start = (priv->orientation == GNT_PROGRESS_LEFT_TO_RIGHT ? 0 : (1.0 - priv->fraction) * widget->priv.width); + end = (priv->orientation == GNT_PROGRESS_LEFT_TO_RIGHT ? widget->priv.width * priv->fraction : widget->priv.width); + + /* background */ + for (i = 0; i < widget->priv.height; i++) + mvwhline (widget->window, i, 0, ' ' | color, widget->priv.width); + + /* foreground */ + for (i = 0; i < widget->priv.height; i++) + mvwhline (widget->window, i, start, ACS_CKBOARD | color | A_REVERSE, end); + + /* text */ + if (priv->show_value) { + pos = widget->priv.width / 2 - strlen (progress) / 2; + for (i = 0; i < progress[i]; i++, pos++) { + wattrset (widget->window, color | ((pos < start || pos > end) ? A_NORMAL : A_REVERSE)); + mvwprintw (widget->window, widget->priv.height / 2, pos, "%c", progress[i]); + } + wattrset (widget->window, color); + } + + break; + case GNT_PROGRESS_TOP_TO_BOTTOM: + case GNT_PROGRESS_BOTTOM_TO_TOP: + start = (priv->orientation == GNT_PROGRESS_TOP_TO_BOTTOM ? 0 : (1.0 - priv->fraction) * widget->priv.height); + end = (priv->orientation == GNT_PROGRESS_TOP_TO_BOTTOM ? widget->priv.height * priv->fraction : widget->priv.height); + + /* background */ + for (i = 0; i < widget->priv.width; i++) + mvwvline (widget->window, 0, i, ' ' | color, widget->priv.height); + + /* foreground */ + for (i = 0; i < widget->priv.width; i++) + mvwvline (widget->window, start, i, ACS_CKBOARD | color | A_REVERSE, end); + + /* text */ + if (priv->show_value) { + pos = widget->priv.height / 2 - strlen (progress) / 2; + for (i = 0; i < progress[i]; i++, pos++) { + wattrset (widget->window, color | ((pos < start || pos > end) ? A_NORMAL : A_REVERSE)); + mvwprintw (widget->window, pos, widget->priv.width / 2, "%c\n", progress[i]); + } + wattrset (widget->window, color); + } + + break; + default: + g_assert_not_reached (); + } +} + +static void +gnt_progress_bar_size_request (GntWidget *widget) +{ + gnt_widget_set_size (widget, widget->priv.minw, widget->priv.minh); +} + +static void +gnt_progress_bar_class_init (gpointer klass, gpointer class_data) +{ + GObjectClass *g_class = G_OBJECT_CLASS (klass); + + parent_class = GNT_WIDGET_CLASS (klass); + +#if GLIB_CHECK_VERSION(2,4,0) + g_type_class_add_private (g_class, sizeof (GntProgressBarPrivate)); +#endif + + parent_class->draw = gnt_progress_bar_draw; + parent_class->size_request = gnt_progress_bar_size_request; +} + +static void +gnt_progress_bar_init (GTypeInstance *instance, gpointer g_class) +{ + GntWidget *widget = GNT_WIDGET (instance); + GntProgressBarPrivate *priv = GNT_PROGRESS_BAR_GET_PRIVATE (GNT_PROGRESS_BAR (widget)); + + gnt_widget_set_take_focus (widget, FALSE); + GNT_WIDGET_SET_FLAGS (widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW | GNT_WIDGET_GROW_X); + + widget->priv.minw = 8; + widget->priv.minh = 1; + + priv->show_value = TRUE; +} + +GType +gnt_progress_bar_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof (GntProgressBarClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + gnt_progress_bar_class_init, /* class_init */ + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GntProgressBar), + 0, /* n_preallocs */ + gnt_progress_bar_init, /* instance_init */ + NULL /* value_table */ + }; + + type = g_type_register_static (GNT_TYPE_WIDGET, "GntProgressBar", &info, 0); + } + + return type; +} + +GntWidget * +gnt_progress_bar_new (void) +{ + GntWidget *widget = g_object_new (GNT_TYPE_PROGRESS_BAR, NULL); + return widget; +} + +void +gnt_progress_bar_set_fraction (GntProgressBar *pbar, gdouble fraction) +{ + GntProgressBarPrivate *priv = GNT_PROGRESS_BAR_GET_PRIVATE (pbar); + + if (fraction > 1.0) + priv->fraction = 1.0; + else if (fraction < 0.0) + priv->fraction = 0.0; + else + priv->fraction = fraction; + + if ((GNT_WIDGET_FLAGS(pbar) & GNT_WIDGET_MAPPED)) + gnt_widget_draw(GNT_WIDGET(pbar)); +} + +void +gnt_progress_bar_set_orientation (GntProgressBar *pbar, + GntProgressBarOrientation orientation) +{ + GntProgressBarPrivate *priv = GNT_PROGRESS_BAR_GET_PRIVATE (pbar); + GntWidget *widget = GNT_WIDGET(pbar); + + priv->orientation = orientation; + if (orientation == GNT_PROGRESS_LEFT_TO_RIGHT || + orientation == GNT_PROGRESS_RIGHT_TO_LEFT) { + GNT_WIDGET_SET_FLAGS(pbar, GNT_WIDGET_GROW_X); + GNT_WIDGET_UNSET_FLAGS(pbar, GNT_WIDGET_GROW_Y); + widget->priv.minw = 8; + widget->priv.minh = 1; + } else { + GNT_WIDGET_UNSET_FLAGS(pbar, GNT_WIDGET_GROW_X); + GNT_WIDGET_SET_FLAGS(pbar, GNT_WIDGET_GROW_Y); + widget->priv.minw = 1; + widget->priv.minh = 8; + } + + if ((GNT_WIDGET_FLAGS(pbar) & GNT_WIDGET_MAPPED)) + gnt_widget_draw(GNT_WIDGET(pbar)); +} + +void +gnt_progress_bar_set_show_progress (GntProgressBar *pbar, gboolean show) +{ + GntProgressBarPrivate *priv = GNT_PROGRESS_BAR_GET_PRIVATE (pbar); + priv->show_value = show; +} + +gdouble +gnt_progress_bar_get_fraction (GntProgressBar *pbar) +{ + GntProgressBarPrivate *priv = GNT_PROGRESS_BAR_GET_PRIVATE (pbar); + return priv->fraction; +} + +GntProgressBarOrientation +gnt_progress_bar_get_orientation (GntProgressBar *pbar) +{ + GntProgressBarPrivate *priv = GNT_PROGRESS_BAR_GET_PRIVATE (pbar); + return priv->orientation; +} + +gboolean +gnt_progress_bar_get_show_progress (GntProgressBar *pbar) +{ + GntProgressBarPrivate *priv = GNT_PROGRESS_BAR_GET_PRIVATE (pbar); + return priv->show_value; +} + diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gntprogressbar.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/gntprogressbar.h Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,132 @@ +/** + * @file gntprogressbar.h Progress Bar API + * @ingroup gnt + */ +/* + * GNT - The GLib Ncurses Toolkit + * + * GNT 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 library 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_PROGRESS_BAR_H +#define GNT_PROGRESS_BAR_H + +#include "gnt.h" +#include "gntwidget.h" + +#define GNT_TYPE_PROGRESS_BAR (gnt_progress_bar_get_type ()) +#define GNT_PROGRESS_BAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GNT_TYPE_PROGRESS_BAR, GntProgressBar)) +#define GNT_PROGRESS_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GNT_TYPE_PROGRESS_BAR, GntProgressBarClass)) +#define GNT_IS_PROGRESS_BAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNT_TYPE_PROGRESS_BAR)) +#define GNT_IS_PROGRESS_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GNT_TYPE_PROGRESS_BAR)) +#define GNT_PROGRESS_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GNT_TYPE_PROGRESS_BAR, GntProgressBarClass)) + +typedef enum _GntProgressBarOrientation +{ + GNT_PROGRESS_LEFT_TO_RIGHT, + GNT_PROGRESS_RIGHT_TO_LEFT, + GNT_PROGRESS_BOTTOM_TO_TOP, + GNT_PROGRESS_TOP_TO_BOTTOM, +} GntProgressBarOrientation; + +typedef struct _GntProgressBar GntProgressBar; + +typedef struct _GntProgressBarClass +{ + GntWidgetClass parent; + + void (*gnt_reserved1)(void); + void (*gnt_reserved2)(void); + void (*gnt_reserved3)(void); + void (*gnt_reserved4)(void); +} GntProgressBarClass; + +G_BEGIN_DECLS + +/** + * Get the GType for GntProgressBar + * @return The GType for GntProrgressBar + **/ +GType +gnt_progress_bar_get_type (void); + +/** + * Create a new GntProgressBar + * @return The new GntProgressBar + **/ +GntWidget * +gnt_progress_bar_new (void); + +/** + * Set the progress for a progress bar + * + * @param pbar The GntProgressBar + * @param fraction The value between 0 and 1 to display + **/ +void +gnt_progress_bar_set_fraction (GntProgressBar *pbar, gdouble fraction); + +/** + * Set the orientation for a progress bar + * + * @param pbar The GntProgressBar + * @param orientation The orientation to use + **/ +void +gnt_progress_bar_set_orientation (GntProgressBar *pbar, GntProgressBarOrientation orientation); + +/** + * Controls whether the progress value is shown + * + * @param pbar The GntProgressBar + * @param show A boolean indicating if the value is shown + **/ +void +gnt_progress_bar_set_show_progress (GntProgressBar *pbar, gboolean show); + +/** + * Get the progress that is displayed + * + * @param pbar The GntProgressBar + * @return The progress displayed as a value between 0 and 1 + **/ +gdouble +gnt_progress_bar_get_fraction (GntProgressBar *pbar); + +/** + * Get the orientation for the progress bar + * + * @param pbar The GntProgressBar + * @return The current orientation of the progress bar + **/ +GntProgressBarOrientation +gnt_progress_bar_get_orientation (GntProgressBar *pbar); + +/** + * Get a boolean describing if the progress value is shown + * + * @param pbar The GntProgressBar + * @return A boolean @c true if the progress value is shown, @c false otherwise. + **/ +gboolean +gnt_progress_bar_get_show_progress (GntProgressBar *pbar); + +G_END_DECLS + +#endif /* GNT_PROGRESS_BAR_H */ diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gnttextview.c --- a/finch/libgnt/gnttextview.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/gnttextview.c Mon Apr 20 00:10:51 2009 +0000 @@ -177,7 +177,7 @@ gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); } - GNTDEBUG; + wmove(widget->window, 0, 0); } static void @@ -799,6 +799,7 @@ break; } } + gnt_widget_draw(GNT_WIDGET(view)); return count; } diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gnttree.c --- a/finch/libgnt/gnttree.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/gnttree.c Mon Apr 20 00:10:51 2009 +0000 @@ -28,7 +28,7 @@ #include #include -#define SEARCH_TIMEOUT 4000 /* 4 secs */ +#define SEARCH_TIMEOUT_S 4 /* 4 secs */ #define SEARCHING(tree) (tree->priv->search && tree->priv->search->len > 0) #define COLUMN_INVISIBLE(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_INVISIBLE) @@ -420,6 +420,7 @@ GntTreeRow *row; int pos, up, down = 0; int rows, scrcol; + int current = 0; if (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED)) return; @@ -431,7 +432,7 @@ if (tree->top == NULL) tree->top = tree->root; - if (tree->current == NULL) { + if (tree->current == NULL && tree->root != NULL) { tree->current = tree->root; tree_selection_changed(tree, NULL, tree->current); } @@ -490,6 +491,13 @@ tree->top = get_next(tree->top); row = tree->top; scrcol = widget->priv.width - 1 - 2 * pos; /* exclude the borders and the scrollbar */ + + if (tree->current && !row_matches_search(tree->current)) { + GntTreeRow *old = tree->current; + tree->current = tree->top; + tree_selection_changed(tree, old, tree->current); + } + for (i = start + pos; row && i < widget->priv.height - pos; i++, row = get_next(row)) { @@ -518,6 +526,7 @@ if (row == tree->current) { + current = i; attr |= A_BOLD; if (gnt_widget_has_focus(widget)) attr |= gnt_color_pair(GNT_COLOR_HIGHLIGHT); @@ -606,6 +615,7 @@ mvwaddnstr(widget->window, widget->priv.height - pos - 1, pos, tree->priv->search->str, str - tree->priv->search->str); } + wmove(widget->window, current, pos); gnt_widget_queue_update(widget); } @@ -818,7 +828,7 @@ gnt_bindable_perform_action_key(GNT_BINDABLE(tree), text); } g_source_remove(tree->priv->search_timeout); - tree->priv->search_timeout = g_timeout_add(SEARCH_TIMEOUT, search_timeout, tree); + tree->priv->search_timeout = g_timeout_add_seconds(SEARCH_TIMEOUT_S, search_timeout, tree); return TRUE; } else if (text[0] == ' ' && text[1] == 0) { /* Space pressed */ @@ -930,7 +940,7 @@ return FALSE; GNT_WIDGET_SET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS); tree->priv->search = g_string_new(NULL); - tree->priv->search_timeout = g_timeout_add(SEARCH_TIMEOUT, search_timeout, tree); + tree->priv->search_timeout = g_timeout_add_seconds(SEARCH_TIMEOUT_S, search_timeout, tree); return TRUE; } diff -r 78ef23551355 -r 872d30754311 finch/libgnt/gntwm.c --- a/finch/libgnt/gntwm.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/libgnt/gntwm.c Mon Apr 20 00:10:51 2009 +0000 @@ -135,6 +135,17 @@ src = widget->window; dst = node->window; copywin(src, dst, node->scroll, 0, 0, 0, getmaxy(dst) - 1, getmaxx(dst) - 1, 0); + + /* Update the hardware cursor */ + if (GNT_IS_WINDOW(widget) || GNT_IS_BOX(widget)) { + GntWidget *active = GNT_BOX(widget)->active; + if (active) { + int curx = active->priv.x + getcurx(active->window); + int cury = active->priv.y + getcury(active->window); + if (wmove(node->window, cury - widget->priv.y, curx - widget->priv.x) != OK) + wmove(node->window, 0, 0); + } + } } /** @@ -397,7 +408,7 @@ wm->positions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); if (gnt_style_get_bool(GNT_STYLE_REMPOS, TRUE)) read_window_positions(wm); - g_timeout_add(IDLE_CHECK_INTERVAL * 1000, check_idle, NULL); + g_timeout_add_seconds(IDLE_CHECK_INTERVAL, check_idle, NULL); time(&last_active_time); gnt_wm_switch_workspace(wm, 0); } @@ -1101,8 +1112,8 @@ g_hash_table_foreach(wm->nodes, (GHFunc)refresh_node, GINT_TO_POINTER(TRUE)); g_signal_emit(wm, signals[SIG_TERMINAL_REFRESH], 0); + gnt_ws_draw_taskbar(wm->cws, TRUE); update_screen(wm); - gnt_ws_draw_taskbar(wm->cws, TRUE); curs_set(0); /* endwin resets the cursor to normal */ return TRUE; @@ -1872,8 +1883,8 @@ } } + gnt_ws_draw_taskbar(wm->cws, FALSE); update_screen(wm); - gnt_ws_draw_taskbar(wm->cws, FALSE); } void gnt_wm_window_decorate(GntWM *wm, GntWidget *widget) @@ -1885,6 +1896,7 @@ { GntWS *s; int pos; + gboolean transient = !!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_TRANSIENT); s = gnt_wm_widget_find_workspace(wm, widget); @@ -1908,10 +1920,12 @@ if (s->ordered && wm->cws == s) gnt_wm_raise_window(wm, s->ordered->data); } + } else if (transient && wm->cws && wm->cws->ordered) { + gnt_wm_update_window(wm, wm->cws->ordered->data); } + gnt_ws_draw_taskbar(wm->cws, FALSE); update_screen(wm); - gnt_ws_draw_taskbar(wm->cws, FALSE); } time_t gnt_wm_get_idle_time() @@ -2119,7 +2133,7 @@ if (write_timeout) { g_source_remove(write_timeout); } - write_timeout = g_timeout_add(10000, write_already, wm); + write_timeout = g_timeout_add_seconds(10, write_already, wm); } void gnt_wm_move_window(GntWM *wm, GntWidget *widget, int x, int y) @@ -2181,8 +2195,8 @@ GntNode *nd = g_hash_table_lookup(wm->nodes, wm->_list.window); top_panel(nd->panel); } + gnt_ws_draw_taskbar(wm->cws, FALSE); update_screen(wm); - gnt_ws_draw_taskbar(wm->cws, FALSE); } void gnt_wm_update_window(GntWM *wm, GntWidget *widget) @@ -2207,8 +2221,8 @@ if (ws == wm->cws || GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_TRANSIENT)) { gnt_wm_copy_win(widget, node); + gnt_ws_draw_taskbar(wm->cws, FALSE); update_screen(wm); - gnt_ws_draw_taskbar(wm->cws, FALSE); } else if (ws && ws != wm->cws && GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_URGENT)) { if (!act || (act && !g_list_find(act, ws))) act = g_list_prepend(act, ws); diff -r 78ef23551355 -r 872d30754311 finch/libgnt/wms/Makefile.am diff -r 78ef23551355 -r 872d30754311 finch/libgnt/wms/s.c diff -r 78ef23551355 -r 872d30754311 finch/plugins/Makefile.am --- a/finch/plugins/Makefile.am Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/plugins/Makefile.am Mon Apr 20 00:10:51 2009 +0000 @@ -2,6 +2,7 @@ gntgf_la_LDFLAGS = -module -avoid-version gnthistory_la_LDFLAGS = -module -avoid-version gntlastlog_la_LDFLAGS = -module -avoid-version +gnttinyurl_la_LDFLAGS = -module -avoid-version grouping_la_LDFLAGS = -module -avoid-version if PLUGINS @@ -11,6 +12,7 @@ gntgf.la \ gnthistory.la \ gntlastlog.la \ + gnttinyurl.la \ grouping.la plugindir = $(libdir)/finch @@ -19,6 +21,7 @@ gntgf_la_SOURCES = gntgf.c gnthistory_la_SOURCES = gnthistory.c gntlastlog_la_SOURCES = lastlog.c +gnttinyurl_la_SOURCES = gnttinyurl.c grouping_la_SOURCES = grouping.c gntclipboard_la_CFLAGS = $(X11_CFLAGS) @@ -28,6 +31,7 @@ gntgf_la_LIBADD = $(GLIB_LIBS) $(X11_LIBS) $(top_builddir)/finch/libgnt/libgnt.la gnthistory_la_LIBADD = $(GLIB_LIBS) gntlastlog_la_LIBADD = $(GLIB_LIBS) +gnttinyurl_la_LIBADD = $(GLIB_LIBS) grouping_la_LIBADD = $(GLIB_LIBS) $(top_builddir)/finch/libgnt/libgnt.la endif # PLUGINS diff -r 78ef23551355 -r 872d30754311 finch/plugins/gntgf.c --- a/finch/plugins/gntgf.c Mon Apr 20 00:05:54 2009 +0000 +++ b/finch/plugins/gntgf.c Mon Apr 20 00:10:51 2009 +0000 @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -75,7 +76,7 @@ { toasters = g_list_remove(toasters, toast); gnt_widget_destroy(toast->window); - g_source_remove(toast->timer); + purple_timeout_remove(toast->timer); g_free(toast); } @@ -220,7 +221,7 @@ } gnt_widget_draw(window); - toast->timer = g_timeout_add(4000, (GSourceFunc)remove_toaster, toast); + toast->timer = purple_timeout_add_seconds(4, (GSourceFunc)remove_toaster, toast); toasters = g_list_prepend(toasters, toast); } diff -r 78ef23551355 -r 872d30754311 finch/plugins/gnttinyurl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/plugins/gnttinyurl.c Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,415 @@ +/** + * @file gnttinyurl.c + * + * Copyright (C) 2009 Richard Nelson + * + * 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 + +#define PLUGIN_STATIC_NAME TinyURL +#define PREFS_BASE "/plugins/gnt/tinyurl" +#define PREF_LENGTH PREFS_BASE "/length" +#define PREF_URL PREFS_BASE "/url" + + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include + +static int tag_num = 0; + +typedef struct +{ + PurpleConversation *conv; + gchar *tag; + int num; +} CbInfo; + +/* 3 functions from util.c */ +static gboolean +badchar(char c) +{ + switch (c) { + case ' ': + case ',': + case '\0': + case '\n': + case '\r': + case '<': + case '>': + case '"': + case '\'': + return TRUE; + default: + return FALSE; + } +} + +static gboolean +badentity(const char *c) +{ + if (!g_ascii_strncasecmp(c, "<", 4) || + !g_ascii_strncasecmp(c, ">", 4) || + !g_ascii_strncasecmp(c, """, 6)) { + return TRUE; + } + return FALSE; +} + +static GList *extract_urls(char *text) { + const char *t, *c, *q = NULL; + char *url_buf; + GList *ret = NULL; + gboolean inside_html = FALSE; + int inside_paren = 0; + c = text; + while (*c) { + if (*c == '(' && !inside_html) { + inside_paren++; + c++; + } + if (inside_html) { + if (*c == '>') { + inside_html = FALSE; + } else if (!q && (*c == '\"' || *c == '\'')) { + q = c; + } else if(q) { + if(*c == *q) + q = NULL; + } + } else if (*c == '<') { + inside_html = TRUE; + if (!g_ascii_strncasecmp(c, "') { + inside_html = FALSE; + break; + } + c++; + if (!(*c)) + break; + } + } + } else if ((*c=='h') && (!g_ascii_strncasecmp(c, "http://", 7) || + (!g_ascii_strncasecmp(c, "https://", 8)))) { + t = c; + while (1) { + if (badchar(*t) || badentity(t)) { + + if ((!g_ascii_strncasecmp(c, "http://", 7) && (t - c == 7)) || + (!g_ascii_strncasecmp(c, "https://", 8) && (t - c == 8))) { + break; + } + + if (*(t) == ',' && (*(t + 1) != ' ')) { + t++; + continue; + } + + if (*(t - 1) == '.') + t--; + if ((*(t - 1) == ')' && (inside_paren > 0))) { + t--; + } + + url_buf = g_strndup(c, t - c); + if (!g_list_find_custom(ret, url_buf, (GCompareFunc)strcmp)) { + purple_debug_info("TinyURL", "Added URL %s\n", url_buf); + ret = g_list_append(ret, g_strdup(url_buf)); + } + c = t; + break; + } + t++; + + } + } else if (!g_ascii_strncasecmp(c, "www.", 4) && (c == text || badchar(c[-1]) || badentity(c-1))) { + if (c[4] != '.') { + t = c; + while (1) { + if (badchar(*t) || badentity(t)) { + if (t - c == 4) { + break; + } + + if (*(t) == ',' && (*(t + 1) != ' ')) { + t++; + continue; + } + + if (*(t - 1) == '.') + t--; + if ((*(t - 1) == ')' && (inside_paren > 0))) { + t--; + } + url_buf = g_strndup(c, t - c); + if (!g_list_find_custom(ret, url_buf, (GCompareFunc)strcmp)) { + purple_debug_info("TinyURL", "Added URL %s\n", url_buf); + ret = g_list_append(ret, url_buf); + } + c = t; + break; + } + t++; + } + } + } + if (*c == ')' && !inside_html) { + inside_paren--; + c++; + } + if (*c == 0) + break; + c++; + } + return ret; +} + +static void url_fetched(PurpleUtilFetchUrlData *url_data, gpointer cb_data, + const gchar *url_text, gsize len, const gchar *error_message) +{ + CbInfo *data = (CbInfo *)cb_data; + PurpleConversation *conv = data->conv; + GList *convs = purple_get_conversations(); + /* ensure the conversation still exists */ + for (; convs; convs = convs->next) { + if ((PurpleConversation *)(convs->data) == conv) { + FinchConv *fconv = FINCH_CONV(conv); + gchar *str = g_strdup_printf("[%d] %s", data->num, url_text); + GntTextView *tv = GNT_TEXT_VIEW(fconv->tv); + gnt_text_view_tag_change(tv, data->tag, str, FALSE); + g_free(str); + g_free(data->tag); + return; + } + } + g_free(data->tag); + purple_debug_info("TinyURL", "Conversation no longer exists... :(\n"); +} + +static void free_urls(gpointer data, gpointer null) +{ + g_free(data); +} + +static gboolean receiving_msg(PurpleAccount *account, char **sender, char **message, + PurpleConversation *conv, PurpleMessageFlags *flags) { + GString *t; + GList *iter, *urls; + int c = 0; + + if (!(*flags & PURPLE_MESSAGE_RECV) || *flags & PURPLE_MESSAGE_INVISIBLE) + return FALSE; + + t = g_string_new(*message); + urls = purple_conversation_get_data(conv, "TinyURLs"); + if (urls != NULL) /* message was cancelled somewhere? Reset. */ + g_list_foreach(urls, free_urls, NULL); + g_list_free(urls); + urls = extract_urls(t->str); + g_free(*message); + for (iter = urls; iter; iter = iter->next) { + if (g_utf8_strlen((char *)iter->data, -1) >= purple_prefs_get_int(PREF_LENGTH)) { + int pos, x = 0; + gchar *j, *s, *str, *orig; + glong len = g_utf8_strlen(iter->data, -1); + s = g_strdup(t->str); + orig = s; + str = g_strdup_printf("[%d]", ++c); + while ((j = strstr(s, iter->data))) { /* replace all occurrences */ + pos = j - orig + (x++ * 3); + s = j + len; + t = g_string_insert(t, pos + len, str); + if (*s == '\0') break; + } + g_free(orig); + g_free(str); + continue; + } else { + if (iter->prev) { + iter = iter->prev; + g_free(iter->next->data); + urls = g_list_delete_link(urls, iter->next); + } else { + g_free(iter->data); + g_list_free(urls); + urls = NULL; + } + } + } + *message = t->str; + g_string_free(t, FALSE); + if (conv == NULL) + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, *sender); + purple_conversation_set_data(conv, "TinyURLs", urls); + return FALSE; +} + +static void received_msg(PurpleAccount *account, char *sender, char *message, + PurpleConversation *conv, PurpleMessageFlags flags) { + int c; + GList *urls, *iter; + FinchConv *fconv = FINCH_CONV(conv); + GntTextView *tv = GNT_TEXT_VIEW(fconv->tv); + + urls = purple_conversation_get_data(conv, "TinyURLs"); + if (!(flags & PURPLE_MESSAGE_RECV) || urls == NULL) + return; + + for (iter = urls, c = 0; iter; iter = iter->next) { + int i; + CbInfo *cbdata; + gchar *url, *str, *tmp; + cbdata = g_new(CbInfo, 1); + cbdata->num = ++c; + cbdata->tag = g_strdup_printf("%s%d", "tiny_", tag_num++); + cbdata->conv = conv; + tmp = purple_unescape_html((char *)iter->data); + if (g_ascii_strncasecmp(tmp, "http://", 7) && g_ascii_strncasecmp(tmp, "https://", 8)) { + url = g_strdup_printf("%shttp%%3A%%2F%%2F%s", purple_prefs_get_string(PREF_URL), purple_url_encode(tmp)); + } else { + url = g_strdup_printf("%s%s", purple_prefs_get_string(PREF_URL), purple_url_encode(tmp)); + } + g_free(tmp); + purple_util_fetch_url(url, TRUE, "finch", FALSE, url_fetched, cbdata); + i = gnt_text_view_get_lines_below(tv); + str = g_strdup_printf(_("\nFetching TinyURL...")); + gnt_text_view_append_text_with_tag((tv), str, GNT_TEXT_FLAG_DIM, cbdata->tag); + g_free(str); + if (i == 0) + gnt_text_view_scroll(tv, 0); + g_free(iter->data); + g_free(url); + } + g_list_free(urls); + purple_conversation_set_data(conv, "TinyURLs", NULL); +} + +static void +free_conv_urls(PurpleConversation *conv) +{ + GList *urls = purple_conversation_get_data(conv, "TinyURLs"); + if (urls) + g_list_foreach(urls, free_urls, NULL); + g_list_free(urls); +} + +static gboolean +plugin_load(PurplePlugin *plugin) { + purple_signal_connect(purple_conversations_get_handle(), + "wrote-im-msg", + plugin, PURPLE_CALLBACK(received_msg), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "wrote-chat-msg", + plugin, PURPLE_CALLBACK(received_msg), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "receiving-im-msg", + plugin, PURPLE_CALLBACK(receiving_msg), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "receiving-chat-msg", + plugin, PURPLE_CALLBACK(receiving_msg), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "deleting-conversation", + plugin, PURPLE_CALLBACK(free_conv_urls), NULL); + + return TRUE; +} + +static PurplePluginPrefFrame * +get_plugin_pref_frame(PurplePlugin *plugin) { + + PurplePluginPrefFrame *frame; + PurplePluginPref *pref; + + frame = purple_plugin_pref_frame_new(); + + pref = purple_plugin_pref_new_with_name(PREF_LENGTH); + purple_plugin_pref_set_label(pref, _("Only create TinyURL for urls" + " of this length or greater")); + purple_plugin_pref_frame_add(frame, pref); + pref = purple_plugin_pref_new_with_name(PREF_URL); + purple_plugin_pref_set_label(pref, _("TinyURL (or other) address prefix")); + purple_plugin_pref_frame_add(frame, pref); + + return frame; +} + +static PurplePluginUiInfo prefs_info = { + get_plugin_pref_frame, + 0, /* page_num (Reserved) */ + NULL, /* frame (Reserved) */ + + /* padding */ + NULL, + NULL, + NULL, + NULL +}; + +static PurplePluginInfo info = +{ + PURPLE_PLUGIN_MAGIC, + PURPLE_MAJOR_VERSION, + PURPLE_MINOR_VERSION, + PURPLE_PLUGIN_STANDARD, + FINCH_PLUGIN_TYPE, + 0, + NULL, + PURPLE_PRIORITY_DEFAULT, + "TinyURL", + N_("TinyURL"), + DISPLAY_VERSION, + N_("TinyURL plugin"), + N_("When receiving a message with URL(s), TinyURL for easier copying"), + "Richard Nelson ", + PURPLE_WEBSITE, + plugin_load, + NULL, + NULL, + NULL, + NULL, + &prefs_info, /**< prefs_info */ + NULL, + + /* padding */ + NULL, + NULL, + NULL, + NULL +}; + +static void +init_plugin(PurplePlugin *plugin) { + purple_prefs_add_none(PREFS_BASE); + purple_prefs_add_int(PREF_LENGTH, 30); + purple_prefs_add_string(PREF_URL, "http://tinyurl.com/api-create.php?url="); +} + +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info) diff -r 78ef23551355 -r 872d30754311 libpurple/Makefile.am --- a/libpurple/Makefile.am Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/Makefile.am Mon Apr 20 00:10:51 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 \ + media.h \ + media-gst.h \ + mediamanager.h \ mime.h \ nat-pmp.h \ network.h \ @@ -145,7 +152,16 @@ xmlnode.h \ whiteboard.h -purple_builtheaders = purple.h version.h +purple_builtheaders = purple.h version.h marshallers.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 @@ -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) \ + $(GSTINTERFACES_LIBS) \ -lm AM_CPPFLAGS = \ @@ -270,6 +293,9 @@ $(DEBUG_CFLAGS) \ $(DBUS_CFLAGS) \ $(LIBXML_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(GSTINTERFACES_CFLAGS) \ $(NETWORKMANAGER_CFLAGS) # INSTALL_SSL_CERTIFICATES is true when SSL_CERTIFICATES_DIR is empty. diff -r 78ef23551355 -r 872d30754311 libpurple/Makefile.mingw --- a/libpurple/Makefile.mingw Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/Makefile.mingw Mon Apr 20 00:10:51 2009 +0000 @@ -35,6 +35,7 @@ buddyicon.c \ certificate.c \ cipher.c \ + circbuffer.c \ cmds.c \ connection.c \ conversation.c \ @@ -44,10 +45,11 @@ dnssrv.c \ eventloop.c \ ft.c \ - circbuffer.c \ idle.c \ imgstore.c \ log.c \ + media.c \ + mediamanager.c \ mime.c \ nat-pmp.c \ network.c \ @@ -66,22 +68,22 @@ server.c \ signals.c \ smiley.c \ - sound.c \ + sound-theme-loader.c \ sound-theme.c \ - sound-theme-loader.c \ + sound.c \ sslconn.c \ status.c \ stringref.c \ stun.c \ - theme.c \ theme-loader.c \ theme-manager.c \ + theme.c \ upnp.c \ util.c \ value.c \ version.c \ + whiteboard.c \ xmlnode.c \ - whiteboard.c \ win32/giowin32.c \ win32/libc_interface.c \ win32/win32dep.c diff -r 78ef23551355 -r 872d30754311 libpurple/account.c --- a/libpurple/account.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/account.c Mon Apr 20 00:10:51 2009 +0000 @@ -2797,4 +2797,7 @@ purple_signals_disconnect_by_handle(handle); purple_signals_unregister_by_instance(handle); + + for (; accounts; accounts = g_list_delete_link(accounts, accounts)) + purple_account_destroy(accounts->data); } diff -r 78ef23551355 -r 872d30754311 libpurple/blist.c --- a/libpurple/blist.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/blist.c Mon Apr 20 00:10:51 2009 +0000 @@ -699,10 +699,23 @@ return purplebuddylist ? purplebuddylist->root : NULL; } -GHashTable * +static void +append_buddy(gpointer key, gpointer value, gpointer user_data) +{ + GSList **list = user_data; + *list = g_slist_prepend(*list, value); +} + +GSList * purple_blist_get_buddies() { - return purplebuddylist ? purplebuddylist->buddies : NULL; + GSList *buddies = NULL; + + if (!purplebuddylist) + return NULL; + + g_hash_table_foreach(purplebuddylist->buddies, append_buddy, &buddies); + return buddies; } void * @@ -1202,6 +1215,16 @@ return chat; } +void +purple_chat_destroy(PurpleChat *chat) +{ + g_hash_table_destroy(chat->components); + g_hash_table_destroy(chat->node.settings); + g_free(chat->alias); + PURPLE_DBUS_UNREGISTER_POINTER(chat); + g_free(chat); +} + PurpleBuddy *purple_buddy_new(PurpleAccount *account, const char *name, const char *alias) { PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); @@ -1229,6 +1252,42 @@ } void +purple_buddy_destroy(PurpleBuddy *buddy) +{ + PurplePlugin *prpl; + PurplePluginProtocolInfo *prpl_info; + + /* + * Tell the owner PRPL that we're about to free the buddy so it + * can free proto_data + */ + prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account)); + if (prpl) { + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + if (prpl_info && prpl_info->buddy_free) + prpl_info->buddy_free(buddy); + } + + /* Delete the node */ + purple_buddy_icon_unref(buddy->icon); + g_hash_table_destroy(buddy->node.settings); + purple_presence_destroy(buddy->presence); + g_free(buddy->name); + g_free(buddy->alias); + g_free(buddy->server_alias); + + PURPLE_DBUS_UNREGISTER_POINTER(buddy); + g_free(buddy); + + /* FIXME: Once PurpleBuddy is a GObject, timeout callbacks can + * g_object_ref() it when connecting the callback and + * g_object_unref() it in the handler. That way, it won't + * get freed while the timeout is pending and this line can + * be removed. */ + while (g_source_remove_by_user_data((gpointer *)buddy)); +} + +void purple_buddy_set_icon(PurpleBuddy *buddy, PurpleBuddyIcon *icon) { g_return_if_fail(buddy != NULL); @@ -1519,6 +1578,15 @@ return contact; } +void +purple_contact_destroy(PurpleContact *contact) +{ + g_hash_table_destroy(contact->node.settings); + g_free(contact->alias); + PURPLE_DBUS_UNREGISTER_POINTER(contact); + g_free(contact); +} + void purple_contact_set_alias(PurpleContact *contact, const char *alias) { purple_blist_alias_contact(contact,alias); @@ -1588,6 +1656,15 @@ return group; } +void +purple_group_destroy(PurpleGroup *group) +{ + g_hash_table_destroy(group->node.settings); + g_free(group->name); + PURPLE_DBUS_UNREGISTER_POINTER(group); + g_free(group); +} + void purple_blist_add_contact(PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node) { PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); @@ -1848,9 +1925,7 @@ ops->remove(purplebuddylist, node); /* Delete the node */ - g_hash_table_destroy(contact->node.settings); - PURPLE_DBUS_UNREGISTER_POINTER(contact); - g_free(contact); + purple_contact_destroy(contact); } } @@ -1861,8 +1936,6 @@ PurpleContact *contact; PurpleGroup *group; struct _purple_hbuddy hb; - PurplePlugin *prpl; - PurplePluginProtocolInfo *prpl_info = NULL; g_return_if_fail(buddy != NULL); @@ -1918,33 +1991,7 @@ /* Signal that the buddy has been removed before freeing the memory for it */ purple_signal_emit(purple_blist_get_handle(), "buddy-removed", buddy); - /* - * Tell the owner PRPL that we're about to free the buddy so it - * can free proto_data - */ - prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account)); - if (prpl) - prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); - if (prpl_info && prpl_info->buddy_free) - prpl_info->buddy_free(buddy); - - /* Delete the node */ - purple_buddy_icon_unref(buddy->icon); - g_hash_table_destroy(buddy->node.settings); - purple_presence_destroy(buddy->presence); - g_free(buddy->name); - g_free(buddy->alias); - g_free(buddy->server_alias); - - PURPLE_DBUS_UNREGISTER_POINTER(buddy); - g_free(buddy); - - /* FIXME: Once PurpleBuddy is a GObject, timeout callbacks can - * g_object_ref() it when connecting the callback and - * g_object_unref() it in the handler. That way, it won't - * get freed while the timeout is pending and this line can - * be removed. */ - while (g_source_remove_by_user_data((gpointer *)buddy)); + purple_buddy_destroy(buddy); /* If the contact is empty then remove it */ if ((contact != NULL) && !cnode->child) @@ -1988,11 +2035,7 @@ ops->remove(purplebuddylist, node); /* Delete the node */ - g_hash_table_destroy(chat->components); - g_hash_table_destroy(chat->node.settings); - g_free(chat->alias); - PURPLE_DBUS_UNREGISTER_POINTER(chat); - g_free(chat); + purple_chat_destroy(chat); } void purple_blist_remove_group(PurpleGroup *group) @@ -2033,10 +2076,7 @@ } /* Delete the node */ - g_hash_table_destroy(group->node.settings); - g_free(group->name); - PURPLE_DBUS_UNREGISTER_POINTER(group); - g_free(group); + purple_group_destroy(group); } PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact) @@ -2587,6 +2627,28 @@ } static void +purple_blist_node_destroy(PurpleBlistNode *node) +{ + PurpleBlistNode *child, *next_child; + + child = node->child; + while (child) { + next_child = child->next; + purple_blist_node_destroy(child); + child = next_child; + } + + if (PURPLE_BLIST_NODE_IS_BUDDY(node)) + purple_buddy_destroy((PurpleBuddy*)node); + else if (PURPLE_BLIST_NODE_IS_CHAT(node)) + purple_chat_destroy((PurpleChat*)node); + else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) + purple_contact_destroy((PurpleContact*)node); + else if (PURPLE_BLIST_NODE_IS_GROUP(node)) + purple_group_destroy((PurpleGroup*)node); +} + +static void purple_blist_node_setting_free(gpointer data) { PurpleValue *value; @@ -2874,12 +2936,21 @@ void purple_blist_uninit(void) { - if (save_timer != 0) - { + PurpleBlistNode *node, *next_node; + + if (save_timer != 0) { purple_timeout_remove(save_timer); save_timer = 0; purple_blist_sync(); } + node = purple_blist_get_root(); + while (node) { + next_node = node->next; + purple_blist_node_destroy(node); + node = next_node; + } + purplebuddylist->root = NULL; + purple_signals_unregister_by_instance(purple_blist_get_handle()); } diff -r 78ef23551355 -r 872d30754311 libpurple/blist.h --- a/libpurple/blist.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/blist.h Mon Apr 20 00:10:51 2009 +0000 @@ -260,18 +260,14 @@ PurpleBlistNode *purple_blist_get_root(void); /** - * Returns the hash table of every buddy in the list. - * You MUST treat this data structure as immutable. The only use should - * be for iterating over the values (PurpleBuddy*) in performance-critical - * code. + * Returns a list of every buddy in the list. * - * @see purple_find_buddy for the recommended alternative. - * - * @return The hash table of every buddy in the list. + * @return A list of every buddy in the list. Caller is responsible for + * freeing the list. * * @since 2.6.0 */ -GHashTable *purple_blist_get_buddies(void); +GSList *purple_blist_get_buddies(void); /** * Returns the UI data for the list. @@ -485,6 +481,13 @@ PurpleChat *purple_chat_new(PurpleAccount *account, const char *alias, GHashTable *components); /** + * Destroys a chat + * + * @param chat The chat to destroy + */ +void purple_chat_destroy(PurpleChat *chat); + +/** * Adds a new chat to the buddy list. * * The chat will be inserted right after node or appended to the end @@ -515,6 +518,13 @@ PurpleBuddy *purple_buddy_new(PurpleAccount *account, const char *name, const char *alias); /** + * Destroys a buddy + * + * @param buddy The buddy to destroy + */ +void purple_buddy_destroy(PurpleBuddy *buddy); + +/** * Sets a buddy's icon. * * This should only be called from within Purple. You probably want to @@ -625,6 +635,13 @@ PurpleGroup *purple_group_new(const char *name); /** + * Destroys a group + * + * @param group The group to destroy +*/ +void purple_group_destroy(PurpleGroup *group); + +/** * Adds a new group to the buddy list. * * The new group will be inserted after insert or prepended to the list if @@ -643,6 +660,13 @@ PurpleContact *purple_contact_new(void); /** + * Destroys a contact + * + * @param contact The contact to destroy + */ +void purple_contact_destroy(PurpleContact *contact); + +/** * Adds a new contact to the buddy list. * * The new contact will be inserted after insert or prepended to the list if @@ -728,7 +752,7 @@ * @param contact The contact to be removed * * @see purple_blist_remove_buddy - * */ + */ void purple_blist_remove_contact(PurpleContact *contact); /** diff -r 78ef23551355 -r 872d30754311 libpurple/buddyicon.c --- a/libpurple/buddyicon.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/buddyicon.c Mon Apr 20 00:10:51 2009 +0000 @@ -1299,6 +1299,7 @@ g_hash_table_destroy(icon_file_cache); g_hash_table_destroy(pointer_icon_cache); g_free(old_icons_dir); + g_free(cache_dir); } void purple_buddy_icon_get_scale_size(PurpleBuddyIconSpec *spec, int *width, int *height) diff -r 78ef23551355 -r 872d30754311 libpurple/conversation.c --- a/libpurple/conversation.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/conversation.c Mon Apr 20 00:10:51 2009 +0000 @@ -33,7 +33,7 @@ #include "signals.h" #include "util.h" -#define SEND_TYPED_TIMEOUT 5000 +#define SEND_TYPED_TIMEOUT_SECONDS 5 static GList *conversations = NULL; static GList *ims = NULL; @@ -1122,8 +1122,9 @@ { g_return_if_fail(im != NULL); - im->send_typed_timeout = purple_timeout_add(SEND_TYPED_TIMEOUT, send_typed_cb, - purple_conv_im_get_conversation(im)); + im->send_typed_timeout = purple_timeout_add_seconds(SEND_TYPED_TIMEOUT_SECONDS, + send_typed_cb, + purple_conv_im_get_conversation(im)); } void @@ -2004,6 +2005,66 @@ purple_conversation_update(chat->conv, PURPLE_CONV_UPDATE_CHATLEFT); } +static void +invite_user_to_chat(gpointer data, PurpleRequestFields *fields) +{ + PurpleConversation *conv; + PurpleConvChat *chat; + const char *user, *message; + + conv = data; + chat = PURPLE_CONV_CHAT(conv); + user = purple_request_fields_get_string(fields, "screenname"); + message = purple_request_fields_get_string(fields, "message"); + + serv_chat_invite(purple_conversation_get_gc(conv), chat->id, message, user); +} + +void purple_conv_chat_invite_user(PurpleConvChat *chat, const char *user, + const char *message, gboolean confirm) +{ + PurpleAccount *account; + PurpleConversation *conv; + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + + g_return_if_fail(chat); + + if (!user || !*user || !message || !*message) + confirm = TRUE; + + conv = chat->conv; + account = conv->account; + + if (!confirm) { + serv_chat_invite(purple_account_get_connection(account), + purple_conv_chat_get_id(chat), message, user); + return; + } + + fields = purple_request_fields_new(); + group = purple_request_field_group_new(_("Invite to chat")); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_string_new("screenname", _("Buddy"), user, FALSE); + purple_request_field_group_add_field(group, field); + purple_request_field_set_required(field, TRUE); + purple_request_field_set_type_hint(field, "screenname"); + + field = purple_request_field_string_new("message", _("Message"), message, FALSE); + purple_request_field_group_add_field(group, field); + + purple_request_fields(conv, _("Invite to chat"), NULL, + _("Please enter the name of the user you wish to invite, " + "along with an optional invite message."), + fields, + _("Invite"), G_CALLBACK(invite_user_to_chat), + _("Cancel"), NULL, + account, user, conv, + conv); +} + gboolean purple_conv_chat_has_left(PurpleConvChat *chat) { diff -r 78ef23551355 -r 872d30754311 libpurple/conversation.h --- a/libpurple/conversation.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/conversation.h Mon Apr 20 00:10:51 2009 +0000 @@ -1300,6 +1300,22 @@ void purple_conv_chat_left(PurpleConvChat *chat); /** + * Invite a user to a chat. + * The user will be prompted to enter the user's name or a message if one is + * not given. + * + * @param chat The chat. + * @param user The user to invite to the chat. + * @param message The message to send with the invitation. + * @param confirm Prompt before sending the invitation. The user is always + * prompted if either #user or #message is @c NULL. + * + * @since 2.6.0 + */ +void purple_conv_chat_invite_user(PurpleConvChat *chat, const char *user, + const char *message, gboolean confirm); + +/** * Returns true if we're no longer in this chat, * and just left the window open. * diff -r 78ef23551355 -r 872d30754311 libpurple/core.c --- a/libpurple/core.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/core.c Mon Apr 20 00:10:51 2009 +0000 @@ -216,15 +216,10 @@ /* The SSL plugins must be uninit before they're unloaded */ purple_ssl_uninit(); - /* Unload all plugins before the UI because UI plugins might call - * UI-specific functions */ - purple_debug_info("main", "Unloading all plugins\n"); - purple_plugins_destroy_all(); - - /* Shut down the UI before all the subsystems */ - ops = purple_core_get_ui_ops(); - if (ops != NULL && ops->quit != NULL) - ops->quit(); + /* Unload all non-loader, non-prpl plugins before shutting down + * subsystems. */ + purple_debug_info("main", "Unloading normal plugins\n"); + purple_plugins_unload(PURPLE_PLUGIN_STANDARD); /* Save .xml files, remove signals, etc. */ purple_smileys_uninit(); @@ -247,7 +242,16 @@ purple_imgstore_uninit(); purple_network_uninit(); - /* Everything after this must not try to read any prefs */ + /* Everything after unloading all plugins must not fail if prpls aren't + * around */ + purple_debug_info("main", "Unloading all plugins\n"); + purple_plugins_destroy_all(); + + ops = purple_core_get_ui_ops(); + if (ops != NULL && ops->quit != NULL) + ops->quit(); + + /* Everything after prefs_uninit must not try to read any prefs */ purple_prefs_uninit(); purple_plugins_uninit(); #ifdef HAVE_DBUS @@ -255,8 +259,9 @@ #endif purple_cmds_uninit(); - /* Everything after this cannot try to write things to the confdir */ + /* Everything after util_uninit cannot try to write things to the confdir */ purple_util_uninit(); + purple_log_uninit(); purple_signals_uninit(); diff -r 78ef23551355 -r 872d30754311 libpurple/example/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/ft.c --- a/libpurple/ft.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/ft.c Mon Apr 20 00:10:51 2009 +0000 @@ -458,7 +458,7 @@ { PurpleXferType type; struct stat st; - char *msg, *utf8; + char *msg, *utf8, *base; PurpleAccount *account; PurpleBuddy *buddy; @@ -505,7 +505,9 @@ purple_xfer_set_local_filename(xfer, filename); purple_xfer_set_size(xfer, st.st_size); - utf8 = g_filename_to_utf8(g_basename(filename), -1, NULL, NULL, NULL); + base = g_path_get_basename(filename); + utf8 = g_filename_to_utf8(base, -1, NULL, NULL, NULL); + g_free(base); purple_xfer_set_filename(xfer, utf8); msg = g_strdup_printf(_("Offering to send %s to %s"), diff -r 78ef23551355 -r 872d30754311 libpurple/internal.h --- a/libpurple/internal.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/internal.h Mon Apr 20 00:10:51 2009 +0000 @@ -222,6 +222,40 @@ # endif #endif +#include + +#ifndef G_DEFINE_TYPE +#define G_DEFINE_TYPE(TypeName, type_name, TYPE_PARENT) \ +\ +static void type_name##_init (TypeName *self); \ +static void type_name##_class_init (TypeName##Class *klass); \ +static gpointer type_name##_parent_class = NULL; \ +static void type_name##_class_intern_init (gpointer klass) \ +{ \ + type_name##_parent_class = g_type_class_peek_parent (klass); \ + type_name##_class_init ((TypeName##Class*) klass); \ +} \ +\ +GType \ +type_name##_get_type (void) \ +{ \ + static GType g_define_type_id = 0; \ + if (G_UNLIKELY (g_define_type_id == 0)) \ + { \ + g_define_type_id = \ + g_type_register_static_simple (TYPE_PARENT, \ + g_intern_static_string (#TypeName), \ + sizeof (TypeName##Class), \ + (GClassInitFunc)type_name##_class_intern_init, \ + sizeof (TypeName), \ + (GInstanceInitFunc)type_name##_init, \ + (GTypeFlags) 0); \ + } \ + return g_define_type_id; \ +} /* closes type_name##_get_type() */ + +#endif + /* Safer ways to work with static buffers. When using non-static * buffers, either use g_strdup_* functions (preferred) or use * g_strlcpy/g_strlcpy directly. */ diff -r 78ef23551355 -r 872d30754311 libpurple/marshallers.list --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/marshallers.list Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,5 @@ +VOID:POINTER,POINTER,OBJECT +BOOLEAN:OBJECT,POINTER,STRING +VOID:STRING,STRING +VOID:ENUM,STRING,STRING +VOID:ENUM,STRING,STRING,BOOLEAN diff -r 78ef23551355 -r 872d30754311 libpurple/media-gst.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/media-gst.h Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,173 @@ +/** + * @file media-gst.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 _PURPLE_MEDIA_GST_H_ +#define _PURPLE_MEDIA_GST_H_ + +#include "media.h" +#include "mediamanager.h" + +#include + +G_BEGIN_DECLS + +#define PURPLE_TYPE_MEDIA_ELEMENT_TYPE (purple_media_element_type_get_type()) +#define PURPLE_TYPE_MEDIA_ELEMENT_INFO (purple_media_element_info_get_type()) +#define PURPLE_MEDIA_ELEMENT_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_ELEMENT_INFO, PurpleMediaElementInfo)) +#define PURPLE_MEDIA_ELEMENT_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_ELEMENT_INFO, PurpleMediaElementInfo)) +#define PURPLE_IS_MEDIA_ELEMENT_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_ELEMENT_INFO)) +#define PURPLE_IS_MEDIA_ELEMENT_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_ELEMENT_INFO)) +#define PURPLE_MEDIA_ELEMENT_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_ELEMENT_INFO, PurpleMediaElementInfo)) + +/** @copydoc _PurpleMediaElementInfo */ +typedef struct _PurpleMediaElementInfo PurpleMediaElementInfo; +typedef struct _PurpleMediaElementInfoClass PurpleMediaElementInfoClass; +typedef GstElement *(*PurpleMediaElementCreateCallback)(PurpleMedia *media, + const gchar *session_id, const gchar *participant); + +typedef enum { + PURPLE_MEDIA_ELEMENT_NONE = 0, /** empty element */ + 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; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets the element type's GType. + * + * @return The element type's GType. + * + * @since 2.6.0 + */ +GType purple_media_element_type_get_type(void); + +/** + * Gets the element info's GType. + * + * @return The element info's GType. + * + * @since 2.6.0 + */ +GType purple_media_element_info_get_type(void); + +/** + * 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. + * + * @since 2.6.0 + */ +GstElement *purple_media_get_src(PurpleMedia *media, const gchar *sess_id); + +/** + * 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. + * + * @since 2.6.0 + */ +GstElement *purple_media_get_tee(PurpleMedia *media, + const gchar *session_id, const gchar *participant); + + +/** + * Gets the pipeline from the media manager. + * + * @param manager The media manager to get the pipeline from. + * + * @return The pipeline. + * + * @since 2.6.0 + */ +GstElement *purple_media_manager_get_pipeline(PurpleMediaManager *manager); + +/** + * 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. + * + * @since 2.6.0 + */ +GstElement *purple_media_manager_get_element(PurpleMediaManager *manager, + PurpleMediaSessionType type, PurpleMedia *media, + const gchar *session_id, const gchar *participant); + +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); + +gchar *purple_media_element_info_get_id(PurpleMediaElementInfo *info); +gchar *purple_media_element_info_get_name(PurpleMediaElementInfo *info); +PurpleMediaElementType purple_media_element_info_get_element_type( + PurpleMediaElementInfo *info); +GstElement *purple_media_element_info_call_create( + PurpleMediaElementInfo *info, PurpleMedia *media, + const gchar *session_id, const gchar *participant); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* _PURPLE_MEDIA_GST_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/media.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/media.c Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,3066 @@ +/** + * @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 "account.h" +#include "media.h" +#include "mediamanager.h" +#include "network.h" + +#include "debug.h" + +#ifdef USE_GSTREAMER +#include "marshallers.h" +#include "media-gst.h" +#endif + +#ifdef USE_VV + +#include + +/** @copydoc _PurpleMediaSession */ +typedef struct _PurpleMediaSession PurpleMediaSession; +/** @copydoc _PurpleMediaStream */ +typedef struct _PurpleMediaStream PurpleMediaStream; +/** @copydoc _PurpleMediaClass */ +typedef struct _PurpleMediaClass PurpleMediaClass; +/** @copydoc _PurpleMediaPrivate */ +typedef struct _PurpleMediaPrivate PurpleMediaPrivate; +/** @copydoc _PurpleMediaCandidateClass */ +typedef struct _PurpleMediaCandidateClass PurpleMediaCandidateClass; +/** @copydoc _PurpleMediaCandidatePrivate */ +typedef struct _PurpleMediaCandidatePrivate PurpleMediaCandidatePrivate; +/** @copydoc _PurpleMediaCodecClass */ +typedef struct _PurpleMediaCodecClass PurpleMediaCodecClass; +/** @copydoc _PurpleMediaCodecPrivate */ +typedef struct _PurpleMediaCodecPrivate PurpleMediaCodecPrivate; + +/** 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 _PurpleMediaSession +{ + gchar *id; + PurpleMedia *media; + GstElement *src; + GstElement *tee; + FsSession *session; + + PurpleMediaSessionType type; + gboolean initiator; +}; + +struct _PurpleMediaStream +{ + PurpleMediaSession *session; + gchar *participant; + FsStream *stream; + GstElement *src; + GstElement *tee; + + GList *local_candidates; + GList *remote_candidates; + + gboolean initiator; + gboolean accepted; + gboolean candidates_prepared; + + GList *active_local_candidates; + GList *active_remote_candidates; + + guint connected_cb_id; +}; +#endif + +struct _PurpleMediaPrivate +{ +#ifdef USE_VV + PurpleMediaManager *manager; + PurpleAccount *account; + FsConference *conference; + gboolean initiator; + gpointer prpl_data; + + GHashTable *sessions; /* PurpleMediaSession table */ + GHashTable *participants; /* FsParticipant table */ + + GList *streams; /* PurpleMediaStream table */ + + GstElement *confbin; +#else + gpointer dummy; +#endif +}; + +#ifdef USE_VV +#define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate)) +#define PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidatePrivate)) +#define PURPLE_MEDIA_CODEC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodecPrivate)) + +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_ACCOUNT, + PROP_CONFERENCE, + PROP_INITIATOR, + PROP_PRPL_DATA, +}; +#endif + + +/* + * PurpleMediaElementType + */ + +GType +purple_media_session_type_get_type() +{ + static GType type = 0; + if (type == 0) { + static const GFlagsValue values[] = { + { PURPLE_MEDIA_NONE, + "PURPLE_MEDIA_NONE", "none" }, + { PURPLE_MEDIA_RECV_AUDIO, + "PURPLE_MEDIA_RECV_AUDIO", "recv-audio" }, + { PURPLE_MEDIA_SEND_AUDIO, + "PURPLE_MEDIA_SEND_AUDIO", "send-audio" }, + { PURPLE_MEDIA_RECV_VIDEO, + "PURPLE_MEDIA_RECV_VIDEO", "recv-video" }, + { PURPLE_MEDIA_SEND_VIDEO, + "PURPLE_MEDIA_SEND_VIDEO", "send-audio" }, + { PURPLE_MEDIA_AUDIO, + "PURPLE_MEDIA_AUDIO", "audio" }, + { PURPLE_MEDIA_VIDEO, + "PURPLE_MEDIA_VIDEO", "video" }, + { 0, NULL, NULL } + }; + type = g_flags_register_static( + "PurpleMediaSessionType", values); + } + return type; +} + +GType +purple_media_get_type() +{ +#ifdef USE_VV + 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; +#else + return G_TYPE_NONE; +#endif +} + +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_ACCEPT, + "PURPLE_MEDIA_INFO_ACCEPT", "accept" }, + { PURPLE_MEDIA_INFO_REJECT, + "PURPLE_MEDIA_INFO_REJECT", "reject" }, + { PURPLE_MEDIA_INFO_MUTE, + "PURPLE_MEDIA_INFO_MUTE", "mute" }, + { PURPLE_MEDIA_INFO_UNMUTE, + "PURPLE_MEDIA_INFO_UNMUTE", "unmute" }, + { PURPLE_MEDIA_INFO_HOLD, + "PURPLE_MEDIA_INFO_HOLD", "hold" }, + { PURPLE_MEDIA_INFO_UNHOLD, + "PURPLE_MEDIA_INFO_HOLD", "unhold" }, + { 0, NULL, NULL } + }; + type = g_enum_register_static("PurpleMediaInfoType", values); + } + return type; +} + +#ifdef USE_VV +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_ACCOUNT, + g_param_spec_pointer("account", + "PurpleAccount", + "The account 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_WRITABLE)); + + 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_BOOLEAN, + G_TYPE_NONE, 4, PURPLE_MEDIA_TYPE_INFO_TYPE, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); + 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_ACCOUNT: + media->priv->account = 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_ACCOUNT: + g_value_set_pointer(value, media->priv->account); + 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; + } + +} +#endif + +/* + * PurpleMediaCandidateType + */ + +GType +purple_media_candidate_type_get_type() +{ + static GType type = 0; + if (type == 0) { + static const GEnumValue values[] = { + { PURPLE_MEDIA_CANDIDATE_TYPE_HOST, + "PURPLE_MEDIA_CANDIDATE_TYPE_HOST", + "host" }, + { PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX, + "PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX", + "srflx" }, + { PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX, + "PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX", + "prflx" }, + { PURPLE_MEDIA_CANDIDATE_TYPE_RELAY, + "PPURPLE_MEDIA_CANDIDATE_TYPE_RELAY", + "relay" }, + { PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST, + "PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST", + "multicast" }, + { 0, NULL, NULL } + }; + type = g_enum_register_static("PurpleMediaCandidateType", + values); + } + return type; +} + +/* + * PurpleMediaNetworkProtocol + */ + +GType +purple_media_network_protocol_get_type() +{ + static GType type = 0; + if (type == 0) { + static const GEnumValue values[] = { + { PURPLE_MEDIA_NETWORK_PROTOCOL_UDP, + "PURPLE_MEDIA_NETWORK_PROTOCOL_UDP", + "udp" }, + { PURPLE_MEDIA_NETWORK_PROTOCOL_TCP, + "PURPLE_MEDIA_NETWORK_PROTOCOL_TCP", + "tcp" }, + { 0, NULL, NULL } + }; + type = g_enum_register_static("PurpleMediaNetworkProtocol", + values); + } + return type; +} + +/* + * PurpleMediaCandidate + */ + +struct _PurpleMediaCandidateClass +{ + GObjectClass parent_class; +}; + +struct _PurpleMediaCandidate +{ + GObject parent; +}; + +#ifdef USE_VV +struct _PurpleMediaCandidatePrivate +{ + gchar *foundation; + guint component_id; + gchar *ip; + guint16 port; + gchar *base_ip; + guint16 base_port; + PurpleMediaNetworkProtocol proto; + guint32 priority; + PurpleMediaCandidateType type; + gchar *username; + gchar *password; + guint ttl; +}; + +enum { + PROP_CANDIDATE_0, + PROP_FOUNDATION, + PROP_COMPONENT_ID, + PROP_IP, + PROP_PORT, + PROP_BASE_IP, + PROP_BASE_PORT, + PROP_PROTOCOL, + PROP_PRIORITY, + PROP_TYPE, + PROP_USERNAME, + PROP_PASSWORD, + PROP_TTL, +}; + +static void +purple_media_candidate_init(PurpleMediaCandidate *info) +{ + PurpleMediaCandidatePrivate *priv = + PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(info); + priv->foundation = NULL; + priv->component_id = 0; + priv->ip = NULL; + priv->port = 0; + priv->base_ip = NULL; + priv->proto = PURPLE_MEDIA_NETWORK_PROTOCOL_UDP; + priv->priority = 0; + priv->type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST; + priv->username = NULL; + priv->password = NULL; + priv->ttl = 0; +} + +static void +purple_media_candidate_finalize(GObject *info) +{ + PurpleMediaCandidatePrivate *priv = + PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(info); + + g_free(priv->foundation); + g_free(priv->ip); + g_free(priv->base_ip); + g_free(priv->username); + g_free(priv->password); +} + +static void +purple_media_candidate_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + PurpleMediaCandidatePrivate *priv; + g_return_if_fail(PURPLE_IS_MEDIA_CANDIDATE(object)); + + priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_FOUNDATION: + g_free(priv->foundation); + priv->foundation = g_value_dup_string(value); + break; + case PROP_COMPONENT_ID: + priv->component_id = g_value_get_uint(value); + break; + case PROP_IP: + g_free(priv->ip); + priv->ip = g_value_dup_string(value); + break; + case PROP_PORT: + priv->port = g_value_get_uint(value); + break; + case PROP_BASE_IP: + g_free(priv->base_ip); + priv->base_ip = g_value_dup_string(value); + break; + case PROP_BASE_PORT: + priv->base_port = g_value_get_uint(value); + break; + case PROP_PROTOCOL: + priv->proto = g_value_get_enum(value); + break; + case PROP_PRIORITY: + priv->priority = g_value_get_uint(value); + break; + case PROP_TYPE: + priv->type = g_value_get_enum(value); + break; + case PROP_USERNAME: + g_free(priv->username); + priv->username = g_value_dup_string(value); + break; + case PROP_PASSWORD: + g_free(priv->password); + priv->password = g_value_dup_string(value); + break; + case PROP_TTL: + priv->ttl = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( + object, prop_id, pspec); + break; + } +} + +static void +purple_media_candidate_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + PurpleMediaCandidatePrivate *priv; + g_return_if_fail(PURPLE_IS_MEDIA_CANDIDATE(object)); + + priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_FOUNDATION: + g_value_set_string(value, priv->foundation); + break; + case PROP_COMPONENT_ID: + g_value_set_uint(value, priv->component_id); + break; + case PROP_IP: + g_value_set_string(value, priv->ip); + break; + case PROP_PORT: + g_value_set_uint(value, priv->port); + break; + case PROP_BASE_IP: + g_value_set_string(value, priv->base_ip); + break; + case PROP_BASE_PORT: + g_value_set_uint(value, priv->base_port); + break; + case PROP_PROTOCOL: + g_value_set_enum(value, priv->proto); + break; + case PROP_PRIORITY: + g_value_set_uint(value, priv->priority); + break; + case PROP_TYPE: + g_value_set_enum(value, priv->type); + break; + case PROP_USERNAME: + g_value_set_string(value, priv->username); + break; + case PROP_PASSWORD: + g_value_set_string(value, priv->password); + break; + case PROP_TTL: + g_value_set_uint(value, priv->ttl); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( + object, prop_id, pspec); + break; + } +} + +static void +purple_media_candidate_class_init(PurpleMediaCandidateClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + + gobject_class->finalize = purple_media_candidate_finalize; + gobject_class->set_property = purple_media_candidate_set_property; + gobject_class->get_property = purple_media_candidate_get_property; + + g_object_class_install_property(gobject_class, PROP_FOUNDATION, + g_param_spec_string("foundation", + "Foundation", + "The foundation of the candidate.", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_COMPONENT_ID, + g_param_spec_uint("component-id", + "Component ID", + "The component id of the candidate.", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_IP, + g_param_spec_string("ip", + "IP Address", + "The IP address of the candidate.", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_PORT, + g_param_spec_uint("port", + "Port", + "The port of the candidate.", + 0, G_MAXUINT16, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_BASE_IP, + g_param_spec_string("base-ip", + "Base IP", + "The internal IP address of the candidate.", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_BASE_PORT, + g_param_spec_uint("base-port", + "Base Port", + "The internal port of the candidate.", + 0, G_MAXUINT16, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_PROTOCOL, + g_param_spec_enum("protocol", + "Protocol", + "The protocol of the candidate.", + PURPLE_TYPE_MEDIA_NETWORK_PROTOCOL, + PURPLE_MEDIA_NETWORK_PROTOCOL_UDP, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_PRIORITY, + g_param_spec_uint("priority", + "Priority", + "The priority of the candidate.", + 0, G_MAXUINT32, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_TYPE, + g_param_spec_enum("type", + "Type", + "The type of the candidate.", + PURPLE_TYPE_MEDIA_CANDIDATE_TYPE, + PURPLE_MEDIA_CANDIDATE_TYPE_HOST, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_USERNAME, + g_param_spec_string("username", + "Username", + "The username used to connect to the candidate.", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_PASSWORD, + g_param_spec_string("password", + "Password", + "The password use to connect to the candidate.", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_TTL, + g_param_spec_uint("ttl", + "TTL", + "The TTL of the candidate.", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + + g_type_class_add_private(klass, sizeof(PurpleMediaCandidatePrivate)); +} + +G_DEFINE_TYPE(PurpleMediaCandidate, + purple_media_candidate, G_TYPE_OBJECT); +#else +GType +purple_media_candidate_get_type() +{ + return G_TYPE_NONE; +} +#endif + +PurpleMediaCandidate * +purple_media_candidate_new(const gchar *foundation, guint component_id, + PurpleMediaCandidateType type, + PurpleMediaNetworkProtocol proto, + const gchar *ip, guint port) +{ + return g_object_new(PURPLE_TYPE_MEDIA_CANDIDATE, + "foundation", foundation, + "component-id", component_id, + "type", type, + "protocol", proto, + "ip", ip, + "port", port, NULL); +} + +static PurpleMediaCandidate * +purple_media_candidate_copy(PurpleMediaCandidate *candidate) +{ +#ifdef USE_VV + PurpleMediaCandidatePrivate *priv; + PurpleMediaCandidate *new_candidate; + + if (candidate == NULL) + return NULL; + + priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(candidate); + + new_candidate = purple_media_candidate_new(priv->foundation, + priv->component_id, priv->type, priv->proto, + priv->ip, priv->port); + g_object_set(new_candidate, + "base-ip", priv->base_ip, + "base-port", priv->base_port, + "priority", priv->priority, + "username", priv->username, + "password", priv->password, + "ttl", priv->ttl, NULL); + return new_candidate; +#else + return NULL; +#endif +} + +#ifdef USE_VV +static FsCandidate * +purple_media_candidate_to_fs(PurpleMediaCandidate *candidate) +{ + PurpleMediaCandidatePrivate *priv; + FsCandidate *fscandidate; + + if (candidate == NULL) + return NULL; + + priv = PURPLE_MEDIA_CANDIDATE_GET_PRIVATE(candidate); + + fscandidate = fs_candidate_new(priv->foundation, + priv->component_id, priv->type, + priv->proto, priv->ip, priv->port); + + fscandidate->base_ip = g_strdup(priv->base_ip); + fscandidate->base_port = priv->base_port; + fscandidate->priority = priv->priority; + fscandidate->username = g_strdup(priv->username); + fscandidate->password = g_strdup(priv->password); + fscandidate->ttl = priv->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); + g_object_set(candidate, + "base-ip", fscandidate->base_ip, + "base-port", fscandidate->base_port, + "priority", fscandidate->priority, + "username", fscandidate->username, + "password", fscandidate->password, + "ttl", fscandidate->ttl, NULL); + 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; +} +#endif + +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, + purple_media_candidate_copy(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_object_unref(candidates->data); + } +} + +gchar * +purple_media_candidate_get_foundation(PurpleMediaCandidate *candidate) +{ + gchar *foundation; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); + g_object_get(candidate, "foundation", &foundation, NULL); + return foundation; +} + +guint +purple_media_candidate_get_component_id(PurpleMediaCandidate *candidate) +{ + guint component_id; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); + g_object_get(candidate, "component-id", &component_id, NULL); + return component_id; +} + +gchar * +purple_media_candidate_get_ip(PurpleMediaCandidate *candidate) +{ + gchar *ip; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); + g_object_get(candidate, "ip", &ip, NULL); + return ip; +} + +guint16 +purple_media_candidate_get_port(PurpleMediaCandidate *candidate) +{ + guint port; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); + g_object_get(candidate, "port", &port, NULL); + return port; +} + +gchar * +purple_media_candidate_get_base_ip(PurpleMediaCandidate *candidate) +{ + gchar *base_ip; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); + g_object_get(candidate, "base-ip", &base_ip, NULL); + return base_ip; +} + +guint16 +purple_media_candidate_get_base_port(PurpleMediaCandidate *candidate) +{ + guint base_port; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); + g_object_get(candidate, "base_port", &base_port, NULL); + return base_port; +} + +PurpleMediaNetworkProtocol +purple_media_candidate_get_protocol(PurpleMediaCandidate *candidate) +{ + PurpleMediaNetworkProtocol protocol; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), + PURPLE_MEDIA_NETWORK_PROTOCOL_UDP); + g_object_get(candidate, "protocol", &protocol, NULL); + return protocol; +} + +guint32 +purple_media_candidate_get_priority(PurpleMediaCandidate *candidate) +{ + guint priority; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); + g_object_get(candidate, "priority", &priority, NULL); + return priority; +} + +PurpleMediaCandidateType +purple_media_candidate_get_candidate_type(PurpleMediaCandidate *candidate) +{ + PurpleMediaCandidateType type; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), + PURPLE_MEDIA_CANDIDATE_TYPE_HOST); + g_object_get(candidate, "type", &type, NULL); + return type; +} + +gchar * +purple_media_candidate_get_username(PurpleMediaCandidate *candidate) +{ + gchar *username; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); + g_object_get(candidate, "username", &username, NULL); + return username; +} + +gchar * +purple_media_candidate_get_password(PurpleMediaCandidate *candidate) +{ + gchar *password; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), NULL); + g_object_get(candidate, "password", &password, NULL); + return password; +} + +guint +purple_media_candidate_get_ttl(PurpleMediaCandidate *candidate) +{ + guint ttl; + g_return_val_if_fail(PURPLE_IS_MEDIA_CANDIDATE(candidate), 0); + g_object_get(candidate, "ttl", &ttl, NULL); + return ttl; +} + +#ifdef USE_VV +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; +} +#endif + +/* + * PurpleMediaCodec + */ + +struct _PurpleMediaCodecClass +{ + GObjectClass parent_class; +}; + +struct _PurpleMediaCodec +{ + GObject parent; +}; + +#ifdef USE_VV +struct _PurpleMediaCodecPrivate +{ + gint id; + char *encoding_name; + PurpleMediaSessionType media_type; + guint clock_rate; + guint channels; + GList *optional_params; +}; + +enum { + PROP_CODEC_0, + PROP_ID, + PROP_ENCODING_NAME, + PROP_MEDIA_TYPE, + PROP_CLOCK_RATE, + PROP_CHANNELS, + PROP_OPTIONAL_PARAMS, +}; + +static void +purple_media_codec_init(PurpleMediaCodec *info) +{ + PurpleMediaCodecPrivate *priv = + PURPLE_MEDIA_CODEC_GET_PRIVATE(info); + priv->encoding_name = NULL; + priv->optional_params = NULL; +} + +static void +purple_media_codec_finalize(GObject *info) +{ + PurpleMediaCodecPrivate *priv = + PURPLE_MEDIA_CODEC_GET_PRIVATE(info); + g_free(priv->encoding_name); + for (; priv->optional_params; priv->optional_params = + g_list_delete_link(priv->optional_params, + priv->optional_params)) { + g_free(priv->optional_params->data); + } +} + +static void +purple_media_codec_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + PurpleMediaCodecPrivate *priv; + g_return_if_fail(PURPLE_IS_MEDIA_CODEC(object)); + + priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_ID: + priv->id = g_value_get_uint(value); + break; + case PROP_ENCODING_NAME: + g_free(priv->encoding_name); + priv->encoding_name = g_value_dup_string(value); + break; + case PROP_MEDIA_TYPE: + priv->media_type = g_value_get_flags(value); + break; + case PROP_CLOCK_RATE: + priv->clock_rate = g_value_get_uint(value); + break; + case PROP_CHANNELS: + priv->channels = g_value_get_uint(value); + break; + case PROP_OPTIONAL_PARAMS: + priv->optional_params = g_value_get_pointer(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( + object, prop_id, pspec); + break; + } +} + +static void +purple_media_codec_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + PurpleMediaCodecPrivate *priv; + g_return_if_fail(PURPLE_IS_MEDIA_CODEC(object)); + + priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_ID: + g_value_set_uint(value, priv->id); + break; + case PROP_ENCODING_NAME: + g_value_set_string(value, priv->encoding_name); + break; + case PROP_MEDIA_TYPE: + g_value_set_flags(value, priv->media_type); + break; + case PROP_CLOCK_RATE: + g_value_set_uint(value, priv->clock_rate); + break; + case PROP_CHANNELS: + g_value_set_uint(value, priv->channels); + break; + case PROP_OPTIONAL_PARAMS: + g_value_set_pointer(value, priv->optional_params); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( + object, prop_id, pspec); + break; + } +} + +static void +purple_media_codec_class_init(PurpleMediaCodecClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + + gobject_class->finalize = purple_media_codec_finalize; + gobject_class->set_property = purple_media_codec_set_property; + gobject_class->get_property = purple_media_codec_get_property; + + g_object_class_install_property(gobject_class, PROP_ID, + g_param_spec_uint("id", + "ID", + "The numeric identifier of the codec.", + 0, G_MAXUINT, 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_ENCODING_NAME, + g_param_spec_string("encoding-name", + "Encoding Name", + "The name of the codec.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_MEDIA_TYPE, + g_param_spec_flags("media-type", + "Media Type", + "Whether this is an audio of video codec.", + PURPLE_TYPE_MEDIA_SESSION_TYPE, + PURPLE_MEDIA_NONE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_CLOCK_RATE, + g_param_spec_uint("clock-rate", + "Create Callback", + "The function called to create this element.", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_CHANNELS, + g_param_spec_uint("channels", + "Channels", + "The number of channels in this codec.", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_OPTIONAL_PARAMS, + g_param_spec_pointer("optional-params", + "Optional Params", + "A list of optional parameters for the codec.", + G_PARAM_READWRITE)); + + g_type_class_add_private(klass, sizeof(PurpleMediaCodecPrivate)); +} + +G_DEFINE_TYPE(PurpleMediaCodec, + purple_media_codec, G_TYPE_OBJECT); +#else +GType +purple_media_codec_get_type() +{ + return G_TYPE_NONE; +} +#endif + +guint +purple_media_codec_get_id(PurpleMediaCodec *codec) +{ + guint id; + g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0); + g_object_get(codec, "id", &id, NULL); + return id; +} + +gchar * +purple_media_codec_get_encoding_name(PurpleMediaCodec *codec) +{ + gchar *name; + g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), NULL); + g_object_get(codec, "encoding-name", &name, NULL); + return name; +} + +guint +purple_media_codec_get_clock_rate(PurpleMediaCodec *codec) +{ + guint clock_rate; + g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0); + g_object_get(codec, "clock-rate", &clock_rate, NULL); + return clock_rate; +} + +guint +purple_media_codec_get_channels(PurpleMediaCodec *codec) +{ + guint channels; + g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), 0); + g_object_get(codec, "channels", &channels, NULL); + return channels; +} + +GList * +purple_media_codec_get_optional_parameters(PurpleMediaCodec *codec) +{ + GList *optional_params; + g_return_val_if_fail(PURPLE_IS_MEDIA_CODEC(codec), NULL); + g_object_get(codec, "optional-params", &optional_params, NULL); + return optional_params; +} + +void +purple_media_codec_add_optional_parameter(PurpleMediaCodec *codec, + const gchar *name, const gchar *value) +{ +#ifdef USE_VV + PurpleMediaCodecPrivate *priv; + PurpleKeyValuePair *new_param; + + g_return_if_fail(codec != NULL); + g_return_if_fail(name != NULL && value != NULL); + + priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); + + new_param = g_new0(PurpleKeyValuePair, 1); + new_param->key = g_strdup(name); + new_param->value = g_strdup(value); + priv->optional_params = g_list_append( + priv->optional_params, new_param); +#endif +} + +void +purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec, + PurpleKeyValuePair *param) +{ +#ifdef USE_VV + PurpleMediaCodecPrivate *priv; + + g_return_if_fail(codec != NULL && param != NULL); + + priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); + + g_free(param->key); + g_free(param->value); + g_free(param); + + priv->optional_params = + g_list_remove(priv->optional_params, param); +#endif +} + +PurpleKeyValuePair * +purple_media_codec_get_optional_parameter(PurpleMediaCodec *codec, + const gchar *name, const gchar *value) +{ +#ifdef USE_VV + PurpleMediaCodecPrivate *priv; + GList *iter; + + g_return_val_if_fail(codec != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); + + for (iter = priv->optional_params; iter; iter = g_list_next(iter)) { + PurpleKeyValuePair *param = iter->data; + if (!g_ascii_strcasecmp(param->key, name) && + (value == NULL || + !g_ascii_strcasecmp(param->value, value))) + return param; + } +#endif + + return NULL; +} + +PurpleMediaCodec * +purple_media_codec_new(int id, const char *encoding_name, + PurpleMediaSessionType media_type, guint clock_rate) +{ + PurpleMediaCodec *codec = + g_object_new(PURPLE_TYPE_MEDIA_CODEC, + "id", id, + "encoding_name", encoding_name, + "media_type", media_type, + "clock-rate", clock_rate, NULL); + return codec; +} + +static PurpleMediaCodec * +purple_media_codec_copy(PurpleMediaCodec *codec) +{ +#ifdef USE_VV + PurpleMediaCodecPrivate *priv; + PurpleMediaCodec *new_codec; + GList *iter; + + if (codec == NULL) + return NULL; + + priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); + + new_codec = purple_media_codec_new(priv->id, priv->encoding_name, + priv->media_type, priv->clock_rate); + g_object_set(codec, "channels", priv->channels, NULL); + + for (iter = priv->optional_params; iter; iter = g_list_next(iter)) { + PurpleKeyValuePair *param = + (PurpleKeyValuePair*)iter->data; + purple_media_codec_add_optional_parameter(new_codec, + param->key, param->value); + } + + return new_codec; +#else + return NULL; +#endif +} + +#ifdef USE_VV +static FsCodec * +purple_media_codec_to_fs(const PurpleMediaCodec *codec) +{ + PurpleMediaCodecPrivate *priv; + FsCodec *new_codec; + GList *iter; + + if (codec == NULL) + return NULL; + + priv = PURPLE_MEDIA_CODEC_GET_PRIVATE(codec); + + new_codec = fs_codec_new(priv->id, priv->encoding_name, + purple_media_to_fs_media_type(priv->media_type), + priv->clock_rate); + new_codec->channels = priv->channels; + + for (iter = priv->optional_params; iter; iter = g_list_next(iter)) { + PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data; + fs_codec_add_optional_parameter(new_codec, + param->key, 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); + g_object_set(new_codec, "channels", codec->channels, NULL); + + 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; +} +#endif + +gchar * +purple_media_codec_to_string(const PurpleMediaCodec *codec) +{ +#ifdef USE_VV + FsCodec *fscodec = purple_media_codec_to_fs(codec); + gchar *str = fs_codec_to_string(fscodec); + fs_codec_destroy(fscodec); + return str; +#else + return g_strdup(""); +#endif +} + +#ifdef USE_VV +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; +} +#endif + +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, + purple_media_codec_copy(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_object_unref(codecs->data); + } +} + +#ifdef USE_VV +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); +} +#endif + +GList * +purple_media_get_session_names(PurpleMedia *media) +{ +#ifdef USE_VV + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + return media->priv->sessions != NULL ? + g_hash_table_get_keys(media->priv->sessions) : NULL; +#else + return NULL; +#endif +} + +#ifdef USE_VV +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)); + g_return_if_fail(GST_IS_ELEMENT(src)); + + 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); + + session->tee = gst_element_factory_make("tee", NULL); + gst_bin_add(GST_BIN(session->media->priv->confbin), session->tee); + + /* This supposedly isn't necessary, but it silences some warnings */ + if (GST_ELEMENT_PARENT(session->media->priv->confbin) + == GST_ELEMENT_PARENT(session->src)) { + GstPad *pad = gst_element_get_static_pad(session->tee, "sink"); + GstPad *ghost = gst_ghost_pad_new(NULL, pad); + gst_object_unref(pad); + gst_pad_set_active(ghost, TRUE); + gst_element_add_pad(session->media->priv->confbin, ghost); + } + + gst_element_link(session->src, session->media->priv->confbin); + 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); + gst_object_unref(session->src); +} +#endif + +#ifdef USE_GSTREAMER +GstElement * +purple_media_get_src(PurpleMedia *media, const gchar *sess_id) +{ +#ifdef USE_VV + 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; +#else + return NULL; +#endif +} +#endif /* USE_GSTREAMER */ + +#ifdef USE_VV +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; +} +#endif + +PurpleAccount * +purple_media_get_account(PurpleMedia *media) +{ +#ifdef USE_VV + PurpleAccount *account; + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + g_object_get(G_OBJECT(media), "account", &account, NULL); + return account; +#else + return NULL; +#endif +} + +gpointer +purple_media_get_prpl_data(PurpleMedia *media) +{ +#ifdef USE_VV + 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; +#else + return NULL; +#endif +} + +void +purple_media_set_prpl_data(PurpleMedia *media, gpointer prpl_data) +{ +#ifdef USE_VV + g_return_if_fail(PURPLE_IS_MEDIA(media)); + g_object_set(G_OBJECT(media), "prpl-data", prpl_data, NULL); +#endif +} + +void +purple_media_error(PurpleMedia *media, const gchar *error, ...) +{ +#ifdef USE_VV + 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); +#endif +} + +void +purple_media_end(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ +#ifdef USE_VV + 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); + } +#endif +} + +void +purple_media_stream_info(PurpleMedia *media, PurpleMediaInfoType type, + const gchar *session_id, const gchar *participant, + gboolean local) +{ +#ifdef USE_VV + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + if (type == PURPLE_MEDIA_INFO_ACCEPT) { + 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; + 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); + } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE || + type == PURPLE_MEDIA_INFO_UNMUTE)) { + GList *sessions; + gboolean active = (type == PURPLE_MEDIA_INFO_MUTE); + + 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_prepend(NULL, + purple_media_get_session( + media, session_id)); + + 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); + } + } + } + + g_signal_emit(media, purple_media_signals[STREAM_INFO], + 0, type, session_id, participant, local); + + if (type == PURPLE_MEDIA_INFO_HANGUP || + type == PURPLE_MEDIA_INFO_REJECT) { + purple_media_end(media, session_id, participant); + } +#endif +} + +#ifdef USE_VV +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); + g_object_unref(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, + stream->session->media, + stream->session->id, + stream->participant); + } 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); +} +#endif /* USE_VV */ + +gboolean +purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, + const gchar *who, PurpleMediaSessionType type, + gboolean initiator, const gchar *transmitter, + guint num_params, GParameter *params) +{ +#ifdef USE_VV + PurpleMediaSession *session; + FsParticipant *participant = NULL; + PurpleMediaStream *stream = NULL; + FsMediaType media_type = purple_media_to_fs_media_type(type); + FsStreamDirection type_direction = + purple_media_to_fs_stream_direction(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; + PurpleMediaSessionType session_type; + GstElement *src = NULL; + + session = g_new0(PurpleMediaSession, 1); + + session->session = fs_conference_new_session( + media->priv->conference, media_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 = type; + session->initiator = initiator; + + 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(media_type, + FS_DIRECTION_SEND); + src = purple_media_manager_get_element( + media->priv->manager, session_type, + media, session->id, who); + if (!GST_IS_ELEMENT(src)) { + purple_debug_error("media", + "Error creating src for session %s\n", + session->id); + purple_media_end(media, session->id, NULL); + return FALSE; + } + + purple_media_set_src(media, session->id, src); + 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); + stream->initiator = initiator; + + /* 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 (purple_media_to_fs_stream_direction(stream->session->type) + != type_direction) { + /* change direction */ + g_object_set(stream->stream, "direction", + type_direction, NULL); + } + } + + return TRUE; +#else + return FALSE; +#endif /* USE_VV */ +} + +PurpleMediaManager * +purple_media_get_manager(PurpleMedia *media) +{ + PurpleMediaManager *ret; + g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); + g_object_get(media, "manager", &ret, NULL); + return ret; +} + +PurpleMediaSessionType +purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id) +{ +#ifdef USE_VV + 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; +#else + return PURPLE_MEDIA_NONE; +#endif +} +/* XXX: Should wait until codecs-ready is TRUE before using this function */ +GList * +purple_media_get_codecs(PurpleMedia *media, const gchar *sess_id) +{ +#ifdef USE_VV + 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; +#else + return NULL; +#endif +} + +GList * +purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *name) +{ +#ifdef USE_VV + 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); +#else + return NULL; +#endif +} + +void +purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id, + const gchar *name, GList *remote_candidates) +{ +#ifdef USE_VV + PurpleMediaStream *stream; + GError *err = NULL; + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + stream = purple_media_get_stream(media, sess_id, name); + + if (stream == NULL) { + purple_debug_error("media", + "purple_media_add_remote_candidates: " + "couldn't find stream %s %s.\n", + sess_id, name); + return; + } + + 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); + } +#endif +} + +#if 0 +/* + * These two functions aren't being used and I'd rather not lock in the API + * until they are needed. If they ever are. + */ + +GList * +purple_media_get_active_local_candidates(PurpleMedia *media, + const gchar *sess_id, const gchar *name) +{ +#ifdef USE_VV + 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); +#else + return NULL; +#endif +} + +GList * +purple_media_get_active_remote_candidates(PurpleMedia *media, + const gchar *sess_id, const gchar *name) +{ +#ifdef USE_VV + 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); +#else + return NULL; +#endif +} +#endif + +gboolean +purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id, const gchar *name, GList *codecs) +{ +#ifdef USE_VV + 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; +#else + return FALSE; +#endif +} + +gboolean +purple_media_candidates_prepared(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ +#ifdef USE_VV + 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; +#else + return FALSE; +#endif +} + +gboolean +purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, PurpleMediaCodec *codec) +{ +#ifdef USE_VV + 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; +#else + return FALSE; +#endif +} + +gboolean +purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id) +{ +#ifdef USE_VV + 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; +#else + return FALSE; +#endif +} + +gboolean +purple_media_is_initiator(PurpleMedia *media, + const gchar *sess_id, const gchar *participant) +{ +#ifdef USE_VV + g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); + + if (sess_id == NULL && participant == NULL) + return media->priv->initiator; + else if (sess_id != NULL && participant == NULL) { + PurpleMediaSession *session = + purple_media_get_session(media, sess_id); + return session != NULL ? session->initiator : FALSE; + } else if (sess_id != NULL && participant != NULL) { + PurpleMediaStream *stream = purple_media_get_stream( + media, sess_id, participant); + return stream != NULL ? stream->initiator : FALSE; + } +#endif + return FALSE; +} + +gboolean +purple_media_accepted(PurpleMedia *media, const gchar *sess_id, + const gchar *participant) +{ +#ifdef USE_VV + 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; +#else + return FALSE; +#endif +} + +void purple_media_set_input_volume(PurpleMedia *media, + const gchar *session_id, double level) +{ +#ifdef USE_VV + 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); + } + } +#endif +} + +void purple_media_set_output_volume(PurpleMedia *media, + const gchar *session_id, const gchar *participant, + double level) +{ +#ifdef USE_VV + 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); + } + } +#endif +} + +gulong +purple_media_set_output_window(PurpleMedia *media, const gchar *session_id, + const gchar *participant, gulong window_id) +{ +#ifdef USE_VV + 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); +#else + return 0; +#endif +} + +void +purple_media_remove_output_windows(PurpleMedia *media) +{ +#ifdef USE_VV + 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); + } +#endif +} + +#ifdef USE_GSTREAMER +GstElement * +purple_media_get_tee(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ +#ifdef USE_VV + 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); +#else + return NULL; +#endif +} +#endif /* USE_GSTREAMER */ + diff -r 78ef23551355 -r 872d30754311 libpurple/media.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/media.h Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,688 @@ +/** + * @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 _PURPLE_MEDIA_H_ +#define _PURPLE_MEDIA_H_ + +#include +#include + +G_BEGIN_DECLS + +#define PURPLE_TYPE_MEDIA_CANDIDATE (purple_media_candidate_get_type()) +#define PURPLE_MEDIA_CANDIDATE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidate)) +#define PURPLE_MEDIA_CANDIDATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidate)) +#define PURPLE_IS_MEDIA_CANDIDATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_CANDIDATE)) +#define PURPLE_IS_MEDIA_CANDIDATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_CANDIDATE)) +#define PURPLE_MEDIA_CANDIDATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_CANDIDATE, PurpleMediaCandidate)) + +#define PURPLE_TYPE_MEDIA_CODEC (purple_media_codec_get_type()) +#define PURPLE_MEDIA_CODEC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodec)) +#define PURPLE_MEDIA_CODEC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodec)) +#define PURPLE_IS_MEDIA_CODEC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_CODEC)) +#define PURPLE_IS_MEDIA_CODEC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_CODEC)) +#define PURPLE_MEDIA_CODEC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_CODEC, PurpleMediaCodec)) + +#define PURPLE_TYPE_MEDIA_SESSION_TYPE (purple_media_session_type_get_type()) +#define PURPLE_TYPE_MEDIA (purple_media_get_type()) +#define PURPLE_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA, PurpleMedia)) +#define PURPLE_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA, PurpleMediaClass)) +#define PURPLE_IS_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA)) +#define PURPLE_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA)) +#define PURPLE_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA, PurpleMediaClass)) + +#define PURPLE_TYPE_MEDIA_CANDIDATE_TYPE (purple_media_candidate_type_get_type()) +#define PURPLE_TYPE_MEDIA_NETWORK_PROTOCOL (purple_media_network_protocol_get_type()) +#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 _PurpleMediaCandidate */ +typedef struct _PurpleMediaCandidate PurpleMediaCandidate; +/** @copydoc _PurpleMediaCodec */ +typedef struct _PurpleMediaCodec PurpleMediaCodec; + +/** 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_ACCEPT, + PURPLE_MEDIA_INFO_REJECT, + PURPLE_MEDIA_INFO_MUTE, + PURPLE_MEDIA_INFO_UNMUTE, + PURPLE_MEDIA_INFO_HOLD, + PURPLE_MEDIA_INFO_UNHOLD, +} 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; + +#include "signals.h" +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets the media session type's GType + * + * @return The media session type's GType. + * + * @since 2.6.0 + */ +GType purple_media_session_type_get_type(void); + +/** + * Gets the media candidate type's GType + * + * @return The media candidate type's GType. + * + * @since 2.6.0 + */ +GType purple_media_candidate_type_get_type(void); + +/** + * Gets the media network protocol's GType + * + * @return The media network protocol's GType. + * + * @since 2.6.0 + */ +GType purple_media_network_protocol_get_type(void); + +/** + * Gets the media class's GType + * + * @return The media class's GType. + * + * @since 2.6.0 + */ +GType purple_media_get_type(void); + +/** + * Gets the type of the state-changed enum + * + * @return The state-changed enum's GType + * + * @since 2.6.0 + */ +GType purple_media_state_changed_get_type(void); + +/** + * Gets the type of the info type enum + * + * @return The info type enum's GType + * + * @since 2.6.0 + */ +GType purple_media_info_type_get_type(void); + +/** + * Gets the type of the media candidate structure. + * + * @return The media canditate's GType + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +PurpleMediaCandidate *purple_media_candidate_new( + const gchar *foundation, guint component_id, + PurpleMediaCandidateType type, + PurpleMediaNetworkProtocol proto, + const gchar *ip, guint port); + +/** + * Copies a GList of PurpleMediaCandidate and its contents. + * + * @param candidates The list of candidates to be copied. + * + * @return The copy of the GList. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +void purple_media_candidate_list_free(GList *candidates); + +gchar *purple_media_candidate_get_foundation(PurpleMediaCandidate *candidate); +guint purple_media_candidate_get_component_id(PurpleMediaCandidate *candidate); +gchar *purple_media_candidate_get_ip(PurpleMediaCandidate *candidate); +guint16 purple_media_candidate_get_port(PurpleMediaCandidate *candidate); +gchar *purple_media_candidate_get_base_ip(PurpleMediaCandidate *candidate); +guint16 purple_media_candidate_get_base_port(PurpleMediaCandidate *candidate); +PurpleMediaNetworkProtocol purple_media_candidate_get_protocol( + PurpleMediaCandidate *candidate); +guint32 purple_media_candidate_get_priority(PurpleMediaCandidate *candidate); +PurpleMediaCandidateType purple_media_candidate_get_candidate_type( + PurpleMediaCandidate *candidate); +gchar *purple_media_candidate_get_username(PurpleMediaCandidate *candidate); +gchar *purple_media_candidate_get_password(PurpleMediaCandidate *candidate); +guint purple_media_candidate_get_ttl(PurpleMediaCandidate *candidate); + +/** + * Gets the type of the media codec structure. + * + * @return The media codec's GType + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +PurpleMediaCodec *purple_media_codec_new(int id, const char *encoding_name, + PurpleMediaSessionType media_type, guint clock_rate); + +guint purple_media_codec_get_id(PurpleMediaCodec *codec); +gchar *purple_media_codec_get_encoding_name(PurpleMediaCodec *codec); +guint purple_media_codec_get_clock_rate(PurpleMediaCodec *codec); +guint purple_media_codec_get_channels(PurpleMediaCodec *codec); +GList *purple_media_codec_get_optional_parameters(PurpleMediaCodec *codec); + +/** + * Creates a string representation of the codec. + * + * @param codec The codec to create the string of. + * + * @return The new string representation. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +void purple_media_codec_remove_optional_parameter(PurpleMediaCodec *codec, + PurpleKeyValuePair *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. + * + * @since 2.6.0 + */ +PurpleKeyValuePair *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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +GList *purple_media_get_session_names(PurpleMedia *media); + +/** + * Gets the PurpleAccount this media session is on. + * + * @param media The media session to retrieve the account from. + * + * @return The account retrieved. + * + * @since 2.6.0 + */ +PurpleAccount *purple_media_get_account(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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +void purple_media_error(PurpleMedia *media, const gchar *error, ...); + +/** + * 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. + * + * @since 2.6.0 + */ +void purple_media_end(PurpleMedia *media, const gchar *session_id, + const gchar *participant); + +/** + * Signals different information about the given stream. + * + * @param media The media instance to containing the stream to signal. + * @param type The type of info being signaled. + * @param session_id The id of the session of the stream being signaled. + * @param participant The participant of the stream being signaled. + * @param local TRUE if the info originated locally, FALSE if on the remote end. + * + * @since 2.6.0 + */ +void purple_media_stream_info(PurpleMedia *media, PurpleMediaInfoType type, + const gchar *session_id, const gchar *participant, + gboolean local); + +/** + * 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 initiator Whether or not the local user initiated the stream. + * @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. + * + * @since 2.6.0 + */ +gboolean purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, + const gchar *who, PurpleMediaSessionType type, + gboolean initiator, const gchar *transmitter, + guint num_params, GParameter *params); + +/** + * 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. + * + * @since 2.6.0 + */ +PurpleMediaSessionType purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id); + +/** + * Gets the PurpleMediaManager this media session is a part of. + * + * @param media The media object to get the manager instance from. + * + * @return The PurpleMediaManager instance retrieved. + * + * @since 2.6.0 + */ +struct _PurpleMediaManager *purple_media_get_manager(PurpleMedia *media); + +/** + * 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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +GList *purple_media_get_local_candidates(PurpleMedia *media, + const gchar *sess_id, + const gchar *name); + +#if 0 +/* + * These two functions aren't being used and I'd rather not lock in the API + * until they are needed. If they ever are. + */ + +/** + * 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); +#endif + +/** + * Sets 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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +gboolean purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id); + +/** + * Gets whether the local user is the conference/session/stream's initiator. + * + * @param media The media instance to find the session in. + * @param sess_id The session id of the session to check. + * @param participant The participant of the stream to check. + * + * @return TRUE if the local user is the stream's initator, else FALSE. + * + * @since 2.6.0 + */ +gboolean purple_media_is_initiator(PurpleMedia *media, + const gchar *sess_id, const gchar *participant); + +/** + * 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. + * + * @since 2.6.0 + */ +gboolean purple_media_accepted(PurpleMedia *media, const gchar *sess_id, + const gchar *participant); + +/** + * 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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +void purple_media_remove_output_windows(PurpleMedia *media); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* _PURPLE_MEDIA_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/mediamanager.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/mediamanager.c Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,1127 @@ +/** + * @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 "account.h" +#include "debug.h" +#include "media.h" +#include "mediamanager.h" + +#ifdef USE_GSTREAMER +#include "marshallers.h" +#include "media-gst.h" +#endif + +#ifdef USE_VV + +#include +#include + +/** @copydoc _PurpleMediaManagerPrivate */ +typedef struct _PurpleMediaManagerPrivate PurpleMediaManagerPrivate; +/** @copydoc _PurpleMediaOutputWindow */ +typedef struct _PurpleMediaOutputWindow PurpleMediaOutputWindow; +/** @copydoc _PurpleMediaManagerPrivate */ +typedef struct _PurpleMediaElementInfoPrivate PurpleMediaElementInfoPrivate; + +/** 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. */ +}; + +struct _PurpleMediaOutputWindow +{ + gulong id; + PurpleMedia *media; + gchar *session_id; + gchar *participant; + gulong window_id; + GstElement *sink; +}; + +struct _PurpleMediaManagerPrivate +{ + GstElement *pipeline; + PurpleMediaCaps ui_caps; + 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)) +#define PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_ELEMENT_INFO, PurpleMediaElementInfoPrivate)) + +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}; +#endif + +GType +purple_media_manager_get_type() +{ +#ifdef USE_VV + 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; +#else + return G_TYPE_NONE; +#endif +} + +#ifdef USE_VV +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)) { + g_object_unref(priv->elements->data); + } + parent_class->finalize(media); +} +#endif + +PurpleMediaManager * +purple_media_manager_get() +{ +#ifdef USE_VV + static PurpleMediaManager *manager = NULL; + + if (manager == NULL) + manager = PURPLE_MEDIA_MANAGER(g_object_new(purple_media_manager_get_type(), NULL)); + return manager; +#else + return NULL; +#endif +} + +#ifdef USE_VV +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; +} +#endif + +#ifdef USE_GSTREAMER +GstElement * +purple_media_manager_get_pipeline(PurpleMediaManager *manager) +{ +#ifdef USE_VV + 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; +#else + return NULL; +#endif +} +#endif /* USE_GSTREAMER */ + +PurpleMedia * +purple_media_manager_create_media(PurpleMediaManager *manager, + PurpleAccount *account, + const char *conference_type, + const char *remote_user, + gboolean initiator) +{ +#ifdef USE_VV + 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, account, + _("Error creating conference.")); + purple_debug_error("media", "Conference == NULL\n"); + return NULL; + } + + media = PURPLE_MEDIA(g_object_new(purple_media_get_type(), + "manager", manager, + "account", account, + "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, account, + _("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, account, 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; +#else + return NULL; +#endif +} + +GList * +purple_media_manager_get_media(PurpleMediaManager *manager) +{ +#ifdef USE_VV + return manager->priv->medias; +#else + return NULL; +#endif +} + +GList * +purple_media_manager_get_media_by_account(PurpleMediaManager *manager, + PurpleAccount *account) +{ +#ifdef USE_VV + 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_account(iter->data) == account) { + media = g_list_prepend(media, iter->data); + } + } + + return media; +#else + return NULL; +#endif +} + +void +purple_media_manager_remove_media(PurpleMediaManager *manager, + PurpleMedia *media) +{ +#ifdef USE_VV + GList *list = g_list_find(manager->priv->medias, media); + if (list) + manager->priv->medias = + g_list_delete_link(manager->priv->medias, list); +#endif +} + +#ifdef USE_VV +static void +request_pad_unlinked_cb(GstPad *pad, GstPad *peer, gpointer user_data) +{ + GstElement *parent = GST_ELEMENT_PARENT(pad); + GstIterator *iter; + GstPad *remaining_pad; + + gst_element_release_request_pad(GST_ELEMENT_PARENT(pad), pad); + iter = gst_element_iterate_pads(parent); + + if (gst_iterator_next(iter, (gpointer)&remaining_pad) + == GST_ITERATOR_DONE) { + gst_element_set_locked_state(parent, TRUE); + gst_element_set_state(parent, GST_STATE_NULL); + gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(parent)), parent); + } + + gst_iterator_free(iter); +} +#endif + +#ifdef USE_GSTREAMER +GstElement * +purple_media_manager_get_element(PurpleMediaManager *manager, + PurpleMediaSessionType type, PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ +#ifdef USE_VV + GstElement *ret = NULL; + PurpleMediaElementInfo *info = NULL; + PurpleMediaElementType element_type; + + if (type & PURPLE_MEDIA_SEND_AUDIO) + info = manager->priv->audio_src; + else if (type & PURPLE_MEDIA_RECV_AUDIO) + info = manager->priv->audio_sink; + else if (type & PURPLE_MEDIA_SEND_VIDEO) + info = manager->priv->video_src; + else if (type & PURPLE_MEDIA_RECV_VIDEO) + info = manager->priv->video_sink; + + if (info == NULL) + return NULL; + + element_type = purple_media_element_info_get_element_type(info); + + if (element_type & PURPLE_MEDIA_ELEMENT_UNIQUE && + element_type & PURPLE_MEDIA_ELEMENT_SRC) { + GstElement *tee; + GstPad *pad; + GstPad *ghost; + gchar *id = purple_media_element_info_get_id(info); + + ret = gst_bin_get_by_name(GST_BIN( + purple_media_manager_get_pipeline( + manager)), id); + + if (ret == NULL) { + GstElement *bin, *fakesink; + ret = purple_media_element_info_call_create(info, + media, session_id, participant); + bin = gst_bin_new(id); + tee = gst_element_factory_make("tee", "tee"); + gst_bin_add_many(GST_BIN(bin), ret, tee, NULL); + gst_element_link(ret, tee); + + /* + * This shouldn't be necessary, but it stops it from + * giving a not-linked error upon destruction + */ + fakesink = gst_element_factory_make("fakesink", NULL); + g_object_set(fakesink, "sync", FALSE, NULL); + gst_bin_add(GST_BIN(bin), fakesink); + gst_element_link(tee, fakesink); + + ret = bin; + gst_element_set_locked_state(ret, TRUE); + gst_object_ref(ret); + gst_bin_add(GST_BIN(purple_media_manager_get_pipeline( + manager)), ret); + } + g_free(id); + + tee = gst_bin_get_by_name(GST_BIN(ret), "tee"); + pad = gst_element_get_request_pad(tee, "src%d"); + gst_object_unref(tee); + ghost = gst_ghost_pad_new(NULL, pad); + gst_object_unref(pad); + g_signal_connect(GST_PAD(ghost), "unlinked", + G_CALLBACK(request_pad_unlinked_cb), NULL); + gst_pad_set_active(ghost, TRUE); + gst_element_add_pad(ret, ghost); + } else { + ret = purple_media_element_info_call_create(info, + media, session_id, participant); + } + + if (ret == NULL) + purple_debug_error("media", "Error creating source or sink\n"); + + return ret; +#else + return NULL; +#endif +} + +PurpleMediaElementInfo * +purple_media_manager_get_element_info(PurpleMediaManager *manager, + const gchar *id) +{ +#ifdef USE_VV + GList *iter; + + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); + + iter = manager->priv->elements; + + for (; iter; iter = g_list_next(iter)) { + gchar *element_id = + purple_media_element_info_get_id(iter->data); + if (!strcmp(element_id, id)) { + g_free(element_id); + g_object_ref(iter->data); + return iter->data; + } + g_free(element_id); + } +#endif + + return NULL; +} + +gboolean +purple_media_manager_register_element(PurpleMediaManager *manager, + PurpleMediaElementInfo *info) +{ +#ifdef USE_VV + PurpleMediaElementInfo *info2; + gchar *id; + + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); + g_return_val_if_fail(info != NULL, FALSE); + + id = purple_media_element_info_get_id(info); + info2 = purple_media_manager_get_element_info(manager, id); + g_free(id); + + if (info2 != NULL) { + g_object_unref(info2); + return FALSE; + } + + manager->priv->elements = + g_list_prepend(manager->priv->elements, info); + return TRUE; +#else + return FALSE; +#endif +} + +gboolean +purple_media_manager_unregister_element(PurpleMediaManager *manager, + const gchar *id) +{ +#ifdef USE_VV + 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) { + g_object_unref(info); + 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); + g_object_unref(info); + return TRUE; +#else + return FALSE; +#endif +} + +gboolean +purple_media_manager_set_active_element(PurpleMediaManager *manager, + PurpleMediaElementInfo *info) +{ +#ifdef USE_VV + PurpleMediaElementInfo *info2; + PurpleMediaElementType type; + gboolean ret = FALSE; + gchar *id; + + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); + g_return_val_if_fail(info != NULL, FALSE); + + id = purple_media_element_info_get_id(info); + info2 = purple_media_manager_get_element_info(manager, id); + g_free(id); + + if (info2 == NULL) + purple_media_manager_register_element(manager, info); + else + g_object_unref(info2); + + type = purple_media_element_info_get_element_type(info); + + if (type & PURPLE_MEDIA_ELEMENT_SRC) { + if (type & PURPLE_MEDIA_ELEMENT_AUDIO) { + manager->priv->audio_src = info; + ret = TRUE; + } + if (type & PURPLE_MEDIA_ELEMENT_VIDEO) { + manager->priv->video_src = info; + ret = TRUE; + } + } + if (type & PURPLE_MEDIA_ELEMENT_SINK) { + if (type & PURPLE_MEDIA_ELEMENT_AUDIO) { + manager->priv->audio_sink = info; + ret = TRUE; + } + if (type & PURPLE_MEDIA_ELEMENT_VIDEO) { + manager->priv->video_sink = info; + ret = TRUE; + } + } + + return ret; +#else + return FALSE; +#endif +} + +PurpleMediaElementInfo * +purple_media_manager_get_active_element(PurpleMediaManager *manager, + PurpleMediaElementType type) +{ +#ifdef USE_VV + 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; + } +#endif + + return NULL; +} +#endif /* USE_GSTREAMER */ + +#ifdef USE_VV +static void +window_id_cb(GstBus *bus, GstMessage *msg, PurpleMediaOutputWindow *ow) +{ + GstElement *sink; + + if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT || + !gst_structure_has_name(msg->structure, + "prepare-xwindow-id")) + return; + + sink = GST_ELEMENT(GST_MESSAGE_SRC(msg)); + while (sink != ow->sink) { + if (sink == NULL) + return; + sink = GST_ELEMENT_PARENT(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); +} +#endif + +gboolean +purple_media_manager_create_output_window(PurpleMediaManager *manager, + PurpleMedia *media, const gchar *session_id, + const gchar *participant) +{ +#ifdef USE_VV + 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, + ow->media, ow->session_id, + ow->participant); + + 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; +#else + return FALSE; +#endif +} + +gulong +purple_media_manager_set_output_window(PurpleMediaManager *manager, + PurpleMedia *media, const gchar *session_id, + const gchar *participant, gulong window_id) +{ +#ifdef USE_VV + 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; +#else + return 0; +#endif +} + +gboolean +purple_media_manager_remove_output_window(PurpleMediaManager *manager, + gulong output_window_id) +{ +#ifdef USE_VV + 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; +#else + return FALSE; +#endif +} + +void +purple_media_manager_remove_output_windows(PurpleMediaManager *manager, + PurpleMedia *media, const gchar *session_id, + const gchar *participant) +{ +#ifdef USE_VV + 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 +} + +void +purple_media_manager_set_ui_caps(PurpleMediaManager *manager, + PurpleMediaCaps caps) +{ +#ifdef USE_VV + g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager)); + manager->priv->ui_caps = caps; +#endif +} + +PurpleMediaCaps +purple_media_manager_get_ui_caps(PurpleMediaManager *manager) +{ +#ifdef USE_VV + g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), + PURPLE_MEDIA_CAPS_NONE); + return manager->priv->ui_caps; +#else + return PURPLE_MEDIA_CAPS_NONE; +#endif +} + +#ifdef USE_GSTREAMER + +/* + * PurpleMediaElementType + */ + +GType +purple_media_element_type_get_type() +{ + static GType type = 0; + if (type == 0) { + static const GFlagsValue values[] = { + { PURPLE_MEDIA_ELEMENT_NONE, + "PURPLE_MEDIA_ELEMENT_NONE", "none" }, + { PURPLE_MEDIA_ELEMENT_AUDIO, + "PURPLE_MEDIA_ELEMENT_AUDIO", "audio" }, + { PURPLE_MEDIA_ELEMENT_VIDEO, + "PURPLE_MEDIA_ELEMENT_VIDEO", "video" }, + { PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO, + "PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO", + "audio-video" }, + { PURPLE_MEDIA_ELEMENT_NO_SRCS, + "PURPLE_MEDIA_ELEMENT_NO_SRCS", "no-srcs" }, + { PURPLE_MEDIA_ELEMENT_ONE_SRC, + "PURPLE_MEDIA_ELEMENT_ONE_SRC", "one-src" }, + { PURPLE_MEDIA_ELEMENT_MULTI_SRC, + "PURPLE_MEDIA_ELEMENT_MULTI_SRC", + "multi-src" }, + { PURPLE_MEDIA_ELEMENT_REQUEST_SRC, + "PURPLE_MEDIA_ELEMENT_REQUEST_SRC", + "request-src" }, + { PURPLE_MEDIA_ELEMENT_NO_SINKS, + "PURPLE_MEDIA_ELEMENT_NO_SINKS", "no-sinks" }, + { PURPLE_MEDIA_ELEMENT_ONE_SINK, + "PURPLE_MEDIA_ELEMENT_ONE_SINK", "one-sink" }, + { PURPLE_MEDIA_ELEMENT_MULTI_SINK, + "PURPLE_MEDIA_ELEMENT_MULTI_SINK", + "multi-sink" }, + { PURPLE_MEDIA_ELEMENT_REQUEST_SINK, + "PURPLE_MEDIA_ELEMENT_REQUEST_SINK", + "request-sink" }, + { PURPLE_MEDIA_ELEMENT_UNIQUE, + "PURPLE_MEDIA_ELEMENT_UNIQUE", "unique" }, + { PURPLE_MEDIA_ELEMENT_SRC, + "PURPLE_MEDIA_ELEMENT_SRC", "src" }, + { PURPLE_MEDIA_ELEMENT_SINK, + "PURPLE_MEDIA_ELEMENT_SINK", "sink" }, + { 0, NULL, NULL } + }; + type = g_flags_register_static( + "PurpleMediaElementType", values); + } + return type; +} + +/* + * PurpleMediaElementInfo + */ + +struct _PurpleMediaElementInfoClass +{ + GObjectClass parent_class; +}; + +struct _PurpleMediaElementInfo +{ + GObject parent; +}; + +#ifdef USE_VV +struct _PurpleMediaElementInfoPrivate +{ + gchar *id; + gchar *name; + PurpleMediaElementType type; + PurpleMediaElementCreateCallback create; +}; + +enum { + PROP_0, + PROP_ID, + PROP_NAME, + PROP_TYPE, + PROP_CREATE_CB, +}; + +static void +purple_media_element_info_init(PurpleMediaElementInfo *info) +{ + PurpleMediaElementInfoPrivate *priv = + PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info); + priv->id = NULL; + priv->name = NULL; + priv->type = PURPLE_MEDIA_ELEMENT_NONE; + priv->create = NULL; +} + +static void +purple_media_element_info_finalize(GObject *info) +{ + PurpleMediaElementInfoPrivate *priv = + PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info); + g_free(priv->id); + g_free(priv->name); +} + +static void +purple_media_element_info_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + PurpleMediaElementInfoPrivate *priv; + g_return_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(object)); + + priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_ID: + g_free(priv->id); + priv->id = g_value_dup_string(value); + break; + case PROP_NAME: + g_free(priv->name); + priv->name = g_value_dup_string(value); + break; + case PROP_TYPE: { + priv->type = g_value_get_flags(value); + break; + } + case PROP_CREATE_CB: + priv->create = g_value_get_pointer(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( + object, prop_id, pspec); + break; + } +} + +static void +purple_media_element_info_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + PurpleMediaElementInfoPrivate *priv; + g_return_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(object)); + + priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_ID: + g_value_set_string(value, priv->id); + break; + case PROP_NAME: + g_value_set_string(value, priv->name); + break; + case PROP_TYPE: + g_value_set_flags(value, priv->type); + break; + case PROP_CREATE_CB: + g_value_set_pointer(value, priv->create); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( + object, prop_id, pspec); + break; + } +} + +static void +purple_media_element_info_class_init(PurpleMediaElementInfoClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + + gobject_class->finalize = purple_media_element_info_finalize; + gobject_class->set_property = purple_media_element_info_set_property; + gobject_class->get_property = purple_media_element_info_get_property; + + g_object_class_install_property(gobject_class, PROP_ID, + g_param_spec_string("id", + "ID", + "The unique identifier of the element.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_NAME, + g_param_spec_string("name", + "Name", + "The friendly/display name of this element.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_TYPE, + g_param_spec_flags("type", + "Element Type", + "The type of element this is.", + PURPLE_TYPE_MEDIA_ELEMENT_TYPE, + PURPLE_MEDIA_ELEMENT_NONE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_CREATE_CB, + g_param_spec_pointer("create-cb", + "Create Callback", + "The function called to create this element.", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_type_class_add_private(klass, sizeof(PurpleMediaElementInfoPrivate)); +} + +G_DEFINE_TYPE(PurpleMediaElementInfo, + purple_media_element_info, G_TYPE_OBJECT); +#else +GType +purple_media_element_info_get_type() +{ + return G_TYPE_NONE; +} +#endif + +gchar * +purple_media_element_info_get_id(PurpleMediaElementInfo *info) +{ +#ifdef USE_VV + gchar *id; + g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL); + g_object_get(info, "id", &id, NULL); + return id; +#else + return NULL; +#endif +} + +gchar * +purple_media_element_info_get_name(PurpleMediaElementInfo *info) +{ +#ifdef USE_VV + gchar *name; + g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL); + g_object_get(info, "name", &name, NULL); + return name; +#else + return NULL; +#endif +} + +PurpleMediaElementType +purple_media_element_info_get_element_type(PurpleMediaElementInfo *info) +{ +#ifdef USE_VV + PurpleMediaElementType type; + g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), + PURPLE_MEDIA_ELEMENT_NONE); + g_object_get(info, "type", &type, NULL); + return type; +#else + return PURPLE_MEDIA_ELEMENT_NONE; +#endif +} + +GstElement * +purple_media_element_info_call_create(PurpleMediaElementInfo *info, + PurpleMedia *media, const gchar *session_id, + const gchar *participant) +{ +#ifdef USE_VV + PurpleMediaElementCreateCallback create; + g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL); + g_object_get(info, "create-cb", &create, NULL); + if (create) + return create(media, session_id, participant); +#endif + return NULL; +} + +#endif /* USE_GSTREAMER */ + diff -r 78ef23551355 -r 872d30754311 libpurple/mediamanager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/mediamanager.h Mon Apr 20 00:10:51 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 _PURPLE_MEDIA_MANAGER_H_ +#define _PURPLE_MEDIA_MANAGER_H_ + +#include +#include + +/** @copydoc _PurpleMediaManager */ +typedef struct _PurpleMediaManager PurpleMediaManager; +/** @copydoc _PurpleMediaManagerClass */ +typedef struct _PurpleMediaManagerClass PurpleMediaManagerClass; + +#include "account.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)) + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @cname Media Manager API */ +/**************************************************************************/ +/*@{*/ + +/** + * Gets the media manager's GType. + * + * @return The media manager's GType. + * + * @since 2.6.0 + */ +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. + * + * @since 2.6.0 + */ +PurpleMediaManager *purple_media_manager_get(void); + +/** + * Creates a media session. + * + * @param manager The media manager to create the session under. + * @param account The account 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. + * + * @since 2.6.0 + */ +PurpleMedia *purple_media_manager_create_media(PurpleMediaManager *manager, + PurpleAccount *account, + 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. + * + * @since 2.6.0 + */ +GList *purple_media_manager_get_media(PurpleMediaManager *manager); + +/** + * Gets all of the media sessions for a given account. + * + * @param manager The media manager to get the sessions from. + * @param account The account the sessions are on. + * + * @return A list of the media sessions on the given account. + * + * @since 2.6.0 + */ +GList *purple_media_manager_get_media_by_account( + PurpleMediaManager *manager, PurpleAccount *account); + +/** + * 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. + * + * @since 2.6.0 + */ +void +purple_media_manager_remove_media(PurpleMediaManager *manager, + PurpleMedia *media); + +/** + * Signals that output windows should be created for the chosen stream. + * + * This shouldn't be called outside of mediamanager.c and media.c + * + * @param manager Manager the output windows are registered with. + * @param media Media session the output windows are registered for. + * @param session_id The session the output windows are registered with. + * @param participant The participant the output windows are registered with. + * + * @return TRUE if it succeeded, FALSE if it failed. + * + * @since 2.6.0 + */ +gboolean purple_media_manager_create_output_window( + PurpleMediaManager *manager, PurpleMedia *media, + const gchar *session_id, const gchar *participant); + +/** + * Registers a video output window to be created for a given stream. + * + * @param manager The manager to register the output window with. + * @param media The media instance to find the stream in. + * @param session_id The session the stream is associated with. + * @param participant The participant the stream is associated with. + * @param window_id The window ID to embed the video in. + * + * @return A unique ID to the registered output window, 0 if it failed. + * + * @since 2.6.0 + */ +gulong purple_media_manager_set_output_window(PurpleMediaManager *manager, + PurpleMedia *media, const gchar *session_id, + const gchar *participant, gulong window_id); + +/** + * Remove a previously registerd output window. + * + * @param manager The manager the output window was registered with. + * @param output_window_id The ID of the output window. + * + * @return TRUE if it found the output window and was successful, else FALSE. + * + * @since 2.6.0 + */ +gboolean purple_media_manager_remove_output_window( + PurpleMediaManager *manager, gulong output_window_id); + +/** + * Remove all output windows for a given conference/session/participant/stream. + * + * @param manager The manager the output windows were registered with. + * @param media The media instance the output windows were registered for. + * @param session_id The session the output windows were registered for. + * @param participant The participant the output windows were registered for. + * + * @since 2.6.0 + */ +void purple_media_manager_remove_output_windows( + PurpleMediaManager *manager, PurpleMedia *media, + const gchar *session_id, const gchar *participant); + +/** + * Sets which media caps the UI supports. + * + * @param manager The manager to set the caps on. + * @param caps The caps to set. + * + * @since 2.6.0 + */ +void purple_media_manager_set_ui_caps(PurpleMediaManager *manager, + PurpleMediaCaps caps); + +/** + * Gets which media caps the UI supports. + * + * @param manager The manager to get caps from. + * + * @return caps The caps retrieved. + * + * @since 2.6.0 + */ +PurpleMediaCaps purple_media_manager_get_ui_caps(PurpleMediaManager *manager); + +/*}@*/ + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* _PURPLE_MEDIA_MANAGER_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/mime.c --- a/libpurple/mime.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/mime.c Mon Apr 20 00:10:51 2009 +0000 @@ -25,9 +25,6 @@ #include #include -#include -#include -#include #include "internal.h" diff -r 78ef23551355 -r 872d30754311 libpurple/mime.h --- a/libpurple/mime.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/mime.h Mon Apr 20 00:10:51 2009 +0000 @@ -25,7 +25,6 @@ #define _PURPLE_MIME_H #include -#include #ifdef __cplusplus extern "C" { diff -r 78ef23551355 -r 872d30754311 libpurple/network.c --- a/libpurple/network.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/network.c Mon Apr 20 00:10:51 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,93 @@ #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); + } + + while (hosts != NULL) { + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address */ + g_free(hosts->data); + hosts = g_slist_delete_link(hosts, 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 +916,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 +959,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 +1005,7 @@ #endif purple_signal_unregister(purple_network_get_handle(), "network-configuration-changed"); + + if (stun_ip) + g_free(stun_ip); } diff -r 78ef23551355 -r 872d30754311 libpurple/network.h --- a/libpurple/network.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/network.h Mon Apr 20 00:10:51 2009 +0000 @@ -225,6 +225,41 @@ */ 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 + * @since 2.6.0 + */ +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 + * @since 2.6.0 + */ +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 + * @since 2.6.0 + */ +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 + * @since 2.6.0 + */ +const gchar *purple_network_get_turn_ip(void); + + /** * Initializes the network subsystem. */ diff -r 78ef23551355 -r 872d30754311 libpurple/plugin.c --- a/libpurple/plugin.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/plugin.c Mon Apr 20 00:10:51 2009 +0000 @@ -1201,6 +1201,11 @@ purple_signals_disconnect_by_handle(handle); purple_signals_unregister_by_instance(handle); + + while (search_paths) { + g_free(search_paths->data); + search_paths = g_list_delete_link(search_paths, search_paths); + } } /************************************************************************** @@ -1229,6 +1234,21 @@ } void +purple_plugins_unload(PurplePluginType type) +{ +#ifdef PURPLE_PLUGINS + GList *l; + + for (l = plugins; l; l = l->next) { + PurplePlugin *plugin = l->data; + if (plugin->info->type == type && purple_plugin_is_loaded(plugin)) + purple_plugin_unload(plugin); + } + +#endif /* PURPLE_PLUGINS */ +} + +void purple_plugins_destroy_all(void) { #ifdef PURPLE_PLUGINS diff -r 78ef23551355 -r 872d30754311 libpurple/plugin.h --- a/libpurple/plugin.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/plugin.h Mon Apr 20 00:10:51 2009 +0000 @@ -29,7 +29,7 @@ #ifndef _PURPLE_PLUGIN_H_ #define _PURPLE_PLUGIN_H_ -#include +#include #include #include "signals.h" #include "value.h" @@ -503,6 +503,11 @@ void purple_plugins_unload_all(void); /** + * Unloads all plugins of a specific type. + */ +void purple_plugins_unload(PurplePluginType type); + +/** * Destroys all registered plugins. */ void purple_plugins_destroy_all(void); diff -r 78ef23551355 -r 872d30754311 libpurple/plugins/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/plugins/filectl.c --- a/libpurple/plugins/filectl.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/plugins/filectl.c Mon Apr 20 00:10:51 2009 +0000 @@ -220,7 +220,7 @@ plugin_load(PurplePlugin *plugin) { init_file(); - check = purple_timeout_add(5000, (GSourceFunc)check_file, NULL); + check = purple_timeout_add_seconds(5, (GSourceFunc)check_file, NULL); return TRUE; } diff -r 78ef23551355 -r 872d30754311 libpurple/plugins/joinpart.c --- a/libpurple/plugins/joinpart.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/plugins/joinpart.c Mon Apr 20 00:10:51 2009 +0000 @@ -194,7 +194,7 @@ PURPLE_CALLBACK(received_chat_msg_cb), users); /* Cleanup every 5 minutes */ - id = purple_timeout_add(1000 * 60 * 5, (GSourceFunc)clean_users_hash, users); + id = purple_timeout_add_seconds(60 * 5, (GSourceFunc)clean_users_hash, users); data = g_new(gpointer, 2); data[0] = users; diff -r 78ef23551355 -r 872d30754311 libpurple/plugins/perl/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/plugins/perl/perl-common.c --- a/libpurple/plugins/perl/perl-common.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/plugins/perl/perl-common.c Mon Apr 20 00:10:51 2009 +0000 @@ -403,7 +403,7 @@ static SV * purple_perl_sv_from_subtype(const PurpleValue *value, void *arg) { - const char *stash = NULL; + const char *stash = "Purple"; /* ? */ switch (purple_value_get_subtype(value)) { case PURPLE_SUBTYPE_ACCOUNT: @@ -442,6 +442,9 @@ case PURPLE_SUBTYPE_STATUS: stash = "Purple::Status"; break; + case PURPLE_SUBTYPE_SAVEDSTATUS: + stash = "Purple::SavedStatus"; + break; case PURPLE_SUBTYPE_LOG: stash = "Purple::Log"; break; @@ -451,10 +454,19 @@ case PURPLE_SUBTYPE_XMLNODE: stash = "Purple::XMLNode"; break; - - default: - stash = "Purple"; /* ? */ - } + case PURPLE_SUBTYPE_USERINFO: + stash = "Purple::NotifyUserInfo"; + break; + case PURPLE_SUBTYPE_STORED_IMAGE: + stash = "Purple::StoredImage"; + break; + case PURPLE_SUBTYPE_CERTIFICATEPOOL: + stash = "Purple::Certificate::Pool"; + break; + case PURPLE_SUBTYPE_UNKNOWN: + stash = "Purple::Unknown"; + break; + } return sv_2mortal(purple_perl_bless_object(arg, stash)); } diff -r 78ef23551355 -r 872d30754311 libpurple/plugins/ssl/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/plugins/tcl/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/plugins/tcl/tcl_cmds.c --- a/libpurple/plugins/tcl/tcl_cmds.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/plugins/tcl/tcl_cmds.c Mon Apr 20 00:10:51 2009 +0000 @@ -683,8 +683,9 @@ int tcl_cmd_connection(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { Tcl_Obj *list, *elem; - const char *cmds[] = { "account", "displayname", "handle", "list", NULL }; - enum { CMD_CONN_ACCOUNT, CMD_CONN_DISPLAYNAME, CMD_CONN_HANDLE, CMD_CONN_LIST } cmd; + const char *cmds[] = { "account", "displayname", "handle", "list", "state", NULL }; + enum { CMD_CONN_ACCOUNT, CMD_CONN_DISPLAYNAME, CMD_CONN_HANDLE, + CMD_CONN_LIST, CMD_CONN_STATE } cmd; int error; GList *cur; PurpleConnection *gc; @@ -739,6 +740,25 @@ } Tcl_SetObjResult(interp, list); break; + case CMD_CONN_STATE: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "gc"); + return TCL_ERROR; + } + if ((gc = tcl_validate_gc(objv[2], interp)) == NULL) + return TCL_ERROR; + switch (purple_connection_get_state(gc)) { + case PURPLE_DISCONNECTED: + Tcl_SetObjResult(interp, Tcl_NewStringObj("disconnected", -1)); + break; + case PURPLE_CONNECTED: + Tcl_SetObjResult(interp, Tcl_NewStringObj("connected", -1)); + break; + case PURPLE_CONNECTING: + Tcl_SetObjResult(interp, Tcl_NewStringObj("connecting", -1)); + break; + } + break; } return TCL_OK; diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/bonjour/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/bonjour/bonjour.c --- a/libpurple/protocols/bonjour/bonjour.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Mon Apr 20 00:10:51 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 = diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/bonjour/jabber.c --- a/libpurple/protocols/bonjour/jabber.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Mon Apr 20 00:10:51 2009 +0000 @@ -237,7 +237,7 @@ }; static void -_match_buddies_by_address(gpointer key, gpointer value, gpointer data) +_match_buddies_by_address(gpointer value, gpointer data) { PurpleBuddy *pb = value; PurpleAccount *account = NULL; @@ -638,6 +638,7 @@ char *address_text = NULL; struct _match_buddies_by_address_t *mbba; BonjourJabberConversation *bconv; + GSList *buddies; /* Check that it is a read condition */ if (condition != PURPLE_INPUT_READ) @@ -658,7 +659,10 @@ mbba = g_new0(struct _match_buddies_by_address_t, 1); mbba->address = address_text; mbba->jdata = jdata; - g_hash_table_foreach(purple_blist_get_buddies(), _match_buddies_by_address, mbba); + + buddies = purple_blist_get_buddies(); + g_slist_foreach(buddies, _match_buddies_by_address, mbba); + g_slist_free(buddies); if (mbba->matched_buddies == NULL) { purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n"); @@ -850,11 +854,15 @@ bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv) { BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data; struct _match_buddies_by_address_t *mbba; + GSList *buddies; mbba = g_new0(struct _match_buddies_by_address_t, 1); mbba->address = bconv->ip; mbba->jdata = jdata; - g_hash_table_foreach(purple_blist_get_buddies(), _match_buddies_by_address, mbba); + + buddies = purple_blist_get_buddies(); + g_slist_foreach(buddies, _match_buddies_by_address, mbba); + g_slist_free(buddies); /* If there is exactly one match, use it */ if(mbba->matched_buddies != NULL) { diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/gg/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/gg/gg.c --- a/libpurple/protocols/gg/gg.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/gg/gg.c Mon Apr 20 00:10:51 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 = { diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/irc/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/irc/irc.c --- a/libpurple/protocols/irc/irc.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/irc/irc.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/irc/msgs.c --- a/libpurple/protocols/irc/msgs.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/irc/msgs.c Mon Apr 20 00:10:51 2009 +0000 @@ -128,7 +128,7 @@ irc_blist_timeout(irc); if (!irc->timer) - irc->timer = purple_timeout_add(45000, (GSourceFunc)irc_blist_timeout, (gpointer)irc); + irc->timer = purple_timeout_add_seconds(45, (GSourceFunc)irc_blist_timeout, (gpointer)irc); } void irc_msg_default(struct irc_conn *irc, const char *name, const char *from, char **args) @@ -1004,10 +1004,23 @@ void irc_msg_nickused(struct irc_conn *irc, const char *name, const char *from, char **args) { char *newnick, *buf, *end; + PurpleConnection *gc = purple_account_get_connection(irc->account); if (!args || !args[1]) return; + if (gc && purple_connection_get_state(gc) == PURPLE_CONNECTED) { + /* We only want to do the following dance if the connection + has not been successfully completed. If it has, just + notify the user that their /nick command didn't go. */ + buf = g_strdup_printf(_("The nickname \"%s\" is already being used."), + irc->reqnick); + purple_notify_error(gc, _("Nickname in use"), + _("Nickname in use"), buf); + g_free(buf); + g_free(irc->reqnick); + } + if (strlen(args[1]) < strlen(irc->reqnick) || irc->nickused) newnick = g_strdup(args[1]); else diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/Makefile.am --- a/libpurple/protocols/jabber/Makefile.am Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/Makefile.am Mon Apr 20 00:10:51 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 \ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/Makefile.mingw --- a/libpurple/protocols/jabber/Makefile.mingw Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/Makefile.mingw Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/adhoccommands.c --- a/libpurple/protocols/jabber/adhoccommands.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.c Mon Apr 20 00:10:51 2009 +0000 @@ -39,16 +39,17 @@ GList *actionslist; } JabberAdHocActionInfo; -void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data) { - const char *from = xmlnode_get_attrib(packet, "from"); - const char *type = xmlnode_get_attrib(packet, "type"); +void jabber_adhoc_disco_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ const char *node; xmlnode *query, *item; JabberID *jabberid; JabberBuddy *jb; JabberBuddyResource *jbr = NULL; - if(strcmp(type, "result")) + if (type == JABBER_IQ_ERROR) return; query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items"); @@ -95,7 +96,10 @@ } } -static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data); +static void jabber_adhoc_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data); + static void do_adhoc_action_cb(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data) { xmlnode *command; @@ -131,13 +135,16 @@ jabber_iq_send(iq); } -static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data) { +static void +jabber_adhoc_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ xmlnode *command = xmlnode_get_child_with_namespace(packet, "command", "http://jabber.org/protocol/commands"); const char *status = xmlnode_get_attrib(command,"status"); xmlnode *xdata = xmlnode_get_child_with_namespace(command,"x","jabber:x:data"); - const char *type = xmlnode_get_attrib(packet,"type"); - if(type && !strcmp(type,"error")) { + if (type == JABBER_IQ_ERROR) { char *msg = jabber_parse_error(js, packet, NULL); if(!msg) msg = g_strdup(_("Unknown Error")); @@ -147,8 +154,6 @@ g_free(msg); return; } - if(!type || strcmp(type,"result")) - return; if(!status) return; @@ -159,7 +164,7 @@ if(note) { char *data = xmlnode_get_data(note); - purple_notify_info(NULL, xmlnode_get_attrib(packet, "from"), data, NULL); + purple_notify_info(NULL, from, data, NULL); g_free(data); } @@ -199,7 +204,7 @@ actionInfo = g_new0(JabberAdHocActionInfo, 1); actionInfo->sessionid = g_strdup(xmlnode_get_attrib(command,"sessionid")); - actionInfo->who = g_strdup(xmlnode_get_attrib(packet,"from")); + actionInfo->who = g_strdup(from); actionInfo->node = g_strdup(xmlnode_get_attrib(command,"node")); actionInfo->actionslist = actionslist; @@ -218,7 +223,11 @@ } } -static void jabber_adhoc_server_got_list_cb(JabberStream *js, xmlnode *packet, gpointer data) { +static void +jabber_adhoc_server_got_list_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", "http://jabber.org/protocol/disco#items"); xmlnode *item; diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/adhoccommands.h --- a/libpurple/protocols/jabber/adhoccommands.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,14 +19,16 @@ * */ -#ifndef _PURPLE_JABBER_ADHOCCOMMANDS_H_ -#define _PURPLE_JABBER_ADHOCCOMMANDS_H_ +#ifndef PURPLE_JABBER_ADHOCCOMMANDS_H_ +#define PURPLE_JABBER_ADHOCCOMMANDS_H_ #include "jabber.h" /* Implementation of XEP-0050 */ -void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data); +void jabber_adhoc_disco_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data); void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd); @@ -36,4 +38,4 @@ void jabber_adhoc_init_server_commands(JabberStream *js, GList **m); -#endif /* _PURPLE_JABBER_ADHOCCOMMANDS_H_ */ +#endif /* PURPLE_JABBER_ADHOCCOMMANDS_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/auth.c --- a/libpurple/protocols/jabber/auth.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/auth.c Mon Apr 20 00:10:51 2009 +0000 @@ -36,8 +36,9 @@ #include "iq.h" #include "notify.h" -static void auth_old_result_cb(JabberStream *js, xmlnode *packet, - gpointer data); +static void auth_old_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data); gboolean jabber_process_starttls(JabberStream *js, xmlnode *packet) @@ -566,11 +567,11 @@ #endif } -static void auth_old_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +static void auth_old_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { - const char *type = xmlnode_get_attrib(packet, "type"); - - if(type && !strcmp(type, "result")) { + if (type == JABBER_IQ_RESULT) { jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); } else { PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; @@ -593,24 +594,20 @@ } } -static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data) +static void auth_old_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { JabberIq *iq; xmlnode *query, *x; - const char *type = xmlnode_get_attrib(packet, "type"); const char *pw = purple_connection_get_password(js->gc); - if(!type) { - purple_connection_error_reason (js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid response from server.")); - return; - } else if(!strcmp(type, "error")) { + if (type == JABBER_IQ_ERROR) { PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; char *msg = jabber_parse_error(js, packet, &reason); purple_connection_error_reason (js->gc, reason, msg); g_free(msg); - } else if(!strcmp(type, "result")) { + } else if (type == JABBER_IQ_RESULT) { query = xmlnode_get_child(packet, "query"); if(js->stream_id && xmlnode_get_child(query, "digest")) { char *s, *hash; diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/auth.h --- a/libpurple/protocols/jabber/auth.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/auth.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_AUTH_H_ -#define _PURPLE_JABBER_AUTH_H_ +#ifndef PURPLE_JABBER_AUTH_H_ +#define PURPLE_JABBER_AUTH_H_ #include "jabber.h" #include "xmlnode.h" @@ -32,4 +32,4 @@ void jabber_auth_handle_success(JabberStream *js, xmlnode *packet); void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet); -#endif /* _PURPLE_JABBER_AUTH_H_ */ +#endif /* PURPLE_JABBER_AUTH_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/buddy.c Mon Apr 20 00:10:51 2009 +0000 @@ -155,6 +155,7 @@ jbr->jb = jb; jbr->name = g_strdup(resource); jbr->capabilities = JABBER_CAP_XHTML; + jbr->tz_off = PURPLE_NO_TZ_OFF; jb->resources = g_list_append(jb->resources, jbr); } jbr->priority = priority; @@ -797,6 +798,21 @@ purple_notify_user_info_prepend_pair(user_info, _("Operating System"), jbr->client.os); } } + if (jbr && jbr->tz_off != PURPLE_NO_TZ_OFF) { + time_t now_t; + struct tm *now; + char *timestamp; + time(&now_t); + now_t += jbr->tz_off; + now = gmtime(&now_t); + + timestamp = g_strdup_printf("%s %c%02d%02d", purple_time_format(now), + jbr->tz_off < 0 ? '-' : '+', + abs(jbr->tz_off / (60*60)), + abs((jbr->tz_off % (60*60)) / 60)); + purple_notify_user_info_prepend_pair(user_info, _("Local Time"), timestamp); + g_free(timestamp); + } if(jbir) { if(jbir->idle_seconds > 0) { char *idle = purple_str_seconds_to_string(jbir->idle_seconds); @@ -915,7 +931,7 @@ feature = _("User Gaming"); else if(!strcmp(feature, "http://jabber.org/protocol/viewing")) feature = _("User Viewing"); - else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns")) + else if(!strcmp(feature, "urn:xmpp:ping")) feature = _("Ping"); else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns")) feature = _("Stanza Encryption"); @@ -967,6 +983,22 @@ } } + if (jbr->tz_off != PURPLE_NO_TZ_OFF) { + time_t now_t; + struct tm *now; + char *timestamp; + time(&now_t); + now_t += jbr->tz_off; + now = gmtime(&now_t); + + timestamp = g_strdup_printf("%s %c%02d%02d", purple_time_format(now), + jbr->tz_off < 0 ? '-' : '+', + abs(jbr->tz_off / (60*60)), + abs((jbr->tz_off % (60*60)) / 60)); + purple_notify_user_info_prepend_pair(user_info, _("Local Time"), timestamp); + g_free(timestamp); + } + if(jbr->name && (jbir = g_hash_table_lookup(jbi->resources, jbr->name))) { if(jbir->idle_seconds > 0) { char *idle = purple_str_seconds_to_string(jbir->idle_seconds); @@ -1085,7 +1117,7 @@ feature = _("User Gaming"); else if(!strcmp(feature, "http://jabber.org/protocol/viewing")) feature = _("User Viewing"); - else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns")) + else if(!strcmp(feature, "urn:xmpp:ping")) feature = _("Ping"); else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns")) feature = _("Stanza Encryption"); @@ -1151,12 +1183,19 @@ } } -static void jabber_vcard_save_mine(JabberStream *js, xmlnode *packet, gpointer data) +static void jabber_vcard_save_mine(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { xmlnode *vcard; char *txt; PurpleStoredImage *img; + if (type == JABBER_IQ_ERROR) { + purple_debug_warning("jabber", "Server returned error while retrieving vCard"); + return; + } + if((vcard = xmlnode_get_child(packet, "vCard")) || (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) { @@ -1187,9 +1226,10 @@ jabber_iq_send(iq); } -static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data) +static void jabber_vcard_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { - const char *id, *from; char *bare_jid; char *text; char *serverside_alias = NULL; @@ -1198,9 +1238,6 @@ JabberBuddyInfo *jbi = data; PurpleNotifyUserInfo *user_info; - from = xmlnode_get_attrib(packet, "from"); - id = xmlnode_get_attrib(packet, "id"); - if(!jbi) return; @@ -1550,19 +1587,16 @@ g_free(jbri); } -static void jabber_version_parse(JabberStream *js, xmlnode *packet, gpointer data) +static void jabber_version_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { JabberBuddyInfo *jbi = data; - const char *type, *id, *from; xmlnode *query; char *resource_name; g_return_if_fail(jbi != NULL); - type = xmlnode_get_attrib(packet, "type"); - id = xmlnode_get_attrib(packet, "id"); - from = xmlnode_get_attrib(packet, "from"); - jabber_buddy_info_remove_id(jbi, id); if(!from) @@ -1571,7 +1605,7 @@ resource_name = jabber_get_resource(from); if(resource_name) { - if(type && !strcmp(type, "result")) { + if (type == JABBER_IQ_RESULT) { if((query = xmlnode_get_child(packet, "query"))) { JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name); if(jbr) { @@ -1594,19 +1628,17 @@ jabber_buddy_info_show_if_ready(jbi); } -static void jabber_last_parse(JabberStream *js, xmlnode *packet, gpointer data) +static void jabber_last_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { JabberBuddyInfo *jbi = data; xmlnode *query; char *resource_name; - const char *type, *id, *from, *seconds; + const char *seconds; g_return_if_fail(jbi != NULL); - type = xmlnode_get_attrib(packet, "type"); - id = xmlnode_get_attrib(packet, "id"); - from = xmlnode_get_attrib(packet, "from"); - jabber_buddy_info_remove_id(jbi, id); if(!from) @@ -1615,7 +1647,7 @@ resource_name = jabber_get_resource(from); if(resource_name) { - if(type && !strcmp(type, "result")) { + if (type == JABBER_IQ_RESULT) { if((query = xmlnode_get_child(packet, "query"))) { seconds = xmlnode_get_attrib(query, "seconds"); if(seconds) { @@ -1636,6 +1668,56 @@ jabber_buddy_info_show_if_ready(jbi); } +static void jabber_time_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + JabberBuddyInfo *jbi = data; + JabberBuddyResource *jbr; + char *resource_name; + + g_return_if_fail(jbi != NULL); + + jabber_buddy_info_remove_id(jbi, id); + + if (!from) + return; + + resource_name = jabber_get_resource(from); + jbr = resource_name ? jabber_buddy_find_resource(jbi->jb, resource_name) : NULL; + g_free(resource_name); + if (jbr) { + if (type == JABBER_IQ_RESULT) { + xmlnode *time = xmlnode_get_child(packet, "time"); + xmlnode *tzo = time ? xmlnode_get_child(time, "tzo") : NULL; + char *tzo_data = tzo ? xmlnode_get_data(tzo) : NULL; + if (tzo_data) { + char *c = tzo_data; + int hours, minutes; + if (tzo_data[0] == 'Z' && tzo_data[1] == '\0') { + jbr->tz_off = 0; + } else { + gboolean offset_positive = (tzo_data[0] == '+'); + /* [+-]HH:MM */ + if (((*c == '+' || *c == '-') && (c = c + 1)) && + sscanf(c, "%02d:%02d", &hours, &minutes) == 2) { + jbr->tz_off = 60*60*hours + 60*minutes; + if (!offset_positive) + jbr->tz_off *= -1; + } else { + purple_debug_info("jabber", "Ignoring malformed timezone %s", + tzo_data); + } + } + + g_free(tzo_data); + } + } + } + + jabber_buddy_info_show_if_ready(jbi); +} + void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js) { if (js->pending_buddy_info_requests) @@ -1767,11 +1849,24 @@ jabber_iq_send(iq); } + if (jbr->tz_off == PURPLE_NO_TZ_OFF && + (!jbr->caps || + jabber_resource_has_capability(jbr, "urn:xmpp:time"))) { + xmlnode *child; + iq = jabber_iq_new(js, JABBER_IQ_GET); + xmlnode_set_attrib(iq->node, "to", full_jid); + child = xmlnode_new_child(iq->node, "time"); + xmlnode_set_namespace(child, "urn:xmpp:time"); + jabber_iq_set_callback(iq, jabber_time_parse, jbi); + jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id)); + jabber_iq_send(iq); + } + g_free(full_jid); } js->pending_buddy_info_requests = g_slist_prepend(js->pending_buddy_info_requests, jbi); - jbi->timeout_handle = purple_timeout_add(30000, jabber_buddy_get_info_timeout, jbi); + jbi->timeout_handle = purple_timeout_add_seconds(30, jabber_buddy_get_info_timeout, jbi); } void jabber_buddy_get_info(PurpleConnection *gc, const char *who) @@ -2163,7 +2258,9 @@ g_list_nth_data(row, 0), NULL, NULL); } -static void user_search_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +static void user_search_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { PurpleNotifySearchResults *results; PurpleNotifySearchColumn *column; @@ -2359,15 +2456,16 @@ }; #endif -static void user_search_fields_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +static void user_search_fields_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { xmlnode *query, *x; - const char *from, *type; - if(!(from = xmlnode_get_attrib(packet, "from"))) + if (!from) return; - if(!(type = xmlnode_get_attrib(packet, "type")) || !strcmp(type, "error")) { + if (type == JABBER_IQ_ERROR) { char *msg = jabber_parse_error(js, packet, NULL); if(!msg) diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/buddy.h --- a/libpurple/protocols/jabber/buddy.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/buddy.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_BUDDY_H_ -#define _PURPLE_JABBER_BUDDY_H_ +#ifndef PURPLE_JABBER_BUDDY_H_ +#define PURPLE_JABBER_BUDDY_H_ typedef enum { JABBER_BUDDY_STATE_UNKNOWN = -2, @@ -81,6 +81,8 @@ char *name; char *os; } client; + /* tz_off == PURPLE_NO_TZ_OFF when unset */ + long tz_off; JabberCapsClientInfo *caps; GList *commands; } JabberBuddyResource; @@ -121,4 +123,4 @@ const gchar *cap); gboolean jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap); -#endif /* _PURPLE_JABBER_BUDDY_H_ */ +#endif /* PURPLE_JABBER_BUDDY_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/caps.c --- a/libpurple/protocols/jabber/caps.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/caps.c Mon Apr 20 00:10:51 2009 +0000 @@ -369,7 +369,10 @@ } } -static void jabber_caps_ext_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { +static void jabber_caps_ext_iqcb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ /* collect data and fetch all exts */ xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", "http://jabber.org/protocol/disco#info"); jabber_ext_userdata *extuserdata = data; @@ -433,7 +436,10 @@ jabber_caps_get_info_check_completion(userdata); } -static void jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { +static void jabber_caps_client_iqcb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ /* collect data and fetch all exts */ xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", "http://jabber.org/protocol/disco#info"); diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/caps.h --- a/libpurple/protocols/jabber/caps.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/caps.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * */ -#ifndef _PURPLE_JABBER_CAPS_H_ -#define _PURPLE_JABBER_CAPS_H_ +#ifndef PURPLE_JABBER_CAPS_H_ +#define PURPLE_JABBER_CAPS_H_ typedef struct _JabberCapsClientInfo JabberCapsClientInfo; @@ -46,4 +46,4 @@ void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data); void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo); -#endif /* _PURPLE_JABBER_CAPS_H_ */ +#endif /* PURPLE_JABBER_CAPS_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/chat.c --- a/libpurple/protocols/jabber/chat.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/chat.c Mon Apr 20 00:10:51 2009 +0000 @@ -376,21 +376,19 @@ jabber_iq_send(iq); } -static void jabber_chat_room_configure_cb(JabberStream *js, xmlnode *packet, gpointer data) +static void jabber_chat_room_configure_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { xmlnode *query, *x; - const char *type = xmlnode_get_attrib(packet, "type"); - const char *from = xmlnode_get_attrib(packet, "from"); char *msg; JabberChat *chat; JabberID *jid; - if(!type || !from) + if (!from) return; - - - if(!strcmp(type, "result")) { + if (type == JABBER_IQ_RESULT) { jid = jabber_id_new(from); if(!jid) @@ -416,7 +414,7 @@ return; } } - } else if(!strcmp(type, "error")) { + } else if (type == JABBER_IQ_ERROR) { char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Configuration error"), _("Configuration error"), msg); @@ -486,11 +484,12 @@ g_free(room_jid); } -static void jabber_chat_register_x_data_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +static void +jabber_chat_register_x_data_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { - const char *type = xmlnode_get_attrib(packet, "type"); - - if(type && !strcmp(type, "error")) { + if (type == JABBER_IQ_ERROR) { char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Registration error"), _("Registration error"), msg); @@ -521,19 +520,19 @@ jabber_iq_send(iq); } -static void jabber_chat_register_cb(JabberStream *js, xmlnode *packet, gpointer data) +static void jabber_chat_register_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { xmlnode *query, *x; - const char *type = xmlnode_get_attrib(packet, "type"); - const char *from = xmlnode_get_attrib(packet, "from"); char *msg; JabberChat *chat; JabberID *jid; - if(!type || !from) + if (!from) return; - if(!strcmp(type, "result")) { + if (type == JABBER_IQ_RESULT) { jid = jabber_id_new(from); if(!jid) @@ -559,7 +558,7 @@ return; } } - } else if(!strcmp(type, "error")) { + } else if (type == JABBER_IQ_ERROR) { char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Registration error"), _("Registration error"), msg); @@ -690,16 +689,17 @@ g_free(room_jid); } -static void roomlist_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +static void roomlist_disco_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { xmlnode *query; xmlnode *item; - const char *type; if(!js->roomlist) return; - if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) { + if (type == JABBER_IQ_ERROR) { char *err = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error"), _("Error retrieving room list"), err); @@ -988,13 +988,17 @@ return TRUE; } -static void jabber_chat_disco_traffic_cb(JabberStream *js, xmlnode *packet, gpointer data) +static void jabber_chat_disco_traffic_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { JabberChat *chat; - /*xmlnode *query;*/ - int id = GPOINTER_TO_INT(data); +#if 0 + xmlnode *query, *x; +#endif + int chat_id = GPOINTER_TO_INT(data); - if(!(chat = jabber_chat_find_by_id(js, id))) + if(!(chat = jabber_chat_find_by_id(js, chat_id))) return; /* defaults, in case the conference server doesn't @@ -1002,8 +1006,9 @@ chat->xhtml = TRUE; /* disabling this until more MUC servers support - * announcing this - if(xmlnode_get_child(packet, "error")) { + * announcing this */ +#if 0 + if (type == JABBER_IQ_ERROR) { return; } @@ -1019,7 +1024,7 @@ chat->xhtml = TRUE; } } - */ +#endif } void jabber_chat_disco_traffic(JabberChat *chat) diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/chat.h --- a/libpurple/protocols/jabber/chat.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/chat.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_CHAT_H_ -#define _PURPLE_JABBER_CHAT_H_ +#ifndef PURPLE_JABBER_CHAT_H_ +#define PURPLE_JABBER_CHAT_H_ #include "internal.h" #include "connection.h" @@ -94,4 +94,4 @@ char *jabber_roomlist_room_serialize(PurpleRoomlistRoom *room); -#endif /* _PURPLE_JABBER_CHAT_H_ */ +#endif /* PURPLE_JABBER_CHAT_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/data.c --- a/libpurple/protocols/jabber/data.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/data.c Mon Apr 20 00:10:51 2009 +0000 @@ -200,25 +200,26 @@ } void -jabber_data_parse(JabberStream *js, xmlnode *packet) +jabber_data_parse(JabberStream *js, const char *who, JabberIqType type, + const char *id, xmlnode *data_node) { JabberIq *result = NULL; - const char *who = xmlnode_get_attrib(packet, "from"); - xmlnode *data_node = xmlnode_get_child(packet, "data"); - const JabberData *data = - jabber_data_find_local_by_cid(xmlnode_get_attrib(data_node, "cid")); + const char *cid = xmlnode_get_attrib(data_node, "cid"); + const JabberData *data = cid ? jabber_data_find_local_by_cid(cid) : NULL; if (!data) { xmlnode *item_not_found = xmlnode_new("item-not-found"); result = jabber_iq_new(js, JABBER_IQ_ERROR); - xmlnode_set_attrib(result->node, "to", who); - xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id")); + if (who) + xmlnode_set_attrib(result->node, "to", who); + xmlnode_set_attrib(result->node, "id", id); xmlnode_insert_child(result->node, item_not_found); } else { result = jabber_iq_new(js, JABBER_IQ_RESULT); - xmlnode_set_attrib(result->node, "to", who); - xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id")); + if (who) + xmlnode_set_attrib(result->node, "to", who); + xmlnode_set_attrib(result->node, "id", id); xmlnode_insert_child(result->node, jabber_data_get_xml_definition(data)); } @@ -235,6 +236,8 @@ g_free, jabber_data_delete); remote_data_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_data_delete); + + jabber_iq_register_handler("data", XEP_0231_NAMESPACE, jabber_data_parse); } void diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/data.h --- a/libpurple/protocols/jabber/data.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/data.h Mon Apr 20 00:10:51 2009 +0000 @@ -14,8 +14,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA */ -#ifndef JABBER_DATA_H -#define JABBER_DATA_H +#ifndef PURPLE_JABBER_DATA_H +#define PURPLE_JABBER_DATA_H #include "xmlnode.h" #include "jabber.h" @@ -65,9 +65,10 @@ void jabber_data_associate_remote(JabberData *data); /* handles iq requests */ -void jabber_data_parse(JabberStream *js, xmlnode *packet); +void jabber_data_parse(JabberStream *js, const char *who, JabberIqType type, + const char *id, xmlnode *data_node); void jabber_data_init(void); void jabber_data_uninit(void); -#endif /* JABBER_DATA_H */ +#endif /* PURPLE_JABBER_DATA_H */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Mon Apr 20 00:10:51 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" @@ -45,9 +46,11 @@ } static void -jabber_disco_bytestream_server_cb(JabberStream *js, xmlnode *packet, gpointer data) { +jabber_disco_bytestream_server_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ JabberBytestreamsStreamhost *sh = data; - const char *from = xmlnode_get_attrib(packet, "from"); xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", "http://jabber.org/protocol/bytestreams"); @@ -85,29 +88,22 @@ } -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"); +void jabber_disco_info_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *in_query) { - if(!from || !type) + if(!from) return; - if(!strcmp(type, "get")) { + if(type == JABBER_IQ_GET) { xmlnode *query, *identity, *feature; JabberIq *iq; - - xmlnode *in_query; - const char *node = NULL; - - if((in_query = xmlnode_get_child(packet, "query"))) { - node = xmlnode_get_attrib(in_query, "node"); - } - + const char *node = xmlnode_get_attrib(in_query, "node"); iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "http://jabber.org/protocol/disco#info"); - jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); + jabber_iq_set_id(iq, id); xmlnode_set_attrib(iq->node, "to", from); query = xmlnode_get_child(iq->node, "query"); @@ -126,7 +122,7 @@ SUPPORT_FEATURE("jabber:iq:last") SUPPORT_FEATURE("jabber:iq:oob") SUPPORT_FEATURE("jabber:iq:time") - SUPPORT_FEATURE("xmpp:urn:time") + SUPPORT_FEATURE("urn:xmpp:time") SUPPORT_FEATURE("jabber:iq:version") SUPPORT_FEATURE("jabber:x:conference") SUPPORT_FEATURE("http://jabber.org/protocol/bytestreams") @@ -139,7 +135,6 @@ SUPPORT_FEATURE("http://jabber.org/protocol/si/profile/file-transfer") SUPPORT_FEATURE("http://jabber.org/protocol/xhtml-im") SUPPORT_FEATURE("urn:xmpp:ping") - SUPPORT_FEATURE("http://www.xmpp.org/extensions/xep-0199.html#ns") if(!node) { /* non-caps disco#info, add all enabled extensions */ GList *features; @@ -149,6 +144,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; @@ -198,8 +203,7 @@ } jabber_iq_send(iq); - } else if(!strcmp(type, "result")) { - xmlnode *query = xmlnode_get_child(packet, "query"); + } else if(type == JABBER_IQ_RESULT) { xmlnode *child; JabberID *jid; JabberBuddy *jb; @@ -216,7 +220,7 @@ if(jbr) capabilities = jbr->capabilities; - for(child = query->child; child; child = child->next) { + for(child = in_query->child; child; child = child->next) { if(child->type != XMLNODE_TYPE_TAG) continue; @@ -266,7 +270,7 @@ capabilities |= JABBER_CAP_IQ_SEARCH; else if(!strcmp(var, "jabber:iq:register")) capabilities |= JABBER_CAP_IQ_REGISTER; - else if(!strcmp(var, "http://www.xmpp.org/extensions/xep-0199.html#ns")) + else if(!strcmp(var, "urn:xmpp:ping")) capabilities |= JABBER_CAP_PING; else if(!strcmp(var, "http://jabber.org/protocol/commands")) { capabilities |= JABBER_CAP_ADHOC; @@ -287,7 +291,7 @@ jdicd->callback(js, from, capabilities, jdicd->data); g_hash_table_remove(js->disco_callbacks, from); } - } else if(!strcmp(type, "error")) { + } else if(type == JABBER_IQ_ERROR) { JabberID *jid; JabberBuddy *jb; JabberBuddyResource *jbr = NULL; @@ -311,28 +315,23 @@ } } -void jabber_disco_items_parse(JabberStream *js, xmlnode *packet) { - const char *from = xmlnode_get_attrib(packet, "from"); - const char *type = xmlnode_get_attrib(packet, "type"); - - if(type && !strcmp(type, "get")) { +void jabber_disco_items_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *query) { + if(type == JABBER_IQ_GET) { JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "http://jabber.org/protocol/disco#items"); /* preserve node */ - xmlnode *iq_query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items"); - if(iq_query) { - xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items"); - if(query) { - const char *node = xmlnode_get_attrib(query,"node"); - if(node) - xmlnode_set_attrib(iq_query,"node",node); - } - } + xmlnode *iq_query = xmlnode_get_child(iq->node, "query"); + const char *node = xmlnode_get_attrib(query, "node"); + if(node) + xmlnode_set_attrib(iq_query,"node",node); - jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); + jabber_iq_set_id(iq, id); - xmlnode_set_attrib(iq->node, "to", from); + if (from) + xmlnode_set_attrib(iq->node, "to", from); jabber_iq_send(iq); } } @@ -397,19 +396,18 @@ } static void -jabber_disco_server_info_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +jabber_disco_server_info_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { xmlnode *query, *child; - const char *from = xmlnode_get_attrib(packet, "from"); - const char *type = xmlnode_get_attrib(packet, "type"); - if((!from || !type) || - (strcmp(from, js->user->domain))) { + if (!from || strcmp(from, js->user->domain)) { jabber_disco_finish_server_info_result_cb(js); return; } - if(strcmp(type, "result")) { + if (type == JABBER_IQ_ERROR) { /* A common way to get here is for the server not to support xmlns http://jabber.org/protocol/disco#info */ jabber_disco_finish_server_info_result_cb(js); return; @@ -443,6 +441,11 @@ 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... */ } } @@ -470,19 +473,16 @@ } static void -jabber_disco_server_items_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +jabber_disco_server_items_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { xmlnode *query, *child; - const char *from = xmlnode_get_attrib(packet, "from"); - const char *type = xmlnode_get_attrib(packet, "type"); - if(!from || !type) + if (!from || strcmp(from, js->user->domain) != 0) return; - if(strcmp(from, js->user->domain)) - return; - - if(strcmp(type, "result")) + if (type == JABBER_IQ_ERROR) return; while(js->chat_servers) { diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/disco.h --- a/libpurple/protocols/jabber/disco.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/disco.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,20 +19,22 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_DISCO_H_ -#define _PURPLE_JABBER_DISCO_H_ +#ifndef PURPLE_JABBER_DISCO_H_ +#define PURPLE_JABBER_DISCO_H_ #include "jabber.h" typedef void (JabberDiscoInfoCallback)(JabberStream *js, const char *who, JabberCapabilities capabilities, gpointer data); -void jabber_disco_info_parse(JabberStream *js, xmlnode *packet); -void jabber_disco_items_parse(JabberStream *js, xmlnode *packet); +void jabber_disco_info_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, xmlnode *in_query); +void jabber_disco_items_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, xmlnode *query); void jabber_disco_items_server(JabberStream *js); void jabber_disco_info_do(JabberStream *js, const char *who, JabberDiscoInfoCallback *callback, gpointer data); -#endif /* _PURPLE_JABBER_DISCO_H_ */ +#endif /* PURPLE_JABBER_DISCO_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/google.c --- a/libpurple/protocols/jabber/google.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/google.c Mon Apr 20 00:10:51 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,14 +32,594 @@ #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 -jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul) +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) +{ + GList *candidates = purple_media_get_local_candidates(session->media, "google-voice", + session->remote_jid); + PurpleMediaCandidate *transport; + + for (;candidates;candidates = candidates->next) { + JabberIq *iq; + gchar *ip, *port, *pref, *username, *password; + PurpleMediaCandidateType type; + xmlnode *sess; + xmlnode *candidate; + transport = (PurpleMediaCandidate*)(candidates->data); + + if (purple_media_candidate_get_component_id(transport) + != PURPLE_MEDIA_COMPONENT_RTP) + continue; + + iq = jabber_iq_new(session->js, JABBER_IQ_SET); + sess = google_session_create_xmlnode(session, "candidates"); + xmlnode_insert_child(iq->node, sess); + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + + candidate = xmlnode_new("candidate"); + + ip = purple_media_candidate_get_ip(transport); + port = g_strdup_printf("%d", + purple_media_candidate_get_port(transport)); + pref = g_strdup_printf("%f", + purple_media_candidate_get_priority(transport) + /1000.0); + username = purple_media_candidate_get_username(transport); + password = purple_media_candidate_get_password(transport); + type = purple_media_candidate_get_candidate_type(transport); + + xmlnode_set_attrib(candidate, "address", ip); + xmlnode_set_attrib(candidate, "port", port); + xmlnode_set_attrib(candidate, "name", "rtp"); + xmlnode_set_attrib(candidate, "username", 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", + password != NULL ? password : ""); + xmlnode_set_attrib(candidate, "preference", pref); + xmlnode_set_attrib(candidate, "protocol", + purple_media_candidate_get_protocol(transport) + == PURPLE_MEDIA_NETWORK_PROTOCOL_UDP ? + "udp" : "tcp"); + xmlnode_set_attrib(candidate, "type", type == + PURPLE_MEDIA_CANDIDATE_TYPE_HOST ? "local" : + type == + PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX ? "stun" : + 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); + + g_free(ip); + g_free(port); + g_free(pref); + g_free(username); + g_free(password); + + 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 { + google_session_send_candidates(session->media, + "google-voice", session->remote_jid, + session); + 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", + purple_media_codec_get_id(codec)); + gchar *encoding_name = + purple_media_codec_get_encoding_name(codec); + gchar *clock_rate = g_strdup_printf("%d", + purple_media_codec_get_clock_rate(codec)); + payload = xmlnode_new_child(desc, "payload-type"); + xmlnode_set_attrib(payload, "id", id); + xmlnode_set_attrib(payload, "name", encoding_name); + xmlnode_set_attrib(payload, "clockrate", clock_rate); + g_free(clock_rate); + g_free(encoding_name); + g_free(id); + } + purple_media_codec_list_free(codecs); + + jabber_iq_send(iq); + + if (is_initiator) + 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, gboolean local, + 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; +} + + +gboolean +jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type) { - const char *type = xmlnode_get_attrib(packet, "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 FALSE; + } + 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(), + purple_connection_get_account(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, + TRUE, "nice", num_params, params) == FALSE) { + purple_media_error(session->media, "Error adding stream."); + purple_media_stream_info(session->media, + PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); + google_session_destroy(session); + g_free(params); + return FALSE; + } + + 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 != NULL) ? TRUE : FALSE; +} + +static void +google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) +{ + 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(), + purple_connection_get_account(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, FALSE, + "nice", num_params, params) == FALSE) { + purple_media_error(session->media, "Error adding stream."); + purple_media_stream_info(session->media, + PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); + 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, iq_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 *sess, const char *iq_id) +{ + 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"))); + g_object_set(info, "username", xmlnode_get_attrib(cand, "username"), + "password", xmlnode_get_attrib(cand, "password"), NULL); + + 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, iq_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 *sess, const char *iq_id) +{ + xmlnode *desc_element = xmlnode_get_child(sess, "description"); + xmlnode *codec_element = xmlnode_get_child(desc_element, "payload-type"); + GList *codecs = NULL; + JabberIq *result = 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_stream_info(session->media, PURPLE_MEDIA_INFO_ACCEPT, + NULL, NULL, FALSE); + + result = jabber_iq_new(js, JABBER_IQ_RESULT); + jabber_iq_set_id(result, iq_id); + xmlnode_set_attrib(result->node, "to", session->remote_jid); + jabber_iq_send(result); +} + +static void +google_session_handle_reject(JabberStream *js, GoogleSession *session, xmlnode *sess) +{ + purple_media_end(session->media, NULL, NULL); +} + +static void +google_session_handle_terminate(JabberStream *js, GoogleSession *session, xmlnode *sess) +{ + purple_media_end(session->media, NULL, NULL); +} + +static void +google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) +{ + const char *type = xmlnode_get_attrib(sess, "type"); + + if (!strcmp(type, "initiate")) { + google_session_handle_initiate(js, session, sess, iq_id); + } else if (!strcmp(type, "accept")) { + google_session_handle_accept(js, session, sess, iq_id); + } else if (!strcmp(type, "reject")) { + google_session_handle_reject(js, session, sess); + } else if (!strcmp(type, "terminate")) { + google_session_handle_terminate(js, session, sess); + } else if (!strcmp(type, "candidates")) { + google_session_handle_candidates(js, session, sess, iq_id); + } +} + +void +jabber_google_session_parse(JabberStream *js, const char *from, + JabberIqType type, const char *iq_id, + xmlnode *session_node) +{ + GoogleSession *session = NULL; + GoogleSessionId id; + + xmlnode *desc_node; + + GList *iter = NULL; + + if (type != JABBER_IQ_SET) + 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_account( + purple_media_manager_get(), + purple_connection_get_account(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, session_node, iq_id); + 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, session_node, iq_id); +} +#endif /* USE_VV */ + +static void +jabber_gmail_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer nul) +{ xmlnode *child; - xmlnode *message, *sender_node, *subject_node; - const char *from, *to, *url, *tid; - char *subject; + xmlnode *message; + const char *to, *url; const char *in_str; char *to_name; char *default_tos[1]; @@ -46,7 +629,7 @@ const char **tos, **froms, **urls; char **subjects; - if (strcmp(type, "result")) + if (type == JABBER_IQ_ERROR) return; child = xmlnode_get_child(packet, "mailbox"); @@ -87,6 +670,10 @@ message= xmlnode_get_child(child, "mail-thread-info"); for (i=0; message; message = xmlnode_get_next_twin(message), i++) { + xmlnode *sender_node, *subject_node; + const char *from, *tid; + char *subject; + subject_node = xmlnode_get_child(message, "subject"); sender_node = xmlnode_get_child(message, "senders"); sender_node = xmlnode_get_child(sender_node, "sender"); @@ -144,9 +731,9 @@ } void -jabber_gmail_poke(JabberStream *js, xmlnode *packet) +jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *new_mail) { - const char *type; xmlnode *query; JabberIq *iq; @@ -154,11 +741,8 @@ if (!purple_account_get_check_mail(js->gc->account)) return; - type = xmlnode_get_attrib(packet, "type"); - - /* Is this an initial incoming mail notification? If so, send a request for more info */ - if (strcmp(type, "set") || !xmlnode_get_child(packet, "new-mail")) + if (type != JABBER_IQ_SET) return; purple_debug(PURPLE_DEBUG_MISC, "jabber", @@ -529,3 +1113,139 @@ 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; + } + } + + while (hosts != NULL) { + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address */ + g_free(hosts->data); + hosts = g_slist_delete_link(hosts, hosts); + } +} + +static void +jabber_google_jingle_info_common(JabberStream *js, const char *from, + JabberIqType type, xmlnode *query) +{ + const xmlnode *stun = xmlnode_get_child(query, "stun"); + gchar *my_bare_jid; + + /* + * Make sure that random people aren't sending us STUN servers. Per + * http://code.google.com/apis/talk/jep_extensions/jingleinfo.html, these + * stanzas are stamped from our bare JID. + */ + if (from) { + my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); + if (!purple_strequal(from, my_bare_jid)) { + purple_debug_warning("jabber", "got google:jingleinfo with invalid from (%s)\n", + from); + g_free(my_bare_jid); + return; + } + + g_free(my_bare_jid); + } + + if (type == JABBER_IQ_ERROR || type == JABBER_IQ_GET) + return; + + 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... */ +} + +static void +jabber_google_jingle_info_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", + GOOGLE_JINGLE_INFO_NAMESPACE); + + if (query) + jabber_google_jingle_info_common(js, from, type, query); + else + purple_debug_warning("jabber", "Got invalid google:jingleinfo\n"); +} + +void +jabber_google_handle_jingle_info(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *child) +{ + jabber_google_jingle_info_common(js, from, type, child); +} + +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 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/google.h --- a/libpurple/protocols/jabber/google.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/google.h Mon Apr 20 00:10:51 2009 +0000 @@ -18,16 +18,21 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_GOOGLE_H_ -#define _PURPLE_GOOGLE_H_ +#ifndef PURPLE_JABBER_GOOGLE_H_ +#define PURPLE_JABBER_GOOGLE_H_ /* This is a place for Google Talk-specific XMPP extensions to live * 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); +void jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *new_mail); void jabber_google_roster_init(JabberStream *js); void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item); @@ -45,6 +50,12 @@ char *jabber_google_format_to_html(const char *text); - +gboolean jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type); +void jabber_google_session_parse(JabberStream *js, const char *from, JabberIqType type, const char *iq, xmlnode *session); -#endif /* _PURPLE_GOOGLE_H_ */ +void jabber_google_handle_jingle_info(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *child); +void jabber_google_send_jingle_info(JabberStream *js); + +#endif /* PURPLE_JABBER_GOOGLE_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/ibb.c --- a/libpurple/protocols/jabber/ibb.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/ibb.c Mon Apr 20 00:10:51 2009 +0000 @@ -46,12 +46,10 @@ } JabberIBBSession * -jabber_ibb_session_create_from_xmlnode(JabberStream *js, xmlnode *packet, - gpointer user_data) +jabber_ibb_session_create_from_xmlnode(JabberStream *js, const char *from, + const char *id, xmlnode *open, gpointer user_data) { JabberIBBSession *sess = NULL; - xmlnode *open = xmlnode_get_child_with_namespace(packet, "open", - XEP_0047_NAMESPACE); const gchar *sid = xmlnode_get_attrib(open, "sid"); const gchar *block_size = xmlnode_get_attrib(open, "block-size"); @@ -66,9 +64,8 @@ return NULL; } - sess = jabber_ibb_session_create(js, sid, - xmlnode_get_attrib(packet, "from"), user_data); - sess->id = g_strdup(xmlnode_get_attrib(packet, "id")); + sess = jabber_ibb_session_create(js, sid, from, user_data); + sess->id = g_strdup(id); sess->block_size = atoi(block_size); /* if we create a session from an incoming request, it means the session is immediatly open... */ @@ -198,11 +195,13 @@ } static void -jabber_ibb_session_opened_cb(JabberStream *js, xmlnode *packet, gpointer data) +jabber_ibb_session_opened_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { JabberIBBSession *sess = (JabberIBBSession *) data; - if (strcmp(xmlnode_get_attrib(packet, "type"), "error") == 0) { + if (type == JABBER_IQ_ERROR) { sess->state = JABBER_IBB_SESSION_ERROR; } else { sess->state = JABBER_IBB_SESSION_OPENED; @@ -274,10 +273,11 @@ } static void -jabber_ibb_session_send_acknowledge_cb(JabberStream *js, xmlnode *packet, gpointer data) +jabber_ibb_session_send_acknowledge_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { JabberIBBSession *sess = (JabberIBBSession *) data; - xmlnode *error = xmlnode_get_child(packet, "error"); if (sess) { /* reset callback */ @@ -286,7 +286,7 @@ sess->last_iq_id = NULL; } - if (error) { + if (type == JABBER_IQ_ERROR) { jabber_ibb_session_close(sess); sess->state = JABBER_IBB_SESSION_ERROR; @@ -351,7 +351,7 @@ } static void -jabber_ibb_send_error_response(JabberStream *js, xmlnode *packet) +jabber_ibb_send_error_response(JabberStream *js, const char *to, const char *id) { JabberIq *result = jabber_iq_new(js, JABBER_IQ_ERROR); xmlnode *error = xmlnode_new("error"); @@ -361,9 +361,8 @@ "urn:ietf:params:xml:ns:xmpp-stanzas"); xmlnode_set_attrib(error, "code", "440"); xmlnode_set_attrib(error, "type", "cancel"); - jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); - xmlnode_set_attrib(result->node, "to", - xmlnode_get_attrib(packet, "from")); + jabber_iq_set_id(result, id); + xmlnode_set_attrib(result->node, "to", to); xmlnode_insert_child(error, item_not_found); xmlnode_insert_child(result->node, error); @@ -371,20 +370,17 @@ } void -jabber_ibb_parse(JabberStream *js, xmlnode *packet) +jabber_ibb_parse(JabberStream *js, const char *who, JabberIqType type, + const char *id, xmlnode *child) { - xmlnode *data = xmlnode_get_child_with_namespace(packet, "data", - XEP_0047_NAMESPACE); - xmlnode *close = xmlnode_get_child_with_namespace(packet, "close", - XEP_0047_NAMESPACE); - xmlnode *open = xmlnode_get_child_with_namespace(packet, "open", - XEP_0047_NAMESPACE); - const gchar *sid = - data ? xmlnode_get_attrib(data, "sid") : - close ? xmlnode_get_attrib(close, "sid") : NULL; + const char *name = child->name; + gboolean data = g_str_equal(name, "data"); + gboolean close = g_str_equal(name, "close"); + gboolean open = g_str_equal(name, "open"); + const gchar *sid = (data || close) ? + xmlnode_get_attrib(child, "sid") : NULL; JabberIBBSession *sess = sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL; - const gchar *who = xmlnode_get_attrib(packet, "from"); if (sess) { @@ -394,7 +390,7 @@ purple_debug_error("jabber", "Got IBB iq from wrong JID, ignoring\n"); } else if (data) { - const gchar *seq_attr = xmlnode_get_attrib(data, "seq"); + const gchar *seq_attr = xmlnode_get_attrib(child, "seq"); guint16 seq = (seq_attr ? atoi(seq_attr) : 0); /* reject the data, and set the session in error if we get an @@ -403,12 +399,11 @@ /* sequence # is the expected... */ JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT); - jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); - xmlnode_set_attrib(result->node, "to", - xmlnode_get_attrib(packet, "from")); + jabber_iq_set_id(result, id); + xmlnode_set_attrib(result->node, "to", who); if (sess->data_received_cb) { - gchar *base64 = xmlnode_get_data(data); + gchar *base64 = xmlnode_get_data(child); gsize size; gpointer rawdata = purple_base64_decode(base64, &size); @@ -475,20 +470,19 @@ iterator = g_list_next(iterator)) { JabberIBBOpenHandler *handler = iterator->data; - if (handler(js, packet)) { + if (handler(js, who, id, child)) { result = jabber_iq_new(js, JABBER_IQ_RESULT); - xmlnode_set_attrib(result->node, "to", - xmlnode_get_attrib(packet, "from")); - jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "to", who); + jabber_iq_set_id(result, id); jabber_iq_send(result); return; } } /* no open callback returned success, reject */ - jabber_ibb_send_error_response(js, packet); + jabber_ibb_send_error_response(js, who, id); } else { /* send error reply */ - jabber_ibb_send_error_response(js, packet); + jabber_ibb_send_error_response(js, who, id); } } @@ -508,6 +502,10 @@ jabber_ibb_init(void) { jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal); + + jabber_iq_register_handler("close", XEP_0047_NAMESPACE, jabber_ibb_parse); + jabber_iq_register_handler("data", XEP_0047_NAMESPACE, jabber_ibb_parse); + jabber_iq_register_handler("open", XEP_0047_NAMESPACE, jabber_ibb_parse); } void diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/ibb.h --- a/libpurple/protocols/jabber/ibb.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/ibb.h Mon Apr 20 00:10:51 2009 +0000 @@ -14,8 +14,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA */ -#ifndef _PURPLE_JABBER_IBB_H_ -#define _PURPLE_JABBER_IBB_H_ +#ifndef PURPLE_JABBER_IBB_H_ +#define PURPLE_JABBER_IBB_H_ #include "jabber.h" #include "iq.h" @@ -32,7 +32,8 @@ typedef void (JabberIBBErrorCallback)(JabberIBBSession *); typedef void (JabberIBBSentCallback)(JabberIBBSession *); -typedef gboolean (JabberIBBOpenHandler)(JabberStream *js, xmlnode *packet); +typedef gboolean (JabberIBBOpenHandler)(JabberStream *js, const char *from, + const char *id, xmlnode *open); typedef enum { JABBER_IBB_SESSION_NOT_OPENED, @@ -71,7 +72,7 @@ JabberIBBSession *jabber_ibb_session_create(JabberStream *js, const gchar *sid, const gchar *who, gpointer user_data); JabberIBBSession *jabber_ibb_session_create_from_xmlnode(JabberStream *js, - xmlnode *packet, gpointer user_data); + const gchar *from, const gchar *id, xmlnode *open, gpointer user_data); void jabber_ibb_session_destroy(JabberIBBSession *sess); @@ -107,7 +108,8 @@ gpointer jabber_ibb_session_get_user_data(JabberIBBSession *sess); /* handle incoming packet */ -void jabber_ibb_parse(JabberStream *js, xmlnode *packet); +void jabber_ibb_parse(JabberStream *js, const char *who, JabberIqType type, + const char *id, xmlnode *child); /* add a handler for open session */ void jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb); @@ -116,4 +118,4 @@ void jabber_ibb_init(void); void jabber_ibb_uninit(void); -#endif /* _PURPLE_JABBER_IBB_H_ */ +#endif /* PURPLE_JABBER_IBB_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/iq.c --- a/libpurple/protocols/jabber/iq.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/iq.c Mon Apr 20 00:10:51 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" @@ -143,23 +144,19 @@ g_free(iq); } -static void jabber_iq_last_parse(JabberStream *js, xmlnode *packet) +static void jabber_iq_last_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet) { JabberIq *iq; - const char *type; - const char *from; - const char *id; xmlnode *query; char *idle_time; - type = xmlnode_get_attrib(packet, "type"); - from = xmlnode_get_attrib(packet, "from"); - id = xmlnode_get_attrib(packet, "id"); - - if(type && !strcmp(type, "get")) { + if(type == JABBER_IQ_GET) { iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "jabber:iq:last"); jabber_iq_set_id(iq, id); - xmlnode_set_attrib(iq->node, "to", from); + if (from) + xmlnode_set_attrib(iq->node, "to", from); query = xmlnode_get_child(iq->node, "query"); @@ -171,88 +168,67 @@ } } -static void jabber_iq_time_parse(JabberStream *js, xmlnode *packet) +static void jabber_iq_time_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *child) { - const char *type, *from, *id, *xmlns; + const char *xmlns; JabberIq *iq; - xmlnode *query; time_t now_t; + struct tm now_local; + struct tm now_utc; struct tm *now; time(&now_t); now = localtime(&now_t); - - type = xmlnode_get_attrib(packet, "type"); - from = xmlnode_get_attrib(packet, "from"); - id = xmlnode_get_attrib(packet, "id"); + memcpy(&now_local, now, sizeof(struct tm)); + now = gmtime(&now_t); + memcpy(&now_utc, now, sizeof(struct tm)); - /* we're gonna throw this away in a moment, but we need it - * to get the xmlns, so we can figure out if this is - * jabber:iq:time or urn:xmpp:time */ - query = xmlnode_get_child(packet, "query"); - xmlns = xmlnode_get_namespace(query); + xmlns = xmlnode_get_namespace(child); - if(type && !strcmp(type, "get")) { + if(type == JABBER_IQ_GET) { xmlnode *utc; - const char *date; + const char *date, *tz, *display; - iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, xmlns); + iq = jabber_iq_new(js, JABBER_IQ_RESULT); jabber_iq_set_id(iq, id); - xmlnode_set_attrib(iq->node, "to", from); - - query = xmlnode_get_child(iq->node, "query"); + if (from) + xmlnode_set_attrib(iq->node, "to", from); - date = purple_utf8_strftime("%Y%m%dT%T", now); - utc = xmlnode_new_child(query, "utc"); - xmlnode_insert_data(utc, date, -1); + child = xmlnode_new_child(iq->node, child->name); + xmlnode_set_namespace(child, xmlns); + utc = xmlnode_new_child(child, "utc"); if(!strcmp("urn:xmpp:time", xmlns)) { - xmlnode_insert_data(utc, "Z", 1); /* of COURSE the thing that is the same is different */ + tz = purple_get_tzoff_str(&now_local, TRUE); + xmlnode_insert_data(xmlnode_new_child(child, "tzo"), tz, -1); - date = purple_get_tzoff_str(now, TRUE); - xmlnode_insert_data(xmlnode_new_child(query, "tzo"), date, -1); + date = purple_utf8_strftime("%FT%TZ", &now_utc); + xmlnode_insert_data(utc, date, -1); } else { /* jabber:iq:time */ - date = purple_utf8_strftime("%Z", now); - xmlnode_insert_data(xmlnode_new_child(query, "tz"), date, -1); + tz = purple_utf8_strftime("%Z", &now_local); + xmlnode_insert_data(xmlnode_new_child(child, "tz"), tz, -1); - date = purple_utf8_strftime("%d %b %Y %T", now); - xmlnode_insert_data(xmlnode_new_child(query, "display"), date, -1); + date = purple_utf8_strftime("%Y%m%dT%T", &now_utc); + xmlnode_insert_data(utc, date, -1); + + display = purple_utf8_strftime("%d %b %Y %T", &now_local); + xmlnode_insert_data(xmlnode_new_child(child, "display"), display, -1); } jabber_iq_send(iq); } } -static void urn_xmpp_ping_parse(JabberStream *js, xmlnode *packet) -{ - const char *type, *id, *from; - JabberIq *iq; - - type = xmlnode_get_attrib(packet, "type"); - from = xmlnode_get_attrib(packet, "from"); - id = xmlnode_get_attrib(packet, "id"); - - if(type && !strcmp(type, "get")) { - iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "urn:xmpp:ping"); - - jabber_iq_set_id(iq, id); - xmlnode_set_attrib(iq->node, "to", from); - - jabber_iq_send(iq); - } else { - /* XXX: error */ - } -} - -static void jabber_iq_version_parse(JabberStream *js, xmlnode *packet) +static void jabber_iq_version_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet) { JabberIq *iq; - const char *type, *from, *id; xmlnode *query; - type = xmlnode_get_attrib(packet, "type"); - - if(type && !strcmp(type, "get")) { + if(type == JABBER_IQ_GET) { GHashTable *ui_info; const char *ui_name = NULL, *ui_version = NULL; #if 0 @@ -265,11 +241,10 @@ osinfo.machine); } #endif - from = xmlnode_get_attrib(packet, "from"); - id = xmlnode_get_attrib(packet, "id"); iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "jabber:iq:version"); - xmlnode_set_attrib(iq->node, "to", from); + if (from) + xmlnode_set_attrib(iq->node, "to", from); jabber_iq_set_id(iq, id); query = xmlnode_get_child(iq->node, "query"); @@ -310,33 +285,56 @@ void jabber_iq_parse(JabberStream *js, xmlnode *packet) { JabberCallbackData *jcd; - xmlnode *query, *error, *x; + xmlnode *child, *error, *x; const char *xmlns; - const char *type, *id, *from; - JabberIqHandler *jih; + const char *iq_type, *id, *from; + JabberIqType type = JABBER_IQ_NONE; - query = xmlnode_get_child(packet, "query"); - type = xmlnode_get_attrib(packet, "type"); + /* + * child will be either the first tag child or NULL if there is no child. + * Historically, we used just the 'query' subchild, but newer XEPs use + * differently named children. Grabbing the first child is (for the time + * being) sufficient. + */ + for (child = packet->child; child; child = child->next) { + if (child->type == XMLNODE_TYPE_TAG) + break; + } + + iq_type = xmlnode_get_attrib(packet, "type"); from = xmlnode_get_attrib(packet, "from"); id = xmlnode_get_attrib(packet, "id"); - if(type == NULL || !(!strcmp(type, "get") || !strcmp(type, "set") - || !strcmp(type, "result") || !strcmp(type, "error"))) { + if (iq_type) { + if (!strcmp(iq_type, "get")) + type = JABBER_IQ_GET; + else if (!strcmp(iq_type, "set")) + type = JABBER_IQ_SET; + else if (!strcmp(iq_type, "result")) + type = JABBER_IQ_RESULT; + else if (!strcmp(iq_type, "error")) + type = JABBER_IQ_ERROR; + } + + if (type == JABBER_IQ_NONE) { purple_debug_error("jabber", "IQ with invalid type ('%s') - ignoring.\n", - type ? type : "(null)"); + iq_type ? iq_type : "(null)"); return; } /* All IQs must have an ID, so send an error for a set/get that doesn't */ if(!id || !*id) { - if(!strcmp(type, "set") || !strcmp(type, "get")) { + if(type == JABBER_IQ_SET || type == JABBER_IQ_GET) { JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR); xmlnode_free(iq->node); iq->node = xmlnode_copy(packet); - xmlnode_set_attrib(iq->node, "to", from); - xmlnode_remove_attrib(iq->node, "from"); + if (from) { + xmlnode_set_attrib(iq->node, "to", from); + xmlnode_remove_attrib(iq->node, "from"); + } + xmlnode_set_attrib(iq->node, "type", "error"); /* This id is clearly not useful, but we must put something there for a valid stanza */ iq->id = jabber_get_next_id(js); @@ -348,67 +346,46 @@ jabber_iq_send(iq); } else - purple_debug_error("jabber", "IQ of type '%s' missing id - ignoring.\n", type); + purple_debug_error("jabber", "IQ of type '%s' missing id - ignoring.\n", + iq_type); return; } /* First, lets see if a special callback got registered */ - - if(!strcmp(type, "result") || !strcmp(type, "error")) { - if(id && *id && (jcd = g_hash_table_lookup(js->iq_callbacks, id))) { - jcd->callback(js, packet, jcd->data); + if(type == JABBER_IQ_RESULT || type == JABBER_IQ_ERROR) { + if((jcd = g_hash_table_lookup(js->iq_callbacks, id))) { + jcd->callback(js, from, type, id, packet, jcd->data); jabber_iq_remove_callback_by_id(js, id); return; } } /* Apparently not, so lets see if we have a pre-defined handler */ + if(child && (xmlns = xmlnode_get_namespace(child))) { + char *key = g_strdup_printf("%s %s", child->name, xmlns); + JabberIqHandler *jih = g_hash_table_lookup(iq_handlers, key); + g_free(key); - if(query && (xmlns = xmlnode_get_namespace(query))) { - if((jih = g_hash_table_lookup(iq_handlers, xmlns))) { - jih(js, packet); + if(jih) { + jih(js, from, type, id, child); return; } } - if(xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si")) { - jabber_si_parse(js, packet); - return; - } - - if(xmlnode_get_child_with_namespace(packet, "new-mail", "google:mail:notify")) { - jabber_gmail_poke(js, packet); - return; - } - purple_debug_info("jabber", "jabber_iq_parse\n"); - if(xmlnode_get_child_with_namespace(packet, "ping", "urn:xmpp:ping")) { - jabber_ping_parse(js, packet); - return; - } - - if (xmlnode_get_child_with_namespace(packet, "data", XEP_0231_NAMESPACE)) { - jabber_data_parse(js, packet); - return; - } - - if (xmlnode_get_child_with_namespace(packet, "data", XEP_0047_NAMESPACE) - || xmlnode_get_child_with_namespace(packet, "close", XEP_0047_NAMESPACE) - || xmlnode_get_child_with_namespace(packet, "open", XEP_0047_NAMESPACE)) { - jabber_ibb_parse(js, packet); - return; - } - /* If we get here, send the default error reply mandated by XMPP-CORE */ - if(!strcmp(type, "set") || !strcmp(type, "get")) { + if(type == JABBER_IQ_SET || type == JABBER_IQ_GET) { JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR); xmlnode_free(iq->node); iq->node = xmlnode_copy(packet); - xmlnode_set_attrib(iq->node, "to", from); - xmlnode_remove_attrib(iq->node, "from"); + if (from) { + xmlnode_set_attrib(iq->node, "to", from); + xmlnode_remove_attrib(iq->node, "from"); + } + xmlnode_set_attrib(iq->node, "type", "error"); error = xmlnode_new_child(iq->node, "error"); xmlnode_set_attrib(error, "type", "cancel"); @@ -420,26 +397,50 @@ } } -void jabber_iq_register_handler(const char *xmlns, JabberIqHandler *handlerfunc) +void jabber_iq_register_handler(const char *node, const char *xmlns, JabberIqHandler *handlerfunc) { - g_hash_table_replace(iq_handlers, g_strdup(xmlns), handlerfunc); + /* + * This is valid because nodes nor namespaces cannot have spaces in them + * (see http://www.w3.org/TR/2006/REC-xml-20060816/ and + * http://www.w3.org/TR/REC-xml-names/) + */ + char *key = g_strdup_printf("%s %s", node, xmlns); + g_hash_table_replace(iq_handlers, key, handlerfunc); } void jabber_iq_init(void) { iq_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); - jabber_iq_register_handler("jabber:iq:roster", jabber_roster_parse); - jabber_iq_register_handler("jabber:iq:oob", jabber_oob_parse); - jabber_iq_register_handler("http://jabber.org/protocol/bytestreams", jabber_bytestreams_parse); - jabber_iq_register_handler("jabber:iq:last", jabber_iq_last_parse); - jabber_iq_register_handler("jabber:iq:time", jabber_iq_time_parse); - jabber_iq_register_handler("urn:xmpp:time", jabber_iq_time_parse); - jabber_iq_register_handler("jabber:iq:version", jabber_iq_version_parse); - jabber_iq_register_handler("http://jabber.org/protocol/disco#info", jabber_disco_info_parse); - 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, jingle_parse); + jabber_iq_register_handler("mailbox", "google:mail:notify", + jabber_gmail_poke); + jabber_iq_register_handler("new-mail", "google:mail:notify", + jabber_gmail_poke); + jabber_iq_register_handler("ping", "urn:xmpp:ping", jabber_ping_parse); + jabber_iq_register_handler("query", GOOGLE_JINGLE_INFO_NAMESPACE, + jabber_google_handle_jingle_info); + jabber_iq_register_handler("query", "http://jabber.org/protocol/bytestreams", + jabber_bytestreams_parse); + jabber_iq_register_handler("query", "http://jabber.org/protocol/disco#info", + jabber_disco_info_parse); + jabber_iq_register_handler("query", "http://jabber.org/protocol/disco#items", + jabber_disco_items_parse); + jabber_iq_register_handler("query", "jabber:iq:last", jabber_iq_last_parse); + jabber_iq_register_handler("query", "jabber:iq:oob", jabber_oob_parse); + jabber_iq_register_handler("query", "jabber:iq:register", + jabber_register_parse); + jabber_iq_register_handler("query", "jabber:iq:roster", + jabber_roster_parse); + jabber_iq_register_handler("query", "jabber:iq:time", jabber_iq_time_parse); + jabber_iq_register_handler("query", "jabber:iq:version", + jabber_iq_version_parse); +#ifdef USE_VV + jabber_iq_register_handler("session", "http://www.google.com/session", + jabber_google_session_parse); +#endif + jabber_iq_register_handler("time", "urn:xmpp:time", jabber_iq_time_parse); + } void jabber_iq_uninit(void) @@ -447,4 +448,3 @@ g_hash_table_destroy(iq_handlers); iq_handlers = NULL; } - diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/iq.h --- a/libpurple/protocols/jabber/iq.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/iq.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,12 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_IQ_H_ -#define _PURPLE_JABBER_IQ_H_ - -#include "jabber.h" - -typedef struct _JabberIq JabberIq; +#ifndef PURPLE_JABBER_IQ_H_ +#define PURPLE_JABBER_IQ_H_ typedef enum { JABBER_IQ_SET, @@ -34,9 +30,51 @@ JABBER_IQ_NONE } JabberIqType; -typedef void (JabberIqHandler)(JabberStream *js, xmlnode *packet); +#include "jabber.h" + +typedef struct _JabberIq JabberIq; -typedef void (JabberIqCallback)(JabberStream *js, xmlnode *packet, gpointer data); +/** + * A JabberIqHandler is called to process an incoming IQ stanza. + * Handlers typically process unsolicited incoming GETs or SETs for their + * registered namespace, but may be called to handle the results of a + * GET or SET that we generated if no JabberIqCallback was generated + * The handler may be called for the results of a GET or SET (RESULT or ERROR) + * that we generated + * if the generating function did not register a JabberIqCallback. + * + * @param js The JabberStream object. + * @param from The remote entity (the from attribute on the stanza) + * @param type The IQ type. + * @param id The IQ id (the id attribute on the stanza) + * @param child The child element of the stanza that matches the name + * and namespace registered with jabber_iq_register_handler. + * + * @see jabber_iq_register_handler() + * @see JabberIqCallback + */ +typedef void (JabberIqHandler)(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *child); + +/** + * A JabberIqCallback is called to process the results of a GET or SET that + * we send to a remote entity. The callback is matched based on the id + * of the incoming stanza (which matches the one on the initial stanza). + * + * @param js The JabberStream object. + * @param from The remote entity (the from attribute on the stanza) + * @param type The IQ type. The only possible values are JABBER_IQ_RESULT + * and JABBER_IQ_ERROR. + * @param id The IQ id (the id attribute on the stanza) + * @param packet The stanza + * @param data The callback data passed to jabber_iq_set_callback() + * + * @see jabber_iq_set_callback() + */ +typedef void (JabberIqCallback)(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data); struct _JabberIq { JabberIqType type; @@ -65,6 +103,7 @@ void jabber_iq_init(void); void jabber_iq_uninit(void); -void jabber_iq_register_handler(const char *xmlns, JabberIqHandler *func); +void jabber_iq_register_handler(const char *node, const char *xmlns, + JabberIqHandler *func); -#endif /* _PURPLE_JABBER_IQ_H_ */ +#endif /* PURPLE_JABBER_IQ_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Mon Apr 20 00:10:51 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) @@ -85,10 +87,11 @@ } static void -jabber_session_initialized_cb(JabberStream *js, xmlnode *packet, gpointer data) +jabber_session_initialized_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { - const char *type = xmlnode_get_attrib(packet, "type"); - if(type && !strcmp(type, "result")) { + if (type == JABBER_IQ_RESULT) { jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); if(js->unregistration) jabber_unregister_account_cb(js); @@ -112,13 +115,13 @@ jabber_iq_send(iq); } -static void jabber_bind_result_cb(JabberStream *js, xmlnode *packet, - gpointer data) +static void jabber_bind_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { - const char *type = xmlnode_get_attrib(packet, "type"); xmlnode *bind; - if(type && !strcmp(type, "result") && + if (type == JABBER_IQ_RESULT && (bind = xmlnode_get_child_with_namespace(packet, "bind", "urn:ietf:params:xml:ns:xmpp-bind"))) { xmlnode *jid; char *full_jid; @@ -446,13 +449,7 @@ g_free(txt); } -static void jabber_pong_cb(JabberStream *js, xmlnode *packet, gpointer unused) -{ - purple_timeout_remove(js->keepalive_timeout); - js->keepalive_timeout = -1; -} - -static gboolean jabber_pong_timeout(PurpleConnection *gc) +static gboolean jabber_keepalive_timeout(PurpleConnection *gc) { JabberStream *js = gc->proto_data; purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, @@ -466,14 +463,9 @@ JabberStream *js = gc->proto_data; if (js->keepalive_timeout == -1) { - JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET); - - xmlnode *ping = xmlnode_new_child(iq->node, "ping"); - xmlnode_set_namespace(ping, "urn:xmpp:ping"); - - js->keepalive_timeout = purple_timeout_add_seconds(120, (GSourceFunc)(jabber_pong_timeout), gc); - jabber_iq_set_callback(iq, jabber_pong_cb, NULL); - jabber_iq_send(iq); + jabber_ping_jid(js, js->user->domain); + js->keepalive_timeout = purple_timeout_add_seconds(120, + (GSourceFunc)(jabber_keepalive_timeout), gc); } } @@ -729,6 +721,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, @@ -796,14 +792,15 @@ } static void -jabber_registration_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +jabber_registration_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { PurpleAccount *account = purple_connection_get_account(js->gc); - const char *type = xmlnode_get_attrib(packet, "type"); char *buf; char *to = data; - if(!strcmp(type, "result")) { + if (type == JABBER_IQ_RESULT) { if(js->registration) { buf = g_strdup_printf(_("Registration of %s@%s successful"), js->user->node, js->user->domain); @@ -831,13 +828,14 @@ } g_free(to); if(js->registration) - jabber_connection_schedule_close(js); + jabber_connection_schedule_close(js); } static void -jabber_unregistration_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +jabber_unregistration_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { - const char *type = xmlnode_get_attrib(packet, "type"); char *buf; char *to = data; @@ -845,7 +843,7 @@ * the server, so there should always be a 'to' address. */ g_return_if_fail(to != NULL); - if(!strcmp(type, "result")) { + if (type == JABBER_IQ_RESULT) { buf = g_strdup_printf(_("Registration from %s successfully removed"), to); purple_notify_info(NULL, _("Unregistration Successful"), @@ -994,31 +992,30 @@ jabber_iq_send(iq); } -void jabber_register_parse(JabberStream *js, xmlnode *packet) +void jabber_register_parse(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *query) { PurpleAccount *account = purple_connection_get_account(js->gc); - const char *type; - const char *from; PurpleRequestFields *fields; PurpleRequestFieldGroup *group; PurpleRequestField *field; - xmlnode *query, *x, *y; + xmlnode *x, *y; char *instructions; JabberRegisterCBData *cbdata; gboolean registered = FALSE; - if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) + if (type != JABBER_IQ_RESULT) return; - from = xmlnode_get_attrib(packet, "from"); + if (!from) + from = js->serverFQDN; + g_return_if_fail(from != NULL); if(js->registration) { /* get rid of the login thingy */ purple_connection_set_state(js->gc, PURPLE_CONNECTED); } - query = xmlnode_get_child(packet, "query"); - if(xmlnode_get_child(query, "registered")) { registered = TRUE; @@ -1223,6 +1220,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)) { @@ -1252,10 +1253,14 @@ } } -static void jabber_unregister_account_iq_cb(JabberStream *js, xmlnode *packet, gpointer data) { +static void +jabber_unregister_account_iq_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ PurpleAccount *account = purple_connection_get_account(js->gc); - const char *type = xmlnode_get_attrib(packet,"type"); - if(!strcmp(type,"error")) { + + if (type == JABBER_IQ_ERROR) { char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error unregistering account"), @@ -1263,7 +1268,7 @@ g_free(msg); if(js->unregistration_cb) js->unregistration_cb(account, FALSE, js->unregistration_user_data); - } else if(!strcmp(type,"result")) { + } else { purple_notify_info(js->gc, _("Account successfully unregistered"), _("Account successfully unregistered"), NULL); if(js->unregistration_cb) @@ -1320,6 +1325,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 +1429,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; @@ -1492,7 +1509,9 @@ js->idle = idle ? time(NULL) - idle : idle; } -static void jabber_blocklist_parse(JabberStream *js, xmlnode *packet, gpointer data) +static void jabber_blocklist_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { xmlnode *blocklist, *item; PurpleAccount *account; @@ -1916,14 +1935,11 @@ } static void -jabber_password_change_result_cb(JabberStream *js, xmlnode *packet, - gpointer data) +jabber_password_change_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { - const char *type; - - type = xmlnode_get_attrib(packet, "type"); - - if(type && !strcmp(type, "result")) { + if (type == JABBER_IQ_RESULT) { purple_notify_info(js->gc, _("Password Changed"), _("Password Changed"), _("Your password has been changed.")); @@ -2455,10 +2471,16 @@ static PurpleCmdRet jabber_cmd_ping(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { + PurpleAccount *account; + PurpleConnection *pc; + if(!args || !args[0]) return PURPLE_CMD_RET_FAILED; - if(!jabber_ping_jid(conv, args[0])) { + account = purple_conversation_get_account(conv); + pc = purple_account_get_connection(account); + + if(!jabber_ping_jid(purple_connection_get_protocol_data(pc), args[0])) { *error = g_strdup_printf(_("Unable to ping user %s"), args[0]); return PURPLE_CMD_RET_FAILED; } @@ -2590,6 +2612,277 @@ return TRUE; } +#ifdef USE_VV +typedef struct { + PurpleAccount *account; + gchar *who; + PurpleMediaSessionType type; + +} JabberMediaRequest; + +static void +jabber_media_cancel_cb(JabberMediaRequest *request, + PurpleRequestFields *fields) +{ + g_free(request->who); + g_free(request); +} + +static void +jabber_media_ok_cb(JabberMediaRequest *request, PurpleRequestFields *fields) +{ + PurpleRequestField *field = + purple_request_fields_get_field(fields, "resource"); + int selected_id = purple_request_field_choice_get_value(field); + GList *labels = purple_request_field_choice_get_labels(field); + gchar *who = g_strdup_printf("%s/%s", request->who, + (gchar*)g_list_nth_data(labels, selected_id)); + jabber_initiate_media(request->account, who, request->type); + + g_free(who); + g_free(request->who); + g_free(request); +} +#endif + +gboolean +jabber_initiate_media(PurpleAccount *account, const char *who, + PurpleMediaSessionType type) +{ +#ifdef USE_VV + JabberStream *js = (JabberStream *) + purple_account_get_connection(account)->proto_data; + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + char *resource; + + if (!js) { + purple_debug_error("jabber", + "jabber_initiate_media: NULL stream\n"); + return FALSE; + } + + + if((resource = jabber_get_resource(who)) != NULL) { + /* they've specified a resource, no need to ask or + * default or anything, just do it */ + + jb = jabber_buddy_find(js, who, FALSE); + jbr = jabber_buddy_find_resource(jb, resource); + g_free(resource); + + if (type & PURPLE_MEDIA_AUDIO && + !jabber_resource_has_capability(jbr, + JINGLE_APP_RTP_SUPPORT_AUDIO) && + jabber_resource_has_capability(jbr, + GOOGLE_VOICE_CAP)) + return jabber_google_session_initiate(js, who, type); + else + return jingle_rtp_initiate_media(js, who, type); + } + + jb = jabber_buddy_find(js, who, FALSE); + + if(!jb || !jb->resources) { + /* no resources online, we're trying to initiate with someone + * whose presence we're not subscribed to, or + * someone who is offline. Let's inform the user */ + char *msg; + + if(!jb) { + msg = g_strdup_printf(_("Unable to initiate media with %s: invalid JID"), who); + } else if(jb->subscription & JABBER_SUB_TO) { + msg = g_strdup_printf(_("Unable to initiate media with %s: user is not online"), who); + } else { + msg = g_strdup_printf(_("Unable to initiate media with %s: not subscribed to user presence"), who); + } + + purple_notify_error(account, _("Media Initiation Failed"), + _("Media Initiation Failed"), msg); + g_free(msg); + return FALSE; + } else if(!jb->resources->next) { + /* only 1 resource online (probably our most common case) + * so no need to ask who to initiate with */ + gchar *name; + gboolean result; + jbr = jb->resources->data; + name = g_strdup_printf("%s/%s", who, jbr->name); + result = jabber_initiate_media(account, name, type); + g_free(name); + return result; + } else { + /* we've got multiple resources, + * we need to pick one to initiate with */ + GList *l; + char *msg; + PurpleRequestFields *fields; + PurpleRequestField *field = purple_request_field_choice_new( + "resource", _("Resource"), 0); + PurpleRequestFieldGroup *group; + JabberMediaRequest *request; + + for(l = jb->resources; l; l = l->next) + { + JabberBuddyResource *ljbr = l->data; + PurpleMediaCaps caps; + gchar *name; + name = g_strdup_printf("%s/%s", who, ljbr->name); + caps = jabber_get_media_caps(account, name); + g_free(name); + + if ((type & PURPLE_MEDIA_AUDIO) && + (type & PURPLE_MEDIA_VIDEO)) { + if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) { + jbr = ljbr; + purple_request_field_choice_add( + field, jbr->name); + } + } else if (type & (PURPLE_MEDIA_AUDIO) && + (caps & PURPLE_MEDIA_CAPS_AUDIO)) { + jbr = ljbr; + purple_request_field_choice_add( + field, jbr->name); + }else if (type & (PURPLE_MEDIA_VIDEO) && + (caps & PURPLE_MEDIA_CAPS_VIDEO)) { + jbr = ljbr; + purple_request_field_choice_add( + field, jbr->name); + } + } + + if (jbr == NULL) { + purple_debug_error("jabber", + "No resources available\n"); + return FALSE; + } + + if (g_list_length(purple_request_field_choice_get_labels( + field)) <= 1) { + gchar *name; + gboolean result; + purple_request_field_destroy(field); + name = g_strdup_printf("%s/%s", who, jbr->name); + result = jabber_initiate_media(account, name, type); + g_free(name); + return result; + } + + msg = g_strdup_printf(_("Please select the resource of %s with which you would like to start a media session."), who); + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + request = g_new0(JabberMediaRequest, 1); + request->account = account; + request->who = g_strdup(who); + request->type = type; + + purple_request_field_group_add_field(group, field); + purple_request_fields_add_group(fields, group); + purple_request_fields(account, _("Select a Resource"), msg, + NULL, fields, _("Initiate Media"), + G_CALLBACK(jabber_media_ok_cb), _("Cancel"), + G_CALLBACK(jabber_media_cancel_cb), + account, who, NULL, request); + + g_free(msg); + return TRUE; + } +#endif + return FALSE; +} + +PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who) +{ +#ifdef USE_VV + JabberStream *js = (JabberStream *) + purple_account_get_connection(account)->proto_data; + JabberBuddy *jb; + JabberBuddyResource *jbr; + PurpleMediaCaps caps = PURPLE_MEDIA_CAPS_NONE; + gchar *resource; + + if (!js) { + purple_debug_info("jabber", + "jabber_can_do_media: NULL stream\n"); + return FALSE; + } + + if ((resource = jabber_get_resource(who)) != NULL) { + /* they've specified a resource, no need to ask or + * default or anything, just do it */ + + jb = jabber_buddy_find(js, who, FALSE); + jbr = jabber_buddy_find_resource(jb, resource); + g_free(resource); + + if (!jbr) { + purple_debug_error("jabber", "jabber_get_media_caps:" + " Can't find resource %s\n", who); + return caps; + } + + if (jabber_resource_has_capability(jbr, + JINGLE_APP_RTP_SUPPORT_AUDIO)) + caps |= PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION | + PURPLE_MEDIA_CAPS_AUDIO; + if (jabber_resource_has_capability(jbr, + JINGLE_APP_RTP_SUPPORT_VIDEO)) + caps |= PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION | + PURPLE_MEDIA_CAPS_VIDEO; + 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_resource_has_capability(jbr, + JINGLE_TRANSPORT_ICEUDP) && + !jabber_resource_has_capability(jbr, + 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_resource_has_capability(jbr, GOOGLE_VOICE_CAP)) + caps |= PURPLE_MEDIA_CAPS_AUDIO; + return caps; + } + + jb = jabber_buddy_find(js, who, FALSE); + + if(!jb || !jb->resources) { + /* no resources online, we're trying to get caps for someone + * whose presence we're not subscribed to, or + * someone who is offline. */ + return caps; + } else if(!jb->resources->next) { + /* only 1 resource online (probably our most common case) */ + gchar *name; + jbr = jb->resources->data; + name = g_strdup_printf("%s/%s", who, jbr->name); + caps = jabber_get_media_caps(account, name); + g_free(name); + } else { + /* we've got multiple resources, combine their caps */ + GList *l; + + for(l = jb->resources; l; l = l->next) + { + gchar *name; + jbr = l->data; + name = g_strdup_printf("%s/%s", who, jbr->name); + caps |= jabber_get_media_caps(account, name); + g_free(name); + } + } + + return caps; +#else + return PURPLE_MEDIA_CAPS_NONE; +#endif +} + void jabber_register_commands(void) { purple_cmd_register("config", "", PURPLE_CMD_P_PRPL, diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_H_ -#define _PURPLE_JABBER_H_ +#ifndef PURPLE_JABBER_H_ +#define PURPLE_JABBER_H_ typedef enum { JABBER_CAP_NONE = 0, @@ -54,9 +54,13 @@ #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 "iq.h" #include "jutil.h" #include "xmlnode.h" #include "buddy.h" @@ -242,6 +246,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); @@ -269,7 +282,8 @@ void jabber_stream_set_state(JabberStream *js, JabberStreamState state); -void jabber_register_parse(JabberStream *js, xmlnode *packet); +void jabber_register_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, xmlnode *query); void jabber_register_start(JabberStream *js); char *jabber_get_next_id(JabberStream *js); @@ -309,7 +323,10 @@ 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); +gboolean jabber_initiate_media(PurpleAccount *account, const char *who, + PurpleMediaSessionType type); +PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who); void jabber_register_commands(void); void jabber_init_plugin(PurplePlugin *plugin); -#endif /* _PURPLE_JABBER_H_ */ +#endif /* PURPLE_JABBER_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/content.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/content.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/content.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/content.h Mon Apr 20 00:10:51 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 PURPLE_JABBER_JINGLE_CONTENT_H +#define PURPLE_JABBER_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 /* PURPLE_JABBER_JINGLE_CONTENT_H */ + diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/iceudp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/iceudp.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/iceudp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/iceudp.h Mon Apr 20 00:10:51 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 PURPLE_JABBER_JINGLE_ICEUDP_H +#define PURPLE_JABBER_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 /* PURPLE_JABBER_JINGLE_ICEUDP_H */ + diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/jingle.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/jingle.c Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,457 @@ +/* + * @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, const char *from, JabberIqType type, + const char *id, xmlnode *jingle) +{ + const gchar *action; + const gchar *sid; + JingleActionType action_type; + JingleSession *session; + + if (type != JABBER_IQ_SET) { + /* TODO: send iq error here */ + return; + } + + if (!(action = xmlnode_get_attrib(jingle, "action"))) { + /* TODO: 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 { + char *own_jid = g_strdup_printf("%s@%s/%s", js->user->node, + js->user->domain, js->user->resource); + session = jingle_session_create(js, sid, own_jid, from, FALSE); + g_free(own_jid); + } + } + + 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 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/jingle.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/jingle.h Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,87 @@ +/* + * @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 PURPLE_JABBER_JINGLE_H +#define PURPLE_JABBER_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, const char *from, JabberIqType type, + const char *id, xmlnode *child); + +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 /* PURPLE_JABBER_JINGLE_H */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/rawudp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/rawudp.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/rawudp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/rawudp.h Mon Apr 20 00:10:51 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 PURPLE_JABBER_JINGLE_RAWUDP_H +#define PURPLE_JABBER_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 /* PURPLE_JABBER_JINGLE_RAWUDP_H */ + diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/rtp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/rtp.c Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,924 @@ +/** + * @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_account( + purple_media_manager_get(), + purple_connection_get_account(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)); + gchar *ip = purple_media_candidate_get_ip(candidate); + JingleRawUdpCandidate *rawudp_candidate = + jingle_rawudp_candidate_new(id, generation, + purple_media_candidate_get_component_id(candidate), + ip, purple_media_candidate_get_port(candidate)); + g_free(ip); + 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)); + gchar *ip = purple_media_candidate_get_ip(candidate); + gchar *username = purple_media_candidate_get_username(candidate); + gchar *password = purple_media_candidate_get_password(candidate); + PurpleMediaCandidateType type = + purple_media_candidate_get_candidate_type(candidate); + + JingleIceUdpCandidate *iceudp_candidate = jingle_iceudp_candidate_new( + purple_media_candidate_get_component_id(candidate), + purple_media_candidate_get_foundation(candidate), + generation, id, ip, 0, + purple_media_candidate_get_port(candidate), + purple_media_candidate_get_priority(candidate), "udp", + type == PURPLE_MEDIA_CANDIDATE_TYPE_HOST ? "host" : + type == PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX ? "srflx" : + type == PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX ? "prflx" : + type == PURPLE_MEDIA_CANDIDATE_TYPE_RELAY ? "relay" : + "", username, password); + iceudp_candidate->reladdr = + purple_media_candidate_get_base_ip(candidate); + iceudp_candidate->relport = + purple_media_candidate_get_base_port(candidate); + g_free(password); + g_free(username); + g_free(ip); + 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); + g_object_set(new_candidate, + "base-ip", candidate->reladdr, + "base-port", candidate->relport, + "username", candidate->username, + "password", candidate->password, + "priority", candidate->priority, NULL); + 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, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + JingleSession *session = data; + + if (type == JABBER_IQ_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, gboolean local, + JingleSession *session) +{ + purple_debug_info("jingle-rtp", "stream-info: type %d " + "id: %s name: %s\n", type, sid, name); + + g_return_if_fail(JINGLE_IS_SESSION(session)); + + 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(), + purple_connection_get_account(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 *creator; + gchar *media_type; + gchar *remote_jid; + gchar *senders; + gchar *name; + const gchar *transmitter; + gboolean is_audio; + gboolean is_creator; + 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); + + creator = jingle_content_get_creator(content); + if (!strcmp(creator, "initiator")) + is_creator = jingle_session_is_initiator(session); + else + is_creator = !jingle_session_is_initiator(session); + g_free(creator); + + purple_media_add_stream(media, name, remote_jid, + type, is_creator, 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 = purple_media_codec_get_optional_parameters(codec); + gchar *id, *name, *clockrate, *channels; + gchar *codec_str; + xmlnode *payload = xmlnode_new_child(description, "payload-type"); + + id = g_strdup_printf("%d", + purple_media_codec_get_id(codec)); + name = purple_media_codec_get_encoding_name(codec); + clockrate = g_strdup_printf("%d", + purple_media_codec_get_clock_rate(codec)); + channels = g_strdup_printf("%d", + purple_media_codec_get_channels(codec)); + + xmlnode_set_attrib(payload, "name", name); + xmlnode_set_attrib(payload, "id", id); + xmlnode_set_attrib(payload, "clockrate", clockrate); + xmlnode_set_attrib(payload, "channels", channels); + + g_free(channels); + g_free(clockrate); + g_free(name); + g_free(id); + + for (; iter; iter = g_list_next(iter)) { + PurpleKeyValuePair *mparam = iter->data; + xmlnode *param = xmlnode_new_child(payload, "parameter"); + xmlnode_set_attrib(param, "name", mparam->key); + 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); + + if (action == JINGLE_SESSION_ACCEPT) + purple_media_stream_info(media, + PURPLE_MEDIA_INFO_ACCEPT, + name, remote_jid, FALSE); + + 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; + } +} + +gboolean +jingle_rtp_initiate_media(JabberStream *js, const gchar *who, + PurpleMediaSessionType type) +{ + /* create content negotiation */ + JingleSession *session; + JingleContent *content; + JingleTransport *transport; + JabberBuddy *jb; + JabberBuddyResource *jbr; + const gchar *transport_type; + + gchar *resource = 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 FALSE; + } + + resource = jabber_get_resource(who); + jbr = jabber_buddy_find_resource(jb, resource); + g_free(resource); + + 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 FALSE; + } + + /* 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, who, 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); + } + + g_free(me); + + if (jingle_rtp_get_media(session) == NULL) { + return FALSE; + } + + return TRUE; +} + +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_stream_info(media, + PURPLE_MEDIA_INFO_HANGUP, + NULL, NULL, TRUE); + } + } +} + +#endif /* USE_VV */ + diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/rtp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/rtp.h Mon Apr 20 00:10:51 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 PURPLE_JABBER_JINGLE_RTP_H +#define PURPLE_JABBER_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); + +gboolean 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 /* PURPLE_JABBER_JINGLE_RTP_H */ + diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/session.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/session.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/session.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/session.h Mon Apr 20 00:10:51 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 PURPLE_JABBER_JINGLE_SESSION_H +#define PURPLE_JABBER_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 /* PURPLE_JABBER_JINGLE_SESSION_H */ + diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/transport.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/transport.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jingle/transport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle/transport.h Mon Apr 20 00:10:51 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 PURPLE_JABBER_JINGLE_TRANSPORT_H +#define PURPLE_JABBER_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 /* PURPLE_JABBER_JINGLE_TRANSPORT_H */ + diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/jutil.h --- a/libpurple/protocols/jabber/jutil.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/jutil.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_JUTIL_H_ -#define _PURPLE_JABBER_JUTIL_H_ +#ifndef PURPLE_JABBER_JUTIL_H_ +#define PURPLE_JABBER_JUTIL_H_ typedef struct _JabberID { char *node; @@ -43,4 +43,4 @@ PurpleConversation *jabber_find_unnormalized_conv(const char *name, PurpleAccount *account); char *jabber_calculate_data_sha1sum(gconstpointer data, size_t len); -#endif /* _PURPLE_JABBER_JUTIL_H_ */ +#endif /* PURPLE_JABBER_JUTIL_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/message.c --- a/libpurple/protocols/jabber/message.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/message.c Mon Apr 20 00:10:51 2009 +0000 @@ -477,7 +477,9 @@ } JabberDataRef; static void -jabber_message_get_data_cb(JabberStream *js, xmlnode *packet, gpointer data) +jabber_message_get_data_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { JabberDataRef *ref = (JabberDataRef *) data; PurpleConversation *conv = ref->conv; @@ -624,24 +626,27 @@ purple_debug_info("jabber", "found %d smileys\n", g_list_length(smiley_refs)); - if (jm->type == JABBER_MESSAGE_GROUPCHAT) { - JabberID *jid = jabber_id_new(jm->from); - JabberChat *chat = NULL; + if (smiley_refs) { + if (jm->type == JABBER_MESSAGE_GROUPCHAT) { + JabberID *jid = jabber_id_new(jm->from); + JabberChat *chat = NULL; - if (jid) { - chat = jabber_chat_find(js, jid->node, jid->domain); - if (chat) conv = chat->conv; - } + if (jid) { + chat = jabber_chat_find(js, jid->node, jid->domain); + if (chat) conv = chat->conv; + } - jabber_id_free(jid); - } else { - conv = - purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, - who, account); - if (!conv) { - /* we need to create the conversation here */ - conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, - account, who); + jabber_id_free(jid); + } else { + conv = + purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, + who, account); + if (!conv) { + /* we need to create the conversation here */ + conv = + purple_conversation_new(PURPLE_CONV_TYPE_IM, + account, who); + } } } @@ -673,7 +678,7 @@ TRUE)) { const JabberData *data = jabber_data_find_remote_by_cid(cid); - /* if data is already known, we add write it immediatly */ + /* if data is already known, we write it immediatly */ if (data) { purple_debug_info("jabber", "data is already known\n"); diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/message.h --- a/libpurple/protocols/jabber/message.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/message.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_MESSAGE_H_ -#define _PURPLE_JABBER_MESSAGE_H_ +#ifndef PURPLE_JABBER_MESSAGE_H_ +#define PURPLE_JABBER_MESSAGE_H_ #include "buddy.h" #include "jabber.h" @@ -85,4 +85,4 @@ gboolean jabber_custom_smileys_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace); -#endif /* _PURPLE_JABBER_MESSAGE_H_ */ +#endif /* PURPLE_JABBER_MESSAGE_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/oob.c --- a/libpurple/protocols/jabber/oob.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/oob.c Mon Apr 20 00:10:51 2009 +0000 @@ -187,18 +187,18 @@ jabber_oob_xfer_recv_error(xfer, "404"); } -void jabber_oob_parse(JabberStream *js, xmlnode *packet) { +void jabber_oob_parse(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *querynode) { JabberOOBXfer *jox; PurpleXfer *xfer; char *filename; char *url; - const char *type; - xmlnode *querynode, *urlnode; + xmlnode *urlnode; - if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "set")) + if(type != JABBER_IQ_SET) return; - if(!(querynode = xmlnode_get_child(packet, "query"))) + if(!from) return; if(!(urlnode = xmlnode_get_child(querynode, "url"))) @@ -211,10 +211,9 @@ g_free(url); jox->js = js; jox->headers = g_string_new(""); - jox->iq_id = g_strdup(xmlnode_get_attrib(packet, "id")); + jox->iq_id = g_strdup(id); - xfer = purple_xfer_new(js->gc->account, PURPLE_XFER_RECEIVE, - xmlnode_get_attrib(packet, "from")); + xfer = purple_xfer_new(js->gc->account, PURPLE_XFER_RECEIVE, from); if (xfer) { xfer->data = jox; diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/oob.h --- a/libpurple/protocols/jabber/oob.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/oob.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,9 +19,12 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_OOB_H_ -#define _PURPLE_JABBER_OOB_H_ +#ifndef PURPLE_JABBER_OOB_H_ +#define PURPLE_JABBER_OOB_H_ + +#include "jabber.h" -void jabber_oob_parse(JabberStream *js, xmlnode *packet); +void jabber_oob_parse(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *querynode); -#endif /* _PURPLE_JABBER_OOB_H_ */ +#endif /* PURPLE_JABBER_OOB_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/parser.h --- a/libpurple/protocols/jabber/parser.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/parser.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_PARSER_H_ -#define _PURPLE_JABBER_PARSER_H_ +#ifndef PURPLE_JABBER_PARSER_H_ +#define PURPLE_JABBER_PARSER_H_ #include "jabber.h" @@ -28,4 +28,4 @@ void jabber_parser_free(JabberStream *js); void jabber_parser_process(JabberStream *js, const char *buf, int len); -#endif /* _PURPLE_JABBER_PARSER_H_ */ +#endif /* PURPLE_JABBER_PARSER_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/pep.c --- a/libpurple/protocols/jabber/pep.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/pep.c Mon Apr 20 00:10:51 2009 +0000 @@ -52,8 +52,11 @@ g_hash_table_replace(pep_handlers, g_strdup(xmlns), handlerfunc); } -static void do_pep_iq_request_item_callback(JabberStream *js, xmlnode *packet, gpointer data) { - const char *from = xmlnode_get_attrib(packet,"from"); +static void +do_pep_iq_request_item_callback(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ xmlnode *pubsub = xmlnode_get_child_with_namespace(packet,"pubsub","http://jabber.org/protocol/pubsub"); xmlnode *items = NULL; JabberPEPHandler *cb = data; diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/pep.h --- a/libpurple/protocols/jabber/pep.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/pep.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * */ -#ifndef _PURPLE_JABBER_PEP_H_ -#define _PURPLE_JABBER_PEP_H_ +#ifndef PURPLE_JABBER_PEP_H_ +#define PURPLE_JABBER_PEP_H_ #include "jabber.h" #include "message.h" @@ -82,4 +82,4 @@ */ void jabber_pep_publish(JabberStream *js, xmlnode *publish); -#endif /* _PURPLE_JABBER_PEP_H_ */ +#endif /* PURPLE_JABBER_PEP_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/ping.c --- a/libpurple/protocols/jabber/ping.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/ping.c Mon Apr 20 00:10:51 2009 +0000 @@ -23,50 +23,58 @@ #include "internal.h" #include "debug.h" -#include "xmlnode.h" #include "jabber.h" #include "ping.h" #include "iq.h" -void -jabber_ping_parse(JabberStream *js, xmlnode *packet) +static void jabber_keepalive_pong_cb(JabberStream *js) { - JabberIq *iq; - - purple_debug_info("jabber", "jabber_ping_parse\n"); - - iq = jabber_iq_new(js, JABBER_IQ_RESULT); - - xmlnode_set_attrib(iq->node, "to", xmlnode_get_attrib(packet, "from") ); - - jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); - - jabber_iq_send(iq); + purple_timeout_remove(js->keepalive_timeout); + js->keepalive_timeout = -1; } -static void jabber_ping_result_cb(JabberStream *js, xmlnode *packet, - gpointer data) +void +jabber_ping_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, xmlnode *ping) { - const char *type = xmlnode_get_attrib(packet, "type"); + if (type == JABBER_IQ_GET) { + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_RESULT); + + if (from) + xmlnode_set_attrib(iq->node, "to", from); + xmlnode_set_attrib(iq->node, "id", id); - purple_debug_info("jabber", "jabber_ping_result_cb\n"); - if(type && !strcmp(type, "result")) { + jabber_iq_send(iq); + } else if (type == JABBER_IQ_SET) { + /* XXX: error */ + } +} + +static void jabber_ping_result_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + if (purple_strequal(from, js->user->domain)) + /* If the pong is from the server, assume it's a result of the + * keepalive functions */ + jabber_keepalive_pong_cb(js); + + if (type == JABBER_IQ_RESULT) { purple_debug_info("jabber", "PONG!\n"); } else { purple_debug_info("jabber", "(not supported)\n"); } } -gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid) +gboolean jabber_ping_jid(JabberStream *js, const char *jid) { JabberIq *iq; xmlnode *ping; - purple_debug_info("jabber", "jabber_ping_jid\n"); - - iq = jabber_iq_new(conv->account->gc->proto_data, JABBER_IQ_GET); - xmlnode_set_attrib(iq->node, "to", jid); + iq = jabber_iq_new(js, JABBER_IQ_GET); + if (jid) + xmlnode_set_attrib(iq->node, "to", jid); ping = xmlnode_new_child(iq->node, "ping"); xmlnode_set_namespace(ping, "urn:xmpp:ping"); @@ -74,7 +82,5 @@ jabber_iq_set_callback(iq, jabber_ping_result_cb, NULL); jabber_iq_send(iq); - - return TRUE; } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/ping.h --- a/libpurple/protocols/jabber/ping.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/ping.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,17 +19,15 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef _PURPLE_JABBER_PING_H_ -#define _PURPLE_JABBER_PING_H_ +#ifndef PURPLE_JABBER_PING_H_ +#define PURPLE_JABBER_PING_H_ #include "jabber.h" -#include "conversation.h" - -void jabber_ping_parse(JabberStream *js, - xmlnode *packet); +#include "iq.h" +#include "xmlnode.h" - -gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid); +void jabber_ping_parse(JabberStream *js, const char *from, + JabberIqType, const char *id, xmlnode *child); +gboolean jabber_ping_jid(JabberStream *js, const char *jid); - -#endif /* _PURPLE_JABBER_PING_H_ */ +#endif /* PURPLE_JABBER_PING_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/presence.c Mon Apr 20 00:10:51 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 */ @@ -326,14 +330,16 @@ g_free(jap); } -static void jabber_vcard_parse_avatar(JabberStream *js, xmlnode *packet, gpointer blah) +static void +jabber_vcard_parse_avatar(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer blah) { JabberBuddy *jb = NULL; xmlnode *vcard, *photo, *binval; char *text; guchar *data; gsize size; - const char *from = xmlnode_get_attrib(packet, "from"); if(!from) return; diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/presence.h --- a/libpurple/protocols/jabber/presence.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/presence.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_PRESENCE_H_ -#define _PURPLE_JABBER_PRESENCE_H_ +#ifndef PURPLE_JABBER_PRESENCE_H_ +#define PURPLE_JABBER_PRESENCE_H_ #include "buddy.h" #include "jabber.h" @@ -35,4 +35,4 @@ void jabber_presence_fake_to_self(JabberStream *js, const PurpleStatus *status); void purple_status_to_jabber(const PurpleStatus *status, JabberBuddyState *state, char **msg, int *priority); -#endif /* _PURPLE_JABBER_PRESENCE_H_ */ +#endif /* PURPLE_JABBER_PRESENCE_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/roster.c --- a/libpurple/protocols/jabber/roster.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/roster.c Mon Apr 20 00:10:51 2009 +0000 @@ -145,10 +145,10 @@ g_slist_free(buddies); } -void jabber_roster_parse(JabberStream *js, xmlnode *packet) +void jabber_roster_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, xmlnode *query) { - xmlnode *query, *item, *group; - const char *from = xmlnode_get_attrib(packet, "from"); + xmlnode *item, *group; if(from) { char *from_norm; @@ -169,10 +169,6 @@ return; } - query = xmlnode_get_child(packet, "query"); - if(!query) - return; - js->currently_parsing_roster_push = TRUE; for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/roster.h --- a/libpurple/protocols/jabber/roster.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/roster.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,14 +19,15 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_ROSTER_H_ -#define _PURPLE_JABBER_ROSTER_H_ +#ifndef PURPLE_JABBER_ROSTER_H_ +#define PURPLE_JABBER_ROSTER_H_ #include "jabber.h" void jabber_roster_request(JabberStream *js); -void jabber_roster_parse(JabberStream *js, xmlnode *packet); +void jabber_roster_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, xmlnode *query); void jabber_roster_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); @@ -39,4 +40,4 @@ void jabber_roster_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); -#endif /* _PURPLE_JABBER_ROSTER_H_ */ +#endif /* PURPLE_JABBER_ROSTER_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/si.c --- a/libpurple/protocols/jabber/si.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/si.c Mon Apr 20 00:10:51 2009 +0000 @@ -311,20 +311,18 @@ } } -void jabber_bytestreams_parse(JabberStream *js, xmlnode *packet) +void jabber_bytestreams_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, xmlnode *query) { PurpleXfer *xfer; JabberSIXfer *jsx; - xmlnode *query, *streamhost; - const char *sid, *from, *type; + xmlnode *streamhost; + const char *sid; - if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "set")) + if(type != JABBER_IQ_SET) return; - if(!(from = xmlnode_get_attrib(packet, "from"))) - return; - - if(!(query = xmlnode_get_child(packet, "query"))) + if(!from) return; if(!(sid = xmlnode_get_attrib(query, "sid"))) @@ -340,7 +338,7 @@ if(jsx->iq_id) g_free(jsx->iq_id); - jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id")); + jsx->iq_id = g_strdup(id); for(streamhost = xmlnode_get_child(query, "streamhost"); streamhost; streamhost = xmlnode_get_next_twin(streamhost)) { @@ -685,13 +683,14 @@ } static void -jabber_si_connect_proxy_cb(JabberStream *js, xmlnode *packet, - gpointer data) +jabber_si_connect_proxy_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { PurpleXfer *xfer = data; JabberSIXfer *jsx; xmlnode *query, *streamhost_used; - const char *from, *type, *jid; + const char *jid; GList *matched; /* TODO: This need to send errors if we don't see what we're looking for */ @@ -708,37 +707,34 @@ jsx = xfer->data; - if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) { - purple_debug_info("jabber", - "jabber_si_xfer_connect_proxy_cb: type = %s\n", - type); - if (type && !strcmp(type, "error")) { - /* if IBB is available, open IBB session */ - purple_debug_info("jabber", - "jabber_si_xfer_connect_proxy_cb: got error, method: %d\n", - jsx->stream_method); - if (jsx->stream_method & STREAM_METHOD_IBB) { - purple_debug_info("jabber", "IBB is possible, try it\n"); - /* if we are the sender and haven't already opened an IBB - session, do so now (we might already have failed to open - the bytestream proxy ourselves when receiving this */ - if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND - && !jsx->ibb_session) { - jabber_si_xfer_ibb_send_init(js, xfer); - } else { - jsx->ibb_timeout_handle = purple_timeout_add_seconds(30, - jabber_si_bytestreams_ibb_timeout_cb, xfer); - } - /* if we are receiver, just wait for IBB open stanza, callback - is already set up */ + if(type != JABBER_IQ_RESULT) { + purple_debug_info("jabber", + "jabber_si_xfer_connect_proxy_cb: type = error\n"); + /* if IBB is available, open IBB session */ + purple_debug_info("jabber", + "jabber_si_xfer_connect_proxy_cb: got error, method: %d\n", + jsx->stream_method); + if (jsx->stream_method & STREAM_METHOD_IBB) { + purple_debug_info("jabber", "IBB is possible, try it\n"); + /* if we are the sender and haven't already opened an IBB + session, do so now (we might already have failed to open + the bytestream proxy ourselves when receiving this */ + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND + && !jsx->ibb_session) { + jabber_si_xfer_ibb_send_init(js, xfer); } else { - purple_xfer_cancel_remote(xfer); + jsx->ibb_timeout_handle = purple_timeout_add_seconds(30, + jabber_si_bytestreams_ibb_timeout_cb, xfer); } + /* if we are receiver, just wait for IBB open stanza, callback + is already set up */ + } else { + purple_xfer_cancel_remote(xfer); } return; } - if(!(from = xmlnode_get_attrib(packet, "from"))) + if (!from) return; if(!(query = xmlnode_get_child(packet, "query"))) @@ -1019,16 +1015,15 @@ } static gboolean -jabber_si_xfer_ibb_open_cb(JabberStream *js, xmlnode *packet) +jabber_si_xfer_ibb_open_cb(JabberStream *js, const char *who, const char *id, + xmlnode *open) { - const gchar *who = xmlnode_get_attrib(packet, "from"); - xmlnode *open = xmlnode_get_child(packet, "open"); const gchar *sid = xmlnode_get_attrib(open, "sid"); PurpleXfer *xfer = jabber_si_xfer_find(js, sid, who); if (xfer) { JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; JabberIBBSession *sess = - jabber_ibb_session_create_from_xmlnode(js, packet, xfer); + jabber_ibb_session_create_from_xmlnode(js, who, id, open, xfer); const char *filename; jabber_si_bytestreams_ibb_timeout_remove(jsx); @@ -1183,8 +1178,9 @@ } } -static void jabber_si_xfer_send_method_cb(JabberStream *js, xmlnode *packet, - gpointer data) +static void jabber_si_xfer_send_method_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) { PurpleXfer *xfer = data; xmlnode *si, *feature, *x, *field, *value; @@ -1585,17 +1581,15 @@ purple_xfer_request(xfer); } -void jabber_si_parse(JabberStream *js, xmlnode *packet) +void jabber_si_parse(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *si) { JabberSIXfer *jsx; PurpleXfer *xfer; - xmlnode *si, *file, *feature, *x, *field, *option, *value; - const char *stream_id, *filename, *filesize_c, *profile, *from; + xmlnode *file, *feature, *x, *field, *option, *value; + const char *stream_id, *filename, *filesize_c, *profile; size_t filesize = 0; - if(!(si = xmlnode_get_child(packet, "si"))) - return; - if(!(profile = xmlnode_get_attrib(si, "profile")) || strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer")) return; @@ -1618,7 +1612,7 @@ if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) return; - if(!(from = xmlnode_get_attrib(packet, "from"))) + if(!from) return; /* if they've already sent us this file transfer with the same damn id @@ -1659,7 +1653,7 @@ jsx->js = js; jsx->stream_id = g_strdup(stream_id); - jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id")); + jsx->iq_id = g_strdup(id); xfer = purple_xfer_new(js->gc->account, PURPLE_XFER_RECEIVE, from); g_return_if_fail(xfer != NULL); @@ -1683,6 +1677,8 @@ void jabber_si_init(void) { + jabber_iq_register_handler("si", "http://jabber.org/protocol/si", jabber_si_parse); + jabber_ibb_register_open_handler(jabber_si_xfer_ibb_open_cb); } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/si.h --- a/libpurple/protocols/jabber/si.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/si.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,18 +19,20 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_SI_H_ -#define _PURPLE_JABBER_SI_H_ +#ifndef PURPLE_JABBER_SI_H_ +#define PURPLE_JABBER_SI_H_ #include "ft.h" #include "jabber.h" -void jabber_bytestreams_parse(JabberStream *js, xmlnode *packet); -void jabber_si_parse(JabberStream *js, xmlnode *packet); +void jabber_bytestreams_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, xmlnode *query); +void jabber_si_parse(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *si); PurpleXfer *jabber_si_new_xfer(PurpleConnection *gc, const char *who); void jabber_si_xfer_send(PurpleConnection *gc, const char *who, const char *file); void jabber_si_init(void); void jabber_si_uninit(void); -#endif /* _PURPLE_JABBER_SI_H_ */ +#endif /* PURPLE_JABBER_SI_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/usermood.h --- a/libpurple/protocols/jabber/usermood.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/usermood.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * */ -#ifndef _PURPLE_JABBER_USERMOOD_H_ -#define _PURPLE_JABBER_USERMOOD_H_ +#ifndef PURPLE_JABBER_USERMOOD_H_ +#define PURPLE_JABBER_USERMOOD_H_ #include "jabber.h" @@ -34,4 +34,4 @@ const char *mood, /* must be one of the valid strings defined in the XEP */ const char *text /* might be NULL */); -#endif /* _PURPLE_JABBER_USERMOOD_H_ */ +#endif /* PURPLE_JABBER_USERMOOD_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/usernick.h --- a/libpurple/protocols/jabber/usernick.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/usernick.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * */ -#ifndef _PURPLE_JABBER_USERNICK_H_ -#define _PURPLE_JABBER_USERNICK_H_ +#ifndef PURPLE_JABBER_USERNICK_H_ +#define PURPLE_JABBER_USERNICK_H_ #include "jabber.h" @@ -29,4 +29,4 @@ void jabber_nick_init(void); void jabber_nick_init_action(GList **m); -#endif /* _PURPLE_JABBER_USERNICK_H_ */ +#endif /* PURPLE_JABBER_USERNICK_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/usertune.h --- a/libpurple/protocols/jabber/usertune.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/usertune.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * */ -#ifndef _PURPLE_JABBER_USERTUNE_H_ -#define _PURPLE_JABBER_USERTUNE_H_ +#ifndef PURPLE_JABBER_USERTUNE_H_ +#define PURPLE_JABBER_USERTUNE_H_ #include "jabber.h" @@ -40,4 +40,4 @@ void jabber_tune_set(PurpleConnection *gc, const PurpleJabberTuneInfo *tuneinfo); -#endif /* _PURPLE_JABBER_USERTUNE_H_ */ +#endif /* PURPLE_JABBER_USERTUNE_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/jabber/xdata.h --- a/libpurple/protocols/jabber/xdata.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/jabber/xdata.h Mon Apr 20 00:10:51 2009 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef _PURPLE_JABBER_XDATA_H_ -#define _PURPLE_JABBER_XDATA_H_ +#ifndef PURPLE_JABBER_XDATA_H_ +#define PURPLE_JABBER_XDATA_H_ #include "jabber.h" #include "xmlnode.h" @@ -35,4 +35,4 @@ void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data); void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data); -#endif /* _PURPLE_JABBER_XDATA_H_ */ +#endif /* PURPLE_JABBER_XDATA_H_ */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msn/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msn/msg.c --- a/libpurple/protocols/msn/msg.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msn/msg.c Mon Apr 20 00:10:51 2009 +0000 @@ -984,3 +984,67 @@ g_hash_table_destroy(body); } +void +msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + GHashTable *body; + const gchar *guid; + + g_return_if_fail(cmdproc != NULL); + g_return_if_fail(msg != NULL); + + body = msn_message_get_hashtable_from_body(msg); + + if (body == NULL) { + purple_debug_warning("msn", + "Unable to parse invite msg body.\n"); + return; + } + + guid = g_hash_table_lookup(body, "Application-GUID"); + + if (guid == NULL) { + const gchar *cmd = g_hash_table_lookup( + body, "Invitation-Command"); + + if (cmd && !strcmp(cmd, "CANCEL")) { + const gchar *code = g_hash_table_lookup( + body, "Cancel-Code"); + purple_debug_info("msn", + "MSMSGS invitation cancelled: %s.\n", + code ? code : "no reason given"); + } else + purple_debug_warning("msn", "Invite msg missing " + "Application-GUID.\n"); + } else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) { + purple_debug_info("msn", "Computer call\n"); + + if (cmdproc->session) { + PurpleConversation *conv = NULL; + gchar *from = msg->remote_user; + gchar *buf = NULL; + + if (from) + conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_IM, from, + cmdproc->session->account); + if (conv) + buf = g_strdup_printf( + _("%s sent you a voice chat " + "invite, which is not yet " + "supported."), from); + if (buf) { + purple_conversation_write(conv, NULL, buf, + PURPLE_MESSAGE_SYSTEM | + PURPLE_MESSAGE_NOTIFY, + time(NULL)); + g_free(buf); + } + } + } else + purple_debug_warning("msn", + "Unhandled invite msg with GUID %s.\n", guid); + + g_hash_table_destroy(body); +} + diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msn/msn.c Mon Apr 20 00:10:51 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 = diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msn/msn.h --- a/libpurple/protocols/msn/msn.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msn/msn.h Mon Apr 20 00:10:51 2009 +0000 @@ -76,6 +76,8 @@ #define BUDDY_ALIAS_MAXLEN 387 +#define MSN_CAM_GUID "4BD96FC0-AB17-4425-A14A-439185962DC8" +#define MSN_CAM_REQUEST_GUID "1C9AA97E-9C05-4583-A3BD-908A196F1E92" #define MSN_FT_GUID "5D3E02AB-6190-11D3-BBBB-00C04F795683" #define MSN_OBJ_GUID "A4268EEC-FEC5-49E5-95C3-F126696BDBF6" diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msn/session.c --- a/libpurple/protocols/msn/session.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msn/session.c Mon Apr 20 00:10:51 2009 +0000 @@ -303,7 +303,7 @@ for (l = remote_user->group_ids; l != NULL; l = l->next) { const char *name = msn_userlist_find_group_name(remote_user->userlist, l->data); - if (name && !g_strcasecmp(group_name, name)) + if (name && !g_ascii_strcasecmp(group_name, name)) { found = TRUE; break; diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msn/slp.c --- a/libpurple/protocols/msn/slp.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msn/slp.c Mon Apr 20 00:10:51 2009 +0000 @@ -377,6 +377,50 @@ purple_xfer_request(xfer); } + } else if (!strcmp(euf_guid, MSN_CAM_REQUEST_GUID)) { + purple_debug_info("msn", "Cam request.\n"); + if (slpcall && slpcall->slplink && + slpcall->slplink->session) { + PurpleConversation *conv; + gchar *from = slpcall->slplink->remote_user; + conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_IM, from, + slpcall->slplink->session->account); + if (conv) { + char *buf; + buf = g_strdup_printf( + _("%s requests to view your " + "webcam, but this request is " + "not yet supported."), from); + purple_conversation_write(conv, NULL, buf, + PURPLE_MESSAGE_SYSTEM | + PURPLE_MESSAGE_NOTIFY, + time(NULL)); + g_free(buf); + } + } + } else if (!strcmp(euf_guid, MSN_CAM_GUID)) { + purple_debug_info("msn", "Cam invite.\n"); + if (slpcall && slpcall->slplink && + slpcall->slplink->session) { + PurpleConversation *conv; + gchar *from = slpcall->slplink->remote_user; + conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_IM, from, + slpcall->slplink->session->account); + if (conv) { + char *buf; + buf = g_strdup_printf( + _("%s has sent you a webcam " + "invite, which is not yet " + "supported."), from); + purple_conversation_write(conv, NULL, buf, + PURPLE_MESSAGE_SYSTEM | + PURPLE_MESSAGE_NOTIFY, + time(NULL)); + g_free(buf); + } + } } else purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid); } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msn/slplink.c --- a/libpurple/protocols/msn/slplink.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msn/slplink.c Mon Apr 20 00:10:51 2009 +0000 @@ -46,7 +46,7 @@ pload = msn_message_gen_payload(msg, &pload_size); if (!purple_util_write_data_to_file_absolute(tmp, pload, pload_size)) { - purple_debug_error("msn", "could not save debug file"); + purple_debug_error("msn", "could not save debug file\n"); } g_free(tmp); } @@ -682,7 +682,9 @@ size = st.st_size; if(!file_name) { - u8 = purple_utf8_try_convert(g_basename(file_path)); + base = g_path_get_basename(file_path); + u8 = purple_utf8_try_convert(base); + g_free(base); file_name = u8; } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msn/switchboard.c --- a/libpurple/protocols/msn/switchboard.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msn/switchboard.c Mon Apr 20 00:10:51 2009 +0000 @@ -1237,10 +1237,8 @@ msn_emoticon_msg); msn_table_add_msg_type(cbs_table, "text/x-msnmsgr-datacast", msn_datacast_msg); -#if 0 - msn_table_add_msg_type(cbs_table, "text/x-msmmsginvite", + msn_table_add_msg_type(cbs_table, "text/x-msmsgsinvite", msn_invite_msg); -#endif } void diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msn/userlist.c --- a/libpurple/protocols/msn/userlist.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msn/userlist.c Mon Apr 20 00:10:51 2009 +0000 @@ -448,7 +448,7 @@ g_return_val_if_fail(user->passport != NULL, NULL); - if (!g_strcasecmp(passport, user->passport)){ + if (!g_ascii_strcasecmp(passport, user->passport)){ return user; } } @@ -470,7 +470,7 @@ continue; } - if ( !g_strcasecmp(uid, user->uid) ) { + if ( !g_ascii_strcasecmp(uid, user->uid) ) { return user; } } @@ -492,7 +492,7 @@ continue; } - if (!g_strcasecmp(number, user->phone.mobile)) { + if (!g_ascii_strcasecmp(number, user->phone.mobile)) { return user; } } @@ -524,7 +524,7 @@ { MsnGroup *group = l->data; - if (!g_strcasecmp(group->id,id)) + if (!g_ascii_strcasecmp(group->id,id)) return group; } @@ -543,7 +543,7 @@ { MsnGroup *group = l->data; - if ((group->name != NULL) && !g_strcasecmp(name, group->name)) + if ((group->name != NULL) && !g_ascii_strcasecmp(name, group->name)) return group; } @@ -784,7 +784,7 @@ { user = (MsnUser *)l->data; - if (!g_strcasecmp(who, user->passport)) { + if (!g_ascii_strcasecmp(who, user->passport)) { userlist->pending = g_list_delete_link(userlist->pending, l); break; } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msnp9/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msnp9/httpconn.c --- a/libpurple/protocols/msnp9/httpconn.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msnp9/httpconn.c Mon Apr 20 00:10:51 2009 +0000 @@ -703,7 +703,7 @@ httpconn->inpa = purple_input_add(httpconn->fd, PURPLE_INPUT_READ, read_cb, data); - httpconn->timer = purple_timeout_add(2000, msn_httpconn_poll, httpconn); + httpconn->timer = purple_timeout_add_seconds(3, msn_httpconn_poll, httpconn); msn_httpconn_process_queue(httpconn); } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msnp9/msn.c --- a/libpurple/protocols/msnp9/msn.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msnp9/msn.c Mon Apr 20 00:10:51 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 = diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msnp9/slp.c --- a/libpurple/protocols/msnp9/slp.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msnp9/slp.c Mon Apr 20 00:10:51 2009 +0000 @@ -33,8 +33,8 @@ #include "smiley.h" -/* ms to delay between sending buddy icon requests to the server. */ -#define BUDDY_ICON_DELAY 20000 +/* Seconds to delay between sending buddy icon requests to the server. */ +#define BUDDY_ICON_DELAY 20 static void send_ok(MsnSlpCall *slpcall, const char *branch, const char *type, const char *content); @@ -1058,8 +1058,8 @@ purple_timeout_remove(userlist->buddy_icon_request_timer); } - /* Wait BUDDY_ICON_DELAY ms before freeing our window slot and requesting the next icon. */ - userlist->buddy_icon_request_timer = purple_timeout_add(BUDDY_ICON_DELAY, + /* Wait BUDDY_ICON_DELAY_S seconds before freeing our window slot and requesting the next icon. */ + userlist->buddy_icon_request_timer = purple_timeout_add_seconds(BUDDY_ICON_DELAY, msn_release_buddy_icon_request_timeout, userlist); } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msnp9/slpcall.c --- a/libpurple/protocols/msnp9/slpcall.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msnp9/slpcall.c Mon Apr 20 00:10:51 2009 +0000 @@ -68,7 +68,7 @@ msn_slplink_add_slpcall(slplink, slpcall); - slpcall->timer = purple_timeout_add(MSN_SLPCALL_TIMEOUT, msn_slp_call_timeout, slpcall); + slpcall->timer = purple_timeout_add_seconds(MSN_SLPCALL_TIMEOUT, msn_slp_call_timeout, slpcall); return slpcall; } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msnp9/slpcall.h --- a/libpurple/protocols/msnp9/slpcall.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msnp9/slpcall.h Mon Apr 20 00:10:51 2009 +0000 @@ -33,7 +33,7 @@ #include "slpsession.h" /* The official client seems to timeout slp calls after 5 minutes */ -#define MSN_SLPCALL_TIMEOUT 300000 +#define MSN_SLPCALL_TIMEOUT 300 typedef enum { diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/msnp9/transaction.c --- a/libpurple/protocols/msnp9/transaction.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/msnp9/transaction.c Mon Apr 20 00:10:51 2009 +0000 @@ -211,7 +211,7 @@ purple_timeout_remove(trans->timer); } trans->timeout_cb = cb; - trans->timer = purple_timeout_add(60000, transaction_timeout, trans); + trans->timer = purple_timeout_add_seconds(60, transaction_timeout, trans); } void diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/myspace/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/myspace/myspace.c --- a/libpurple/protocols/myspace/myspace.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/myspace/myspace.c Mon Apr 20 00:10:51 2009 +0000 @@ -1245,7 +1245,7 @@ /* Disable due to problems with timeouts. TODO: fix. */ #ifdef MSIM_USE_KEEPALIVE - purple_timeout_add(MSIM_KEEPALIVE_INTERVAL_CHECK, + purple_timeout_add_seconds(MSIM_KEEPALIVE_INTERVAL_CHECK, (GSourceFunc)msim_check_alive, session); #endif @@ -3088,9 +3088,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 78ef23551355 -r 872d30754311 libpurple/protocols/myspace/myspace.h --- a/libpurple/protocols/myspace/myspace.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/myspace/myspace.h Mon Apr 20 00:10:51 2009 +0000 @@ -114,8 +114,8 @@ #define MSIM_KEEPALIVE_INTERVAL (3 * 60) /*#define MSIM_USE_KEEPALIVE*/ -/* Time to check if alive (milliseconds) */ -#define MSIM_KEEPALIVE_INTERVAL_CHECK (30 * 1000) +/* Time to check if alive (seconds) */ +#define MSIM_KEEPALIVE_INTERVAL_CHECK 30 /* Time to check for new mail (milliseconds) */ #define MSIM_MAIL_INTERVAL_CHECK (60 * 1000) diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/novell/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/novell/novell.c --- a/libpurple/protocols/novell/novell.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/novell/novell.c Mon Apr 20 00:10:51 2009 +0000 @@ -3524,13 +3524,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 = { diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/null/nullprpl.c --- a/libpurple/protocols/null/nullprpl.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/null/nullprpl.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/oscar/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/oscar/libaim.c --- a/libpurple/protocols/oscar/libaim.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/oscar/libaim.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/oscar/libicq.c --- a/libpurple/protocols/oscar/libicq.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/oscar/libicq.c Mon Apr 20 00:10:51 2009 +0000 @@ -107,6 +107,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 78ef23551355 -r 872d30754311 libpurple/protocols/oscar/oscar.c --- a/libpurple/protocols/oscar/oscar.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/oscar/oscar.c Mon Apr 20 00:10:51 2009 +0000 @@ -1268,7 +1268,7 @@ aim_ssi_reqdata(od); if (od->getblisttimer > 0) purple_timeout_remove(od->getblisttimer); - od->getblisttimer = purple_timeout_add(30000, purple_ssi_rerequestdata, od); + od->getblisttimer = purple_timeout_add_seconds(30, purple_ssi_rerequestdata, od); aim_locate_reqrights(od); aim_buddylist_reqrights(od, conn); @@ -5047,7 +5047,7 @@ _("The AIM servers were temporarily unable to send " "your buddy list. Your buddy list is not lost, and " "will probably become available in a few minutes.")); - od->getblisttimer = purple_timeout_add(30000, purple_ssi_rerequestdata, od); + od->getblisttimer = purple_timeout_add_seconds(30, purple_ssi_rerequestdata, od); return 1; } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/oscar/peer.c --- a/libpurple/protocols/oscar/peer.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/oscar/peer.c Mon Apr 20 00:10:51 2009 +0000 @@ -812,7 +812,7 @@ (conn->client_connect_data != NULL)) { /* Connecting... */ - conn->connect_timeout_timer = purple_timeout_add(5000, + conn->connect_timeout_timer = purple_timeout_add_seconds(5, peer_connection_tooktoolong, conn); return; } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/qq/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/qq/qq.c --- a/libpurple/protocols/qq/qq.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/qq/qq.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/sametime/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/sametime/sametime.c --- a/libpurple/protocols/sametime/sametime.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/sametime/sametime.c Mon Apr 20 00:10:51 2009 +0000 @@ -28,8 +28,6 @@ /* glib includes */ #include -#include -#include /* purple includes */ #include "internal.h" @@ -810,7 +808,7 @@ static void blist_schedule(struct mwPurplePluginData *pd) { if(pd->save_event) return; - pd->save_event = purple_timeout_add(BLIST_SAVE_SECONDS * 1000, + pd->save_event = purple_timeout_add_seconds(BLIST_SAVE_SECONDS, blist_save_cb, pd); } @@ -5209,7 +5207,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 78ef23551355 -r 872d30754311 libpurple/protocols/silc/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/silc/silc.c --- a/libpurple/protocols/silc/silc.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/silc/silc.c Mon Apr 20 00:10:51 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 = diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/silc10/silc.c --- a/libpurple/protocols/silc10/silc.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/silc10/silc.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/protocols/simple/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/simple/simple.c --- a/libpurple/protocols/simple/simple.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/simple/simple.c Mon Apr 20 00:10:51 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 */ }; diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/yahoo/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/yahoo/yahoo.c --- a/libpurple/protocols/yahoo/yahoo.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Mon Apr 20 00:10:51 2009 +0000 @@ -2829,6 +2829,7 @@ p2p_data->host_username = g_strdup(who); p2p_data->val_13 = val_13; p2p_data->connection_type = YAHOO_P2P_WE_ARE_SERVER; + p2p_data->source = -1; purple_network_listen(YAHOO_PAGER_PORT_P2P, SOCK_STREAM, yahoo_p2p_server_listen_cb, p2p_data); @@ -2932,10 +2933,9 @@ if (base64) { guint32 ip; - char *tmp2; YahooFriend *f; char *host_ip; - struct yahoo_p2p_data *p2p_data = g_new0(struct yahoo_p2p_data, 1); + struct yahoo_p2p_data *p2p_data; decoded = purple_base64_decode(base64, &len); if (len) { @@ -2944,9 +2944,7 @@ g_free(tmp); } - tmp2 = g_strndup((const gchar *)decoded, len); /* so its \0 terminated...*/ - ip = strtol(tmp2, NULL, 10); - g_free(tmp2); + ip = strtol((gchar *)decoded, NULL, 10); g_free(decoded); host_ip = g_strdup_printf("%u.%u.%u.%u", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff); @@ -2964,17 +2962,21 @@ val_11 = f->session_id; } - p2p_data->host_username = g_strdup(who); + p2p_data = g_new0(struct yahoo_p2p_data, 1); + p2p_data->host_username = g_strdup(who); p2p_data->val_13 = val_13; p2p_data->session_id = val_11; p2p_data->host_ip = host_ip; p2p_data->gc = gc; p2p_data->connection_type = YAHOO_P2P_WE_ARE_CLIENT; + p2p_data->source = -1; /* connect to host */ if((purple_proxy_connect(NULL, account, host_ip, YAHOO_PAGER_PORT_P2P, yahoo_p2p_init_cb, p2p_data))==NULL) { - yahoo_p2p_disconnect_destroy_data(p2p_data); purple_debug_info("yahoo","p2p: Connection to %s failed\n", host_ip); + g_free(p2p_data->host_ip); + g_free(p2p_data->host_username); + g_free(p2p_data); } } } @@ -4416,7 +4418,7 @@ "Cookie: T=%s; path=/; domain=.yahoo.com; Y=%s; path=/; domain=.yahoo.com;\r\n" "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n" "Host: validate.msg.yahoo.com\r\n" - "Content-Length: %d\r\n" + "Content-Length: %" G_GSIZE_FORMAT "\r\n" "Cache-Control: no-cache\r\n\r\n%s", YAHOO_CLIENT_VERSION, yd->cookie_t, yd->cookie_y, strlen(validate_request_str), validate_request_str); @@ -5412,6 +5414,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 78ef23551355 -r 872d30754311 libpurple/protocols/yahoo/yahoo_filexfer.c --- a/libpurple/protocols/yahoo/yahoo_filexfer.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.c Mon Apr 20 00:10:51 2009 +0000 @@ -1029,12 +1029,7 @@ xd->port = YAHOO_XFER_RELAY_PORT; url = g_strdup_printf("%ld.%ld.%ld.%ld", d, c, b, a); - if (!purple_url_parse(url, &(xd->host), &(xd->port), &(xd->path), NULL, NULL)) { - purple_xfer_cancel_remote(xfer); - g_free(url); - return; - } - g_free(url); + /* Free the address... */ g_free(hosts->data); hosts = g_slist_remove(hosts, hosts->data); @@ -1048,6 +1043,13 @@ hosts = g_slist_remove(hosts, hosts->data); } + if (!purple_url_parse(url, &(xd->host), &(xd->port), &(xd->path), NULL, NULL)) { + purple_xfer_cancel_remote(xfer); + g_free(url); + return; + } + g_free(url); + pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_INFO_15, YAHOO_STATUS_AVAILABLE, yd->session_id); filename = g_path_get_basename(purple_xfer_get_local_filename(xfer)); @@ -1385,7 +1387,13 @@ strcpy(time_str + strlen(time_str) - 1, "\0"); if (xd->txbuflen == 0) { - xd->txbuf = g_strdup_printf("HTTP/1.0 200 OK\r\nDate: %s GMT\r\nServer: Y!/1.0\r\nMIME-version: 1.0\r\nLast-modified: %s GMT\r\nContent-length: %d\r\n\r\n", time_str, time_str, xfer->size); + xd->txbuf = g_strdup_printf("HTTP/1.0 200 OK\r\n" + "Date: %s GMT\r\n" + "Server: Y!/1.0\r\n" + "MIME-version: 1.0\r\n" + "Last-modified: %s GMT\r\n" + "Content-length: %" G_GSIZE_FORMAT "\r\n\r\n", + time_str, time_str, xfer->size); xd->txbuflen = strlen(xd->txbuf); xd->txbuf_written = 0; } diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/zephyr/Makefile.am diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/zephyr/ZVariables.c --- a/libpurple/protocols/zephyr/ZVariables.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/zephyr/ZVariables.c Mon Apr 20 00:10:51 2009 +0000 @@ -186,7 +186,7 @@ #define max(a,b) ((a > b) ? (a) : (b)) #endif - if (g_strncasecmp(bfr, var, max(strlen(var), cp - bfr))) + if (g_ascii_strncasecmp(bfr, var, max(strlen(var), cp - bfr))) return(0); /* var is not the var in bfr ==> no match */ diff -r 78ef23551355 -r 872d30754311 libpurple/protocols/zephyr/zephyr.c --- a/libpurple/protocols/zephyr/zephyr.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/protocols/zephyr/zephyr.c Mon Apr 20 00:10:51 2009 +0000 @@ -960,7 +960,7 @@ tc = tree_child(ptree,0)->contents; /* g_strcasecmp() is deprecated. What is the encoding here??? */ - if (ptree->num_children > 0 && tc && !g_strcasecmp(tc, key)) { + if (ptree->num_children > 0 && tc && !g_ascii_strcasecmp(tc, key)) { return ptree; } else { parse_tree *result = &null_parse_tree; @@ -1880,7 +1880,7 @@ } else if (use_tzc(zephyr)) { zephyr->nottimer = purple_timeout_add(100, check_notify_tzc, gc); } - zephyr->loctimer = purple_timeout_add(20000, check_loc, gc); + zephyr->loctimer = purple_timeout_add_seconds(20, check_loc, gc); } @@ -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 78ef23551355 -r 872d30754311 libpurple/prpl.c --- a/libpurple/prpl.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/prpl.c Mon Apr 20 00:10:51 2009 +0000 @@ -496,6 +496,54 @@ got_attention(gc, id, who, type_code); } +gboolean +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(account, who, type); + } else +#endif + return FALSE; +} + +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(account, who); + } +#endif + return PURPLE_MEDIA_CAPS_NONE; +} + /************************************************************************** * Protocol Plugin Subsystem API **************************************************************************/ diff -r 78ef23551355 -r 872d30754311 libpurple/prpl.h --- a/libpurple/prpl.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/prpl.h Mon Apr 20 00:10:51 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" @@ -459,6 +460,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 account The account 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 TRUE if the call succeeded else FALSE. (Doesn't imply the media session or stream will be successfully created) + */ + gboolean (*initiate_media)(PurpleAccount *account, const char *who, + PurpleMediaSessionType type); + + /** + * Checks to see if the given contact supports the given type of media session. + * + * @param account The account 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)(PurpleAccount *account, + const char *who); }; #define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \ @@ -755,6 +777,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 TRUE if the call succeeded else FALSE. (Doesn't imply the media session or stream will be successfully created) + */ +gboolean purple_prpl_initiate_media(PurpleAccount *account, + const char *who, + PurpleMediaSessionType type); + /*@}*/ /**************************************************************************/ diff -r 78ef23551355 -r 872d30754311 libpurple/server.c --- a/libpurple/server.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/server.c Mon Apr 20 00:10:51 2009 +0000 @@ -587,6 +587,11 @@ account = purple_connection_get_account(gc); + /* + * XXX: Should we be setting this here, or relying on prpls to set it? + */ + flags |= PURPLE_MESSAGE_RECV; + if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->set_permit_deny == NULL) { /* protocol does not support privacy, handle it ourselves */ if (!purple_privacy_check(account, who)) { @@ -630,11 +635,6 @@ if (conv == NULL) conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, gc->account); - /* - * XXX: Should we be setting this here, or relying on prpls to set it? - */ - flags |= PURPLE_MESSAGE_RECV; - if (conv == NULL) conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name); @@ -939,6 +939,14 @@ if (!conv) return; + /* Did I send the message? */ + if (purple_strequal(purple_conv_chat_get_nick(chat), who)) { + flags |= PURPLE_MESSAGE_SEND; + flags &= ~PURPLE_MESSAGE_RECV; /* Just in case some prpl sets it! */ + } else { + flags |= PURPLE_MESSAGE_RECV; + } + /* * Make copies of the message and the sender in case plugins want * to free these strings and replace them with a modifed version. diff -r 78ef23551355 -r 872d30754311 libpurple/smiley.c diff -r 78ef23551355 -r 872d30754311 libpurple/stun.c --- a/libpurple/stun.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/stun.c Mon Apr 20 00:10:51 2009 +0000 @@ -341,6 +341,12 @@ } if (!purple_network_listen_range(12108, 12208, SOCK_DGRAM, hbn_listen_cb, hosts)) { + while(hosts) { + hosts = g_slist_remove(hosts, hosts->data); + g_free(hosts->data); + hosts = g_slist_remove(hosts, hosts->data); + } + nattype.status = PURPLE_STUN_STATUS_UNKNOWN; nattype.lookup_time = time(NULL); do_callbacks(); diff -r 78ef23551355 -r 872d30754311 libpurple/tests/test_cipher.c diff -r 78ef23551355 -r 872d30754311 libpurple/theme-manager.c --- a/libpurple/theme-manager.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/theme-manager.c Mon Apr 20 00:10:51 2009 +0000 @@ -130,6 +130,7 @@ theme_dir = g_build_filename(purple_dir, type, NULL); theme = purple_theme_loader_build(loader, theme_dir); + g_free(theme_dir); if (PURPLE_IS_THEME(theme)) purple_theme_manager_add_theme(theme); diff -r 78ef23551355 -r 872d30754311 libpurple/util.h --- a/libpurple/util.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/util.h Mon Apr 20 00:10:51 2009 +0000 @@ -31,23 +31,26 @@ #include +typedef struct _PurpleUtilFetchUrlData PurpleUtilFetchUrlData; +typedef struct _PurpleMenuAction PurpleMenuAction; +typedef struct _PurpleKeyValuePair PurpleKeyValuePair; + #include "account.h" #include "xmlnode.h" #include "notify.h" + #ifdef __cplusplus extern "C" { #endif -typedef struct _PurpleUtilFetchUrlData PurpleUtilFetchUrlData; - -typedef struct _PurpleMenuAction +struct _PurpleMenuAction { char *label; PurpleCallback callback; gpointer data; GList *children; -} PurpleMenuAction; +}; typedef char *(*PurpleInfoFieldFormatCallback)(const char *field, size_t len); @@ -57,12 +60,12 @@ * This is used by, among other things, purple_gtk_combo* functions to pass in a * list of key-value pairs so it can display a user-friendly value. */ -typedef struct _PurpleKeyValuePair +struct _PurpleKeyValuePair { gchar *key; void *value; -} PurpleKeyValuePair; +}; /** * Creates a new PurpleMenuAction. diff -r 78ef23551355 -r 872d30754311 libpurple/win32/win32dep.c --- a/libpurple/win32/win32dep.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/win32/win32dep.c Mon Apr 20 00:10:51 2009 +0000 @@ -467,7 +467,14 @@ WSACleanup(); g_free(app_data_dir); + g_free(install_dir); + g_free(lib_dir); + g_free(locale_dir); + app_data_dir = NULL; + install_dir = NULL; + lib_dir = NULL; + locale_dir = NULL; libpurpledll_hInstance = NULL; } diff -r 78ef23551355 -r 872d30754311 libpurple/xmlnode.c --- a/libpurple/xmlnode.c Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/xmlnode.c Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 libpurple/xmlnode.h --- a/libpurple/xmlnode.h Mon Apr 20 00:05:54 2009 +0000 +++ b/libpurple/xmlnode.h Mon Apr 20 00:10:51 2009 +0000 @@ -268,6 +268,17 @@ const char *xmlnode_get_prefix(const xmlnode *node); /** + * Gets the parent node. + * + * @param child The child node. + * + * @return The parent or NULL. + * + * @since 2.6.0 + */ +xmlnode *xmlnode_get_parent(const xmlnode *child); + +/** * Returns the node in a string of xml. * * @param node The starting node to output. diff -r 78ef23551355 -r 872d30754311 pidgin/Makefile.am --- a/pidgin/Makefile.am Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/Makefile.am Mon Apr 20 00:10:51 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 \ diff -r 78ef23551355 -r 872d30754311 pidgin/Makefile.mingw --- a/pidgin/Makefile.mingw Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/Makefile.mingw Mon Apr 20 00:10:51 2009 +0000 @@ -77,6 +77,7 @@ gtkimhtmltoolbar.c \ gtklog.c \ gtkmain.c \ + gtkmedia.c \ gtkmenutray.c \ gtknotify.c \ gtkplugin.c \ diff -r 78ef23551355 -r 872d30754311 pidgin/eggtrayicon.h --- a/pidgin/eggtrayicon.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/eggtrayicon.h Mon Apr 20 00:10:51 2009 +0000 @@ -21,8 +21,7 @@ #ifndef __EGG_TRAY_ICON_H__ #define __EGG_TRAY_ICON_H__ -#include -#include +#include #include G_BEGIN_DECLS diff -r 78ef23551355 -r 872d30754311 pidgin/gtkaccount.c --- a/pidgin/gtkaccount.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkaccount.c Mon Apr 20 00:10:51 2009 +0000 @@ -413,7 +413,11 @@ if (dialog->protocol_menu != NULL) { +#if GTK_CHECK_VERSION(2,12,0) + g_object_ref(G_OBJECT(dialog->protocol_menu)); +#else gtk_widget_ref(dialog->protocol_menu); +#endif hbox = g_object_get_data(G_OBJECT(dialog->protocol_menu), "container"); gtk_container_remove(GTK_CONTAINER(hbox), dialog->protocol_menu); } @@ -440,13 +444,21 @@ { dialog->protocol_menu = pidgin_protocol_option_menu_new( dialog->protocol_id, G_CALLBACK(set_account_protocol_cb), dialog); +#if GTK_CHECK_VERSION(2,12,0) + g_object_ref(G_OBJECT(dialog->protocol_menu)); +#else gtk_widget_ref(dialog->protocol_menu); +#endif } hbox = add_pref_box(dialog, vbox, _("Pro_tocol:"), dialog->protocol_menu); g_object_set_data(G_OBJECT(dialog->protocol_menu), "container", hbox); +#if GTK_CHECK_VERSION(2,12,0) + g_object_unref(G_OBJECT(dialog->protocol_menu)); +#else gtk_widget_unref(dialog->protocol_menu); +#endif /* Username */ dialog->username_entry = gtk_entry_new(); diff -r 78ef23551355 -r 872d30754311 pidgin/gtkblist-theme-loader.c --- a/pidgin/gtkblist-theme-loader.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkblist-theme-loader.c Mon Apr 20 00:10:51 2009 +0000 @@ -21,6 +21,7 @@ */ #include +#include #include "xmlnode.h" @@ -44,10 +45,10 @@ gchar *filename_full, *data; const gchar *temp; gboolean success = TRUE; - GdkColor *bgcolor, *expanded_bgcolor, *collapsed_bgcolor, *contact_color; + GdkColor bgcolor, expanded_bgcolor, collapsed_bgcolor, contact_color; GdkColor color; - FontColorPair *expanded, *collapsed, *contact, *online, *away, *offline, *idle, *message, *message_nick_said, *status; - PidginBlistLayout *layout; + FontColorPair expanded, collapsed, contact, online, away, offline, idle, message, message_nick_said, status; + PidginBlistLayout layout; PidginBlistTheme *theme; /* Find the theme file */ @@ -63,145 +64,117 @@ sub_node = xmlnode_get_child(root_node, "description"); data = xmlnode_get_data(sub_node); - /* init all structs and colors */ - bgcolor = g_new0(GdkColor, 1); - expanded_bgcolor = g_new0(GdkColor, 1); - collapsed_bgcolor = g_new0(GdkColor, 1); - - layout = g_new0(PidginBlistLayout, 1); - - contact_color = g_new0(GdkColor, 1); - - expanded = g_new0(FontColorPair, 1); - collapsed = g_new0(FontColorPair, 1); - contact = g_new0(FontColorPair, 1); - online = g_new0(FontColorPair, 1); - away = g_new0(FontColorPair, 1); - offline = g_new0(FontColorPair, 1); - idle = g_new0(FontColorPair, 1); - message = g_new0(FontColorPair, 1); - message_nick_said = g_new0(FontColorPair, 1); - status = g_new0(FontColorPair, 1); - /* */ if ((success = (sub_node = xmlnode_get_child(root_node, "blist")) != NULL)) { - if ((temp = xmlnode_get_attrib(sub_node, "color")) != NULL && gdk_color_parse(temp, bgcolor)) - gdk_colormap_alloc_color(gdk_colormap_get_system(), bgcolor, FALSE, TRUE); - else { - g_free(bgcolor); - bgcolor = NULL; - } + if ((temp = xmlnode_get_attrib(sub_node, "color")) != NULL && gdk_color_parse(temp, &bgcolor)) + gdk_colormap_alloc_color(gdk_colormap_get_system(), &bgcolor, FALSE, TRUE); + else + memset(&bgcolor, 0, sizeof(GdkColor)); } /* */ if ((success = (success && (sub_node = xmlnode_get_child(root_node, "groups")) != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "expanded")) != NULL))) { - expanded->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + expanded.font = xmlnode_get_attrib(sub_sub_node, "font"); if ((temp = xmlnode_get_attrib(sub_sub_node, "text_color")) != NULL && gdk_color_parse(temp, &color)) - expanded->color = g_strdup(temp); - else expanded->color = g_strdup(DEFAULT_TEXT_COLOR); + expanded.color = temp; + else expanded.color = DEFAULT_TEXT_COLOR; - if ((temp = xmlnode_get_attrib(sub_sub_node, "background")) != NULL && gdk_color_parse(temp, expanded_bgcolor)) - gdk_colormap_alloc_color(gdk_colormap_get_system(), expanded_bgcolor, FALSE, TRUE); - else { - g_free(expanded_bgcolor); - expanded_bgcolor = NULL; - } + if ((temp = xmlnode_get_attrib(sub_sub_node, "background")) != NULL && gdk_color_parse(temp, &expanded_bgcolor)) + gdk_colormap_alloc_color(gdk_colormap_get_system(), &expanded_bgcolor, FALSE, TRUE); + else + memset(&expanded_bgcolor, 0, sizeof(GdkColor)); } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "collapsed")) != NULL))) { - collapsed->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + collapsed.font = xmlnode_get_attrib(sub_sub_node, "font"); if((temp = xmlnode_get_attrib(sub_sub_node, "text_color")) != NULL && gdk_color_parse(temp, &color)) - collapsed->color = g_strdup(temp); - else collapsed->color = g_strdup(DEFAULT_TEXT_COLOR); + collapsed.color = temp; + else collapsed.color = DEFAULT_TEXT_COLOR; - if ((temp = xmlnode_get_attrib(sub_sub_node, "background")) != NULL && gdk_color_parse(temp, collapsed_bgcolor)) - gdk_colormap_alloc_color(gdk_colormap_get_system(), collapsed_bgcolor, FALSE, TRUE); - else { - g_free(collapsed_bgcolor); - collapsed_bgcolor = NULL; - } + if ((temp = xmlnode_get_attrib(sub_sub_node, "background")) != NULL && gdk_color_parse(temp, &collapsed_bgcolor)) + gdk_colormap_alloc_color(gdk_colormap_get_system(), &collapsed_bgcolor, FALSE, TRUE); + else + memset(&collapsed_bgcolor, 0, sizeof(GdkColor)); } /* */ if ((success = (success && (sub_node = xmlnode_get_child(root_node, "buddys")) != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "placement")) != NULL))) { - layout->status_icon = (temp = xmlnode_get_attrib(sub_sub_node, "status_icon")) != NULL ? atoi(temp) : 0; - layout->text = (temp = xmlnode_get_attrib(sub_sub_node, "name")) != NULL ? atoi(temp) : 1; - layout->emblem = (temp = xmlnode_get_attrib(sub_sub_node, "emblem")) != NULL ? atoi(temp) : 2; - layout->protocol_icon = (temp = xmlnode_get_attrib(sub_sub_node, "protocol_icon")) != NULL ? atoi(temp) : 3; - layout->buddy_icon = (temp = xmlnode_get_attrib(sub_sub_node, "buddy_icon")) != NULL ? atoi(temp) : 4; - layout->show_status = (temp = xmlnode_get_attrib(sub_sub_node, "status_icon")) != NULL ? atoi(temp) != 0 : 1; + layout.status_icon = (temp = xmlnode_get_attrib(sub_sub_node, "status_icon")) != NULL ? atoi(temp) : 0; + layout.text = (temp = xmlnode_get_attrib(sub_sub_node, "name")) != NULL ? atoi(temp) : 1; + layout.emblem = (temp = xmlnode_get_attrib(sub_sub_node, "emblem")) != NULL ? atoi(temp) : 2; + layout.protocol_icon = (temp = xmlnode_get_attrib(sub_sub_node, "protocol_icon")) != NULL ? atoi(temp) : 3; + layout.buddy_icon = (temp = xmlnode_get_attrib(sub_sub_node, "buddy_icon")) != NULL ? atoi(temp) : 4; + layout.show_status = (temp = xmlnode_get_attrib(sub_sub_node, "status_icon")) != NULL ? atoi(temp) != 0 : 1; } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "background")) != NULL))) { - if(gdk_color_parse(xmlnode_get_attrib(sub_sub_node, "color"), contact_color)) - gdk_colormap_alloc_color(gdk_colormap_get_system(), contact_color, FALSE, TRUE); - else { - g_free(contact_color); - contact_color = NULL; - } + if(gdk_color_parse(xmlnode_get_attrib(sub_sub_node, "color"), &contact_color)) + gdk_colormap_alloc_color(gdk_colormap_get_system(), &contact_color, FALSE, TRUE); + else + memset(&contact_color, 0, sizeof(GdkColor)); } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "contact_text")) != NULL))) { - contact->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + contact.font = xmlnode_get_attrib(sub_sub_node, "font"); if(gdk_color_parse(temp = xmlnode_get_attrib(sub_sub_node, "color"), &color)) - contact->color = g_strdup(temp); - else contact->color = g_strdup(DEFAULT_TEXT_COLOR); + contact.color = temp; + else contact.color = DEFAULT_TEXT_COLOR; } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "online_text")) != NULL))) { - online->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + online.font = xmlnode_get_attrib(sub_sub_node, "font"); if(gdk_color_parse(temp = xmlnode_get_attrib(sub_sub_node, "color"), &color)) - online->color = g_strdup(temp); - else online->color = g_strdup(DEFAULT_TEXT_COLOR); + online.color = temp; + else online.color = DEFAULT_TEXT_COLOR; } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "away_text")) != NULL))) { - away->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + away.font = xmlnode_get_attrib(sub_sub_node, "font"); if(gdk_color_parse(temp = xmlnode_get_attrib(sub_sub_node, "color"), &color)) - away->color = g_strdup(temp); - else away->color = g_strdup(DEFAULT_TEXT_COLOR); + away.color = temp; + else away.color = DEFAULT_TEXT_COLOR; } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "offline_text")) != NULL))) { - offline->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + offline.font = xmlnode_get_attrib(sub_sub_node, "font"); if(gdk_color_parse(temp = xmlnode_get_attrib(sub_sub_node, "color"), &color)) - online->color = g_strdup(temp); - else online->color = g_strdup(DEFAULT_TEXT_COLOR); + offline.color = temp; + else offline.color = DEFAULT_TEXT_COLOR; } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "idle_text")) != NULL))) { - idle->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + idle.font = xmlnode_get_attrib(sub_sub_node, "font"); if(gdk_color_parse(temp = xmlnode_get_attrib(sub_sub_node, "color"), &color)) - idle->color = g_strdup(temp); - else online->color = g_strdup(DEFAULT_TEXT_COLOR); + idle.color = temp; + else idle.color = DEFAULT_TEXT_COLOR; } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "message_text")) != NULL))) { - message->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + message.font = xmlnode_get_attrib(sub_sub_node, "font"); if(gdk_color_parse(temp = xmlnode_get_attrib(sub_sub_node, "color"), &color)) - message->color = g_strdup(temp); - else message->color = g_strdup(DEFAULT_TEXT_COLOR); + message.color = temp; + else message.color = DEFAULT_TEXT_COLOR; } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "message_nick_said_text")) != NULL))) { - message_nick_said->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + message_nick_said.font = xmlnode_get_attrib(sub_sub_node, "font"); if(gdk_color_parse(temp = xmlnode_get_attrib(sub_sub_node, "color"), &color)) - message_nick_said->color = g_strdup(temp); - else message_nick_said->color = g_strdup(DEFAULT_TEXT_COLOR); + message_nick_said.color = temp; + else message_nick_said.color = DEFAULT_TEXT_COLOR; } if ((success = (success && sub_node != NULL && (sub_sub_node = xmlnode_get_child(sub_node, "status_text")) != NULL))) { - status->font = g_strdup(xmlnode_get_attrib(sub_sub_node, "font")); + status.font = xmlnode_get_attrib(sub_sub_node, "font"); if(gdk_color_parse(temp = xmlnode_get_attrib(sub_sub_node, "color"), &color)) - status->color = g_strdup(temp); - else status->color = g_strdup(DEFAULT_TEXT_COLOR); + status.color = temp; + else status.color = DEFAULT_TEXT_COLOR; } /* name is required for theme manager */ @@ -215,21 +188,21 @@ "image", xmlnode_get_attrib(root_node, "image"), "directory", dir, "description", data, - "background-color", bgcolor, - "layout", layout, - "expanded-color", expanded_bgcolor, - "expanded-text", expanded, - "collapsed-color", collapsed_bgcolor, - "collapsed-text", collapsed, - "contact-color", contact_color, - "contact", contact, - "online", online, - "away", away, - "offline", offline, - "idle", idle, - "message", message, - "message_nick_said", message_nick_said, - "status", status, NULL); + "background-color", &bgcolor, + "layout", &layout, + "expanded-color", &expanded_bgcolor, + "expanded-text", &expanded, + "collapsed-color", &collapsed_bgcolor, + "collapsed-text", &collapsed, + "contact-color", &contact_color, + "contact", &contact, + "online", &online, + "away", &away, + "offline", &offline, + "idle", &idle, + "message", &message, + "message_nick_said", &message_nick_said, + "status", &status, NULL); xmlnode_free(root_node); g_free(data); diff -r 78ef23551355 -r 872d30754311 pidgin/gtkblist-theme.c --- a/pidgin/gtkblist-theme.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkblist-theme.c Mon Apr 20 00:10:51 2009 +0000 @@ -96,14 +96,21 @@ free_font_and_color(FontColorPair *pair) { if (pair != NULL) { - if (pair->font) - g_free(pair->font); - if (pair->color) - g_free(pair->color); + g_free((gchar *)pair->font); + g_free((gchar *)pair->color); g_free(pair); } } +static FontColorPair * +copy_font_and_color(const FontColorPair *pair) +{ + FontColorPair *copy = g_new0(FontColorPair, 1); + copy->font = g_strdup(pair->font); + copy->color = g_strdup(pair->color); + return copy; +} + /****************************************************************************** * GObject Stuff *****************************************************************************/ @@ -245,17 +252,22 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(obj); /* Buddy List */ + gdk_color_free(priv->bgcolor); g_free(priv->layout); /* Group */ + gdk_color_free(priv->expanded_color); free_font_and_color(priv->expanded); + gdk_color_free(priv->collapsed_color); free_font_and_color(priv->collapsed); /* Buddy */ + gdk_color_free(priv->contact_color); free_font_and_color(priv->contact); free_font_and_color(priv->online); free_font_and_color(priv->away); free_font_and_color(priv->offline); + free_font_and_color(priv->idle); free_font_and_color(priv->message); free_font_and_color(priv->message_nick_said); free_font_and_color(priv->status); @@ -581,7 +593,7 @@ /* Set Methods */ void -pidgin_blist_theme_set_background_color(PidginBlistTheme *theme, GdkColor *color) +pidgin_blist_theme_set_background_color(PidginBlistTheme *theme, const GdkColor *color) { PidginBlistThemePrivate *priv; @@ -589,7 +601,8 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); - priv->bgcolor = color; + gdk_color_free(priv->bgcolor); + priv->bgcolor = gdk_color_copy(color); } void @@ -605,7 +618,7 @@ } void -pidgin_blist_theme_set_layout(PidginBlistTheme *theme, PidginBlistLayout *layout) +pidgin_blist_theme_set_layout(PidginBlistTheme *theme, const PidginBlistLayout *layout) { PidginBlistThemePrivate *priv; @@ -614,11 +627,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); g_free(priv->layout); - priv->layout = layout; + priv->layout = g_memdup(layout, sizeof(PidginBlistLayout)); } void -pidgin_blist_theme_set_expanded_background_color(PidginBlistTheme *theme, GdkColor *color) +pidgin_blist_theme_set_expanded_background_color(PidginBlistTheme *theme, const GdkColor *color) { PidginBlistThemePrivate *priv; @@ -626,11 +639,12 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); - priv->expanded_color = color; + gdk_color_free(priv->expanded_color); + priv->expanded_color = gdk_color_copy(color); } void -pidgin_blist_theme_set_expanded_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_expanded_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -639,11 +653,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->expanded); - priv->expanded = pair; + priv->expanded = copy_font_and_color(pair); } void -pidgin_blist_theme_set_collapsed_background_color(PidginBlistTheme *theme, GdkColor *color) +pidgin_blist_theme_set_collapsed_background_color(PidginBlistTheme *theme, const GdkColor *color) { PidginBlistThemePrivate *priv; @@ -651,11 +665,12 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); - priv->collapsed_color = color; + gdk_color_free(priv->collapsed_color); + priv->collapsed_color = gdk_color_copy(color); } void -pidgin_blist_theme_set_collapsed_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_collapsed_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -664,11 +679,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->collapsed); - priv->collapsed = pair; + priv->collapsed = copy_font_and_color(pair); } void -pidgin_blist_theme_set_contact_color(PidginBlistTheme *theme, GdkColor *color) +pidgin_blist_theme_set_contact_color(PidginBlistTheme *theme, const GdkColor *color) { PidginBlistThemePrivate *priv; @@ -676,11 +691,12 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); - priv->contact_color = color; + gdk_color_free(priv->contact_color); + priv->contact_color = gdk_color_copy(color); } void -pidgin_blist_theme_set_contact_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_contact_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -689,11 +705,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->contact); - priv->contact = pair; + priv->contact = copy_font_and_color(pair); } void -pidgin_blist_theme_set_online_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_online_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -702,11 +718,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->online); - priv->online = pair; + priv->online = copy_font_and_color(pair); } void -pidgin_blist_theme_set_away_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_away_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -715,11 +731,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->away); - priv->away = pair; + priv->away = copy_font_and_color(pair); } void -pidgin_blist_theme_set_offline_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_offline_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -728,11 +744,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->offline); - priv->offline = pair; + priv->offline = copy_font_and_color(pair); } void -pidgin_blist_theme_set_idle_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_idle_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -741,11 +757,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->idle); - priv->idle = pair; + priv->idle = copy_font_and_color(pair); } void -pidgin_blist_theme_set_unread_message_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_unread_message_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -754,11 +770,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->message); - priv->message = pair; + priv->message = copy_font_and_color(pair); } void -pidgin_blist_theme_set_unread_message_nick_said_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_unread_message_nick_said_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -767,11 +783,11 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->message_nick_said); - priv->message_nick_said = pair; + priv->message_nick_said = copy_font_and_color(pair); } void -pidgin_blist_theme_set_status_text_info(PidginBlistTheme *theme, FontColorPair *pair) +pidgin_blist_theme_set_status_text_info(PidginBlistTheme *theme, const FontColorPair *pair) { PidginBlistThemePrivate *priv; @@ -780,5 +796,5 @@ priv = PIDGIN_BLIST_THEME_GET_PRIVATE(G_OBJECT(theme)); free_font_and_color(priv->status); - priv->status = pair; + priv->status = copy_font_and_color(pair); } diff -r 78ef23551355 -r 872d30754311 pidgin/gtkblist-theme.h --- a/pidgin/gtkblist-theme.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkblist-theme.h Mon Apr 20 00:10:51 2009 +0000 @@ -61,8 +61,8 @@ typedef struct { - gchar *font; - gchar *color; + const gchar *font; + const gchar *color; } FontColorPair; @@ -253,7 +253,7 @@ * @param theme The theme. * @param color The new background color. */ -void pidgin_blist_theme_set_background_color(PidginBlistTheme *theme, GdkColor *color); +void pidgin_blist_theme_set_background_color(PidginBlistTheme *theme, const GdkColor *color); /** * Sets the opacity to be used for this buddy list theme. @@ -269,7 +269,7 @@ * @param theme The theme. * @param layout The new layout. */ -void pidgin_blist_theme_set_layout(PidginBlistTheme *theme, PidginBlistLayout *layout); +void pidgin_blist_theme_set_layout(PidginBlistTheme *theme, const PidginBlistLayout *layout); /** * Sets the background color to be used for expanded groups. @@ -277,7 +277,7 @@ * @param theme The theme. * @param color The new background color. */ -void pidgin_blist_theme_set_expanded_background_color(PidginBlistTheme *theme, GdkColor *color); +void pidgin_blist_theme_set_expanded_background_color(PidginBlistTheme *theme, const GdkColor *color); /** * Sets the text color and font to be used for expanded groups. @@ -285,7 +285,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_expanded_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_expanded_text_info(PidginBlistTheme *theme, const FontColorPair *pair); /** * Sets the background color to be used for collapsed groups. @@ -293,7 +293,7 @@ * @param theme The theme. * @param color The new background color. */ -void pidgin_blist_theme_set_collapsed_background_color(PidginBlistTheme *theme, GdkColor *color); +void pidgin_blist_theme_set_collapsed_background_color(PidginBlistTheme *theme, const GdkColor *color); /** * Sets the text color and font to be used for expanded groups. @@ -301,7 +301,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_collapsed_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_collapsed_text_info(PidginBlistTheme *theme, const FontColorPair *pair); /** * Sets the background color to be used for contacts and chats. @@ -309,7 +309,7 @@ * @param theme The theme. * @param color The color to use for contacts and chats. */ -void pidgin_blist_theme_set_contact_color(PidginBlistTheme *theme, GdkColor *color); +void pidgin_blist_theme_set_contact_color(PidginBlistTheme *theme, const GdkColor *color); /** * Sets the text color and font to be used for expanded contacts. @@ -317,7 +317,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_contact_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_contact_text_info(PidginBlistTheme *theme, const FontColorPair *pair); /** * Sets the text color and font to be used for online buddies. @@ -325,7 +325,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_online_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_online_text_info(PidginBlistTheme *theme, const FontColorPair *pair); /** * Sets the text color and font to be used for away and idle buddies. @@ -333,7 +333,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_away_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_away_text_info(PidginBlistTheme *theme, const FontColorPair *pair); /** * Sets the text color and font to be used for offline buddies. @@ -341,7 +341,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_offline_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_offline_text_info(PidginBlistTheme *theme, const FontColorPair *pair); /** * Sets the text color and font to be used for idle buddies. @@ -349,7 +349,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_idle_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_idle_text_info(PidginBlistTheme *theme, const FontColorPair *pair); /** * Sets the text color and font to be used for buddies with unread messages. @@ -357,7 +357,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_unread_message_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_unread_message_text_info(PidginBlistTheme *theme, const FontColorPair *pair); /** * Sets the text color and font to be used for a chat with unread messages @@ -366,7 +366,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_unread_message_nick_said_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_unread_message_nick_said_text_info(PidginBlistTheme *theme, const FontColorPair *pair); /** * Sets the text color and font to be used for buddy status messages. @@ -374,7 +374,7 @@ * @param theme The theme. * @param pair The new text font and color pair. */ -void pidgin_blist_theme_set_status_text_info(PidginBlistTheme *theme, FontColorPair *pair); +void pidgin_blist_theme_set_status_text_info(PidginBlistTheme *theme, const FontColorPair *pair); G_END_DECLS #endif /* PIDGIN_BLIST_THEME_H */ diff -r 78ef23551355 -r 872d30754311 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkblist.c Mon Apr 20 00:10:51 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)) @@ -5457,6 +5505,7 @@ GtkWidget *sep; GtkWidget *label; char *pretty, *tmp; + const char *theme_name; GtkAccelGroup *accel_group; GtkTreeSelection *selection; GtkTargetEntry dte[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW}, @@ -5475,7 +5524,11 @@ gtkblist = PIDGIN_BLIST(list); priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); - priv->current_theme = PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/theme"), "blist")); + theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/theme"); + if (theme_name && *theme_name) + priv->current_theme = PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist")); + else + priv->current_theme = NULL; gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32); gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000); @@ -5742,7 +5795,7 @@ purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible")); /* start the refresh timer */ - gtkblist->refresh_timer = g_timeout_add(30000, (GSourceFunc)pidgin_blist_refresh_timer, list); + gtkblist->refresh_timer = purple_timeout_add_seconds(30, (GSourceFunc)pidgin_blist_refresh_timer, list); handle = pidgin_blist_get_handle(); @@ -5863,7 +5916,7 @@ blist = purple_get_blist(); gtkblist = PIDGIN_BLIST(purple_get_blist()); - gtkblist->refresh_timer = g_timeout_add(30000,(GSourceFunc)pidgin_blist_refresh_timer, blist); + gtkblist->refresh_timer = purple_timeout_add_seconds(30,(GSourceFunc)pidgin_blist_refresh_timer, blist); } static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter) { @@ -6144,7 +6197,7 @@ PurpleBlistNode *selected_node = NULL; GtkTreeIter iter; FontColorPair *pair; - gchar *text_color, *text_font; + gchar const *text_color, *text_font; PidginBlistTheme *theme; group = (PurpleGroup*)gnode; @@ -6577,14 +6630,14 @@ purple_signals_disconnect_by_handle(gtkblist); if (gtkblist->headline_close) - gdk_pixbuf_unref(gtkblist->headline_close); + g_object_unref(G_OBJECT(gtkblist->headline_close)); gtk_widget_destroy(gtkblist->window); pidgin_blist_tooltip_destroy(); if (gtkblist->refresh_timer) - g_source_remove(gtkblist->refresh_timer); + purple_timeout_remove(gtkblist->refresh_timer); if (gtkblist->timeout) g_source_remove(gtkblist->timeout); if (gtkblist->drag_timeout) @@ -7399,7 +7452,7 @@ if(gtknode->recent_signonoff_timer > 0) purple_timeout_remove(gtknode->recent_signonoff_timer); - gtknode->recent_signonoff_timer = purple_timeout_add(10000, + gtknode->recent_signonoff_timer = purple_timeout_add_seconds(10, (GSourceFunc)buddy_signonoff_timeout_cb, buddy); } diff -r 78ef23551355 -r 872d30754311 pidgin/gtkcellrendererexpander.c --- a/pidgin/gtkcellrendererexpander.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkcellrendererexpander.c Mon Apr 20 00:10:51 2009 +0000 @@ -30,7 +30,6 @@ */ #include -#include #include "gtkcellrendererexpander.h" static void pidgin_cell_renderer_expander_get_property (GObject *object, diff -r 78ef23551355 -r 872d30754311 pidgin/gtkcellrendererexpander.h --- a/pidgin/gtkcellrendererexpander.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkcellrendererexpander.h Mon Apr 20 00:10:51 2009 +0000 @@ -21,7 +21,7 @@ #ifndef _PIDGINCELLRENDEREREXPANDER_H_ #define _PIDGINCELLRENDEREREXPANDER_H_ -#include +#include #ifdef __cplusplus extern "C" { diff -r 78ef23551355 -r 872d30754311 pidgin/gtkcellrendererprogress.h --- a/pidgin/gtkcellrendererprogress.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkcellrendererprogress.h Mon Apr 20 00:10:51 2009 +0000 @@ -21,7 +21,7 @@ #ifndef _PIDGINCELLRENDERERPROGRESS_H_ #define _PIDGINCELLRENDERERPROGRESS_H_ -#include +#include #ifdef __cplusplus extern "C" { diff -r 78ef23551355 -r 872d30754311 pidgin/gtkconn.c diff -r 78ef23551355 -r 872d30754311 pidgin/gtkconv.c --- a/pidgin/gtkconv.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkconv.c Mon Apr 20 00:10:51 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->audio_call = + gtk_item_factory_get_widget(win->menu.item_factory, + N_("/Conversation/Media/Audio Call")); + win->video_call = + gtk_item_factory_get_widget(win->menu.item_factory, + N_("/Conversation/Media/Video Call")); + win->audio_video_call = + gtk_item_factory_get_widget(win->menu.item_factory, + N_("/Conversation/Media/Audio\\/Video Call")); +#endif + /* --- */ win->menu.send_file = @@ -4941,11 +4981,17 @@ PurpleConversation *conv = gtkconv->active_conv; PidginWindow *win = gtkconv->win; PurpleConversation *c; + PurpleAccount *convaccount = purple_conversation_get_account(conv); + PurpleConnection *gc = purple_account_get_connection(convaccount); + PurplePluginProtocolInfo *prpl_info = gc ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL; + if (sd->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE)) { PurpleBlistNode *n = NULL; PurpleBuddy *b; PidginConversation *gtkconv = NULL; + PurpleAccount *buddyaccount; + const char *buddyname; n = *(PurpleBlistNode **)sd->data; @@ -4956,32 +5002,44 @@ else return; + buddyaccount = purple_buddy_get_account(b); + buddyname = purple_buddy_get_name(b); /* - * If we already have an open conversation with this buddy, then - * just move the conv to this window. Otherwise, create a new - * conv and add it to this window. + * If a buddy is dragged to a chat window of the same protocol, + * invite him to the chat. */ - c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, b->name, b->account); - if (c != NULL) { - PidginWindow *oldwin; - gtkconv = PIDGIN_CONVERSATION(c); - oldwin = gtkconv->win; - if (oldwin != win) { - pidgin_conv_window_remove_gtkconv(oldwin, gtkconv); - pidgin_conv_window_add_gtkconv(win, gtkconv); - } + if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT && + prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, chat_invite) && + strcmp(purple_account_get_protocol_id(convaccount), + purple_account_get_protocol_id(buddyaccount)) == 0) { + purple_conv_chat_invite_user(PURPLE_CONV_CHAT(conv), buddyname, NULL, TRUE); } else { - c = purple_conversation_new(PURPLE_CONV_TYPE_IM, b->account, b->name); - gtkconv = PIDGIN_CONVERSATION(c); - if (gtkconv->win != win) - { - pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv); - pidgin_conv_window_add_gtkconv(win, gtkconv); + /* + * If we already have an open conversation with this buddy, then + * just move the conv to this window. Otherwise, create a new + * conv and add it to this window. + */ + c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddyname, buddyaccount); + if (c != NULL) { + PidginWindow *oldwin; + gtkconv = PIDGIN_CONVERSATION(c); + oldwin = gtkconv->win; + if (oldwin != win) { + pidgin_conv_window_remove_gtkconv(oldwin, gtkconv); + pidgin_conv_window_add_gtkconv(win, gtkconv); + } + } else { + c = purple_conversation_new(PURPLE_CONV_TYPE_IM, buddyaccount, buddyname); + gtkconv = PIDGIN_CONVERSATION(c); + if (gtkconv->win != win) { + pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv); + pidgin_conv_window_add_gtkconv(win, gtkconv); + } } - } - - /* Make this conversation the active conversation */ - pidgin_conv_window_switch_gtkconv(win, gtkconv); + + /* Make this conversation the active conversation */ + pidgin_conv_window_switch_gtkconv(win, gtkconv); + } gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t); } @@ -5000,15 +5058,22 @@ purple_notify_error(win, NULL, _("You are not currently signed on with an account that " "can add that buddy."), NULL); - } - else - { - c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, username); - gtkconv = PIDGIN_CONVERSATION(c); - if (gtkconv->win != win) - { - pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv); - pidgin_conv_window_add_gtkconv(win, gtkconv); + } else { + /* + * If a buddy is dragged to a chat window of the same protocol, + * invite him to the chat. + */ + if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT && + prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, chat_invite) && + strcmp(purple_account_get_protocol_id(convaccount), protocol) == 0) { + purple_conv_chat_invite_user(PURPLE_CONV_CHAT(conv), username, NULL, TRUE); + } else { + c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, username); + gtkconv = PIDGIN_CONVERSATION(c); + if (gtkconv->win != win) { + pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv); + pidgin_conv_window_add_gtkconv(win, gtkconv); + } } } } @@ -5020,7 +5085,7 @@ } else if (sd->target == gdk_atom_intern("text/uri-list", FALSE)) { if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) - pidgin_dnd_file_manage(sd, purple_conversation_get_account(conv), purple_conversation_get_name(conv)); + pidgin_dnd_file_manage(sd, convaccount, purple_conversation_get_name(conv)); gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t); } else @@ -6407,6 +6472,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->audio_call, + caps & PURPLE_MEDIA_CAPS_AUDIO + ? TRUE : FALSE); + gtk_widget_set_sensitive(win->video_call, + caps & PURPLE_MEDIA_CAPS_VIDEO + ? TRUE : FALSE); + gtk_widget_set_sensitive(win->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->audio_call, FALSE); + gtk_widget_set_sensitive(win->video_call, FALSE); + gtk_widget_set_sensitive(win->audio_video_call, FALSE); + } else { + gtk_widget_set_sensitive(win->audio_call, FALSE); + gtk_widget_set_sensitive(win->video_call, FALSE); + gtk_widget_set_sensitive(win->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)); @@ -6731,7 +6826,8 @@ wrote_msg_update_unseen_cb(PurpleAccount *account, const char *who, const char *message, PurpleConversation *conv, PurpleMessageFlags flags, gpointer null) { - if (conv == NULL || PIDGIN_IS_PIDGIN_CONVERSATION(conv)) + PidginConversation *gtkconv = conv ? PIDGIN_CONVERSATION(conv) : NULL; + if (conv == NULL || (gtkconv && gtkconv->win != hidden_convwin)) return; if (flags & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)) { PidginUnseenState unseen = PIDGIN_UNSEEN_NONE; @@ -7444,7 +7540,7 @@ } /* In case a conversation is started after the buddy has signed-on/off */ - g_timeout_add(11000, (GSourceFunc)update_buddy_status_timeout, buddy); + purple_timeout_add_seconds(11, (GSourceFunc)update_buddy_status_timeout, buddy); } static void diff -r 78ef23551355 -r 872d30754311 pidgin/gtkconvwin.h --- a/pidgin/gtkconvwin.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkconvwin.h Mon Apr 20 00:10:51 2009 +0000 @@ -96,6 +96,11 @@ gint drag_motion_signal; gint drag_leave_signal; + + /* Media menu options. */ + GtkWidget *audio_call; + GtkWidget *video_call; + GtkWidget *audio_video_call; }; /*@}*/ diff -r 78ef23551355 -r 872d30754311 pidgin/gtkdebug.c --- a/pidgin/gtkdebug.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkdebug.c Mon Apr 20 00:10:51 2009 +0000 @@ -94,7 +94,7 @@ if(debug_win->timer != 0) { const gchar *text; - g_source_remove(debug_win->timer); + purple_timeout_remove(debug_win->timer); text = gtk_entry_get_text(GTK_ENTRY(debug_win->expression)); purple_prefs_set_string(PIDGIN_PREFS_ROOT "/debug/regex", text); @@ -552,7 +552,7 @@ } if(win->timer == 0) - win->timer = purple_timeout_add(5000, (GSourceFunc)regex_timer_cb, win); + win->timer = purple_timeout_add_seconds(5, (GSourceFunc)regex_timer_cb, win); regex_compile(win); } diff -r 78ef23551355 -r 872d30754311 pidgin/gtkdialogs.c --- a/pidgin/gtkdialogs.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkdialogs.c Mon Apr 20 00:10:51 2009 +0000 @@ -427,7 +427,7 @@ #endif gtk_widget_destroy(logo); logo = gtk_image_new_from_pixbuf(pixbuf); - gdk_pixbuf_unref(pixbuf); + g_object_unref(G_OBJECT(pixbuf)); /* Insert the logo */ obj = gtk_widget_get_accessible(logo); tmp = g_strconcat(PIDGIN_NAME, " " DISPLAY_VERSION, NULL); @@ -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 78ef23551355 -r 872d30754311 pidgin/gtkdnd-hints.h --- a/pidgin/gtkdnd-hints.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkdnd-hints.h Mon Apr 20 00:10:51 2009 +0000 @@ -25,7 +25,7 @@ #define _PIDGIN_DND_HINTS_H_ #include -#include +#include /** * Conversation drag-and-drop arrow types. diff -r 78ef23551355 -r 872d30754311 pidgin/gtkdocklet.c --- a/pidgin/gtkdocklet.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkdocklet.c Mon Apr 20 00:10:51 2009 +0000 @@ -482,7 +482,7 @@ } static GtkWidget * -new_menu_item_with_status_icon(GtkWidget *menu, const char *str, PurpleStatusPrimitive primitive, GtkSignalFunc sf, gpointer data, guint accel_key, guint accel_mods, char *mod) +new_menu_item_with_status_icon(GtkWidget *menu, const char *str, PurpleStatusPrimitive primitive, GCallback cb, gpointer data, guint accel_key, guint accel_mods, char *mod) { GtkWidget *menuitem; GdkPixbuf *pixbuf; @@ -493,8 +493,8 @@ if (menu) gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - if (sf) - g_signal_connect(G_OBJECT(menuitem), "activate", sf, data); + if (cb) + g_signal_connect(G_OBJECT(menuitem), "activate", cb, data); pixbuf = pidgin_create_status_icon(primitive, menu, PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL); image = gtk_image_new_from_pixbuf(pixbuf); diff -r 78ef23551355 -r 872d30754311 pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkimhtml.c Mon Apr 20 00:10:51 2009 +0000 @@ -45,7 +45,7 @@ #include "gtksourceundomanager.h" #include "gtksourceview-marshal.h" #include -#include +#include #include #include #include @@ -782,7 +782,7 @@ gc, TRUE, visible_rect.x, visible_rect.y, visible_rect.width, visible_rect.height); - gdk_gc_unref(gc); + g_object_unref(G_OBJECT(gc)); if (GTK_WIDGET_CLASS (parent_class)->expose_event) return (* GTK_WIDGET_CLASS (parent_class)->expose_event) @@ -873,7 +873,7 @@ !gtk_text_iter_begins_tag(&cur, NULL)); } - gdk_gc_unref(gc); + g_object_unref(G_OBJECT(gc)); if (GTK_WIDGET_CLASS (parent_class)->expose_event) return (* GTK_WIDGET_CLASS (parent_class)->expose_event) @@ -1384,7 +1384,7 @@ gtk_widget_destroy(imhtml->tip_window); } if(imhtml->tip_timer) - gtk_timeout_remove(imhtml->tip_timer); + g_source_remove(imhtml->tip_timer); for(scalables = imhtml->scalables; scalables; scalables = scalables->next) { struct scalable_data *sd = scalables->data; @@ -1451,7 +1451,7 @@ GObjectClass *gobject_class; object_class = (GtkObjectClass*) klass; gobject_class = (GObjectClass*) klass; - parent_class = gtk_type_class(GTK_TYPE_TEXT_VIEW); + parent_class = g_type_class_ref(GTK_TYPE_TEXT_VIEW); signals[URL_CLICKED] = g_signal_new("url_clicked", G_TYPE_FROM_CLASS(gobject_class), G_SIGNAL_RUN_FIRST, @@ -3320,7 +3320,8 @@ pos++; } else if ((pos == 0 || wpos == 0 || isspace(*(c - 1))) && (len_protocol = gtk_imhtml_is_protocol(c)) > 0 && - c[len_protocol] && !isspace(c[len_protocol])) { + c[len_protocol] && !isspace(c[len_protocol]) && + (c[len_protocol] != '<' || !gtk_imhtml_is_tag(c + 1, NULL, NULL, NULL))) { br = FALSE; if (wpos > 0) { gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); diff -r 78ef23551355 -r 872d30754311 pidgin/gtkimhtml.h --- a/pidgin/gtkimhtml.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkimhtml.h Mon Apr 20 00:10:51 2009 +0000 @@ -26,9 +26,7 @@ #define _PIDGINIMHTML_H_ #include -#include -#include -#include +#include #include "gtksourceundomanager.h" #include "connection.h" @@ -42,13 +40,13 @@ **************************************************************************/ /*@{*/ -#define GTK_TYPE_IMHTML (gtk_imhtml_get_type ()) -#define GTK_IMHTML(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_IMHTML, GtkIMHtml)) -#define GTK_IMHTML_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_IMHTML, GtkIMHtmlClass)) -#define GTK_IS_IMHTML(obj) (GTK_CHECK_TYPE ((obj), GTK_TYPE_IMHTML)) -#define GTK_IS_IMHTML_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IMHTML)) +#define GTK_TYPE_IMHTML (gtk_imhtml_get_type()) +#define GTK_IMHTML(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_IMHTML, GtkIMHtml)) +#define GTK_IMHTML_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_IMHTML, GtkIMHtmlClass)) +#define GTK_IS_IMHTML(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_IMHTML)) +#define GTK_IS_IMHTML_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_IMHTML)) #define GTK_IMHTML_SCALABLE(obj) ((GtkIMHtmlScalable *)obj) -#define GTK_IMHTML_ANIMATION(obj) ((GtkIMHtmlAnimation *)obj) +#define GTK_IMHTML_ANIMATION(obj) ((GtkIMHtmlAnimation *)obj) typedef struct _GtkIMHtml GtkIMHtml; typedef struct _GtkIMHtmlClass GtkIMHtmlClass; diff -r 78ef23551355 -r 872d30754311 pidgin/gtkimhtmltoolbar.c --- a/pidgin/gtkimhtmltoolbar.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Mon Apr 20 00:10:51 2009 +0000 @@ -1198,7 +1198,7 @@ GObjectClass *gobject_class; object_class = (GtkObjectClass*) class; gobject_class = (GObjectClass*) class; - parent_class = gtk_type_class(GTK_TYPE_HBOX); + parent_class = g_type_class_ref(GTK_TYPE_HBOX); gobject_class->finalize = gtk_imhtmltoolbar_finalize; purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/toolbar"); diff -r 78ef23551355 -r 872d30754311 pidgin/gtkimhtmltoolbar.h --- a/pidgin/gtkimhtmltoolbar.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkimhtmltoolbar.h Mon Apr 20 00:10:51 2009 +0000 @@ -23,7 +23,7 @@ #ifndef _PIDGINIMHTMLTOOLBAR_H_ #define _PIDGINIMHTMLTOOLBAR_H_ -#include +#include #include "gtkimhtml.h" #ifdef __cplusplus @@ -32,11 +32,11 @@ #define DEFAULT_FONT_FACE "Helvetica 12" -#define GTK_TYPE_IMHTMLTOOLBAR (gtk_imhtmltoolbar_get_type ()) -#define GTK_IMHTMLTOOLBAR(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_IMHTMLTOOLBAR, GtkIMHtmlToolbar)) -#define GTK_IMHTMLTOOLBAR_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_IMHTMLTOOLBAR, GtkIMHtmlToolbarClass)) -#define GTK_IS_IMHTMLTOOLBAR(obj) (GTK_CHECK_TYPE ((obj), GTK_TYPE_IMHTMLTOOLBAR)) -#define GTK_IS_IMHTMLTOOLBAR_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IMHTMLTOOLBAR)) +#define GTK_TYPE_IMHTMLTOOLBAR (gtk_imhtmltoolbar_get_type()) +#define GTK_IMHTMLTOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_IMHTMLTOOLBAR, GtkIMHtmlToolbar)) +#define GTK_IMHTMLTOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_IMHTMLTOOLBAR, GtkIMHtmlToolbarClass)) +#define GTK_IS_IMHTMLTOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_IMHTMLTOOLBAR)) +#define GTK_IS_IMHTMLTOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_IMHTMLTOOLBAR)) typedef struct _GtkIMHtmlToolbar GtkIMHtmlToolbar; typedef struct _GtkIMHtmlToolbarClass GtkIMHtmlToolbarClass; @@ -76,6 +76,7 @@ char *sml; GtkWidget *strikethrough; GtkWidget *insert_hr; + GtkWidget *call; }; struct _GtkIMHtmlToolbarClass { diff -r 78ef23551355 -r 872d30754311 pidgin/gtkmain.c --- a/pidgin/gtkmain.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkmain.c Mon Apr 20 00:10:51 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,7 @@ pidgin_docklet_init(); pidgin_smileys_init(); pidgin_utils_init(); + pidgin_medias_init(); } static GHashTable *ui_info = NULL; @@ -785,7 +787,7 @@ DBusMessage *message = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE, DBUS_INTERFACE_PURPLE, "PurpleBlistSetVisible"); gboolean tr = TRUE; - dbus_message_append_args(message, DBUS_TYPE_UINT32, &tr, DBUS_TYPE_INVALID); + dbus_message_append_args(message, DBUS_TYPE_INT32, &tr, DBUS_TYPE_INVALID); dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); #endif diff -r 78ef23551355 -r 872d30754311 pidgin/gtkmedia.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkmedia.c Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,1143 @@ +/** + * @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 "media-gst.h" + +#include + +#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; + +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 _PidginMediaClass +{ + GtkWindowClass parent_class; +}; + +struct _PidginMedia +{ + GtkWindow parent; + PidginMediaPrivate *priv; +}; + +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; + + guint timeout_id; + PurpleMediaSessionType request_type; +}; + +#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 +}; + +static 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_stream_info(media->priv->media, + gtk_toggle_button_get_active(toggle) ? + PURPLE_MEDIA_INFO_MUTE : PURPLE_MEDIA_INFO_UNMUTE, + NULL, NULL, TRUE); +} + +static gboolean +pidgin_media_delete_event_cb(GtkWidget *widget, + GdkEvent *event, PidginMedia *media) +{ + if (media->priv->media) + purple_media_stream_info(media->priv->media, + PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); + 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_stream_info(gtkmedia->priv->media, + PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); +} + +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; + media->priv = PIDGIN_MEDIA_GET_PRIVATE(media); + + XSetErrorHandler(pidgin_x_error_handler); + + vbox = gtk_vbox_new(FALSE, 0); + 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); + + media->priv->display = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_container_set_border_width(GTK_CONTAINER(media->priv->display), + 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) +{ + PurpleMediaManager *manager = purple_media_get_manager(media); + GstElement *element = purple_media_manager_get_pipeline(manager); + 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_media_get_account(gtkmedia->priv->media)); + 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_media_get_account(gtkmedia->priv->media)); + 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 void +pidgin_media_accept_cb(PurpleMedia *media, int index) +{ + purple_media_stream_info(media, PURPLE_MEDIA_INFO_ACCEPT, + NULL, NULL, TRUE); +} + +static void +pidgin_media_reject_cb(PurpleMedia *media, int index) +{ + purple_media_stream_info(media, PURPLE_MEDIA_INFO_REJECT, + NULL, NULL, TRUE); +} + +static gboolean +pidgin_request_timeout_cb(PidginMedia *gtkmedia) +{ + PurpleAccount *account; + PurpleBuddy *buddy; + const gchar *alias; + PurpleMediaSessionType type; + gchar *message = NULL; + + account = purple_media_get_account(gtkmedia->priv->media); + buddy = purple_find_buddy(account, gtkmedia->priv->screenname); + alias = buddy ? purple_buddy_get_contact_alias(buddy) : + gtkmedia->priv->screenname; + type = gtkmedia->priv->request_type; + gtkmedia->priv->timeout_id = 0; + + if (type & PURPLE_MEDIA_AUDIO && type & PURPLE_MEDIA_VIDEO) { + message = g_strdup_printf(_("%s wishes to start an audio/video session with you."), + alias); + } else if (type & PURPLE_MEDIA_AUDIO) { + message = g_strdup_printf(_("%s wishes to start an audio session with you."), + alias); + } else if (type & PURPLE_MEDIA_VIDEO) { + message = g_strdup_printf(_("%s wishes to start a video session with you."), + alias); + } + + gtkmedia->priv->request_type = PURPLE_MEDIA_NONE; + + purple_request_accept_cancel(gtkmedia, "Media invitation", + message, NULL, PURPLE_DEFAULT_ACTION_NONE, + (void*)account, gtkmedia->priv->screenname, NULL, + gtkmedia->priv->media, + pidgin_media_accept_cb, + pidgin_media_reject_cb); + pidgin_media_emit_message(gtkmedia, message); + g_free(message); + return FALSE; +} + +static void +#if GTK_CHECK_VERSION(2,12,0) +pidgin_media_input_volume_changed(GtkScaleButton *range, double value, + PurpleMedia *media) +{ + double val = (double)value * 100.0; +#else +pidgin_media_input_volume_changed(GtkRange *range, PurpleMedia *media) +{ + double val = (double)gtk_range_get_value(GTK_RANGE(range)); +#endif + purple_prefs_set_int("/pidgin/media/audio/volume/input", val); + purple_media_set_input_volume(media, NULL, val / 10.0); +} + +static void +#if GTK_CHECK_VERSION(2,12,0) +pidgin_media_output_volume_changed(GtkScaleButton *range, double value, + PurpleMedia *media) +{ + double val = (double)value * 100.0; +#else +pidgin_media_output_volume_changed(GtkRange *range, PurpleMedia *media) +{ + double val = (double)gtk_range_get_value(GTK_RANGE(range)); +#endif + purple_prefs_set_int("/pidgin/media/audio/volume/output", val); + purple_media_set_output_volume(media, NULL, NULL, val / 10.0); +} + +static GtkWidget * +pidgin_media_add_audio_widget(PidginMedia *gtkmedia, + PurpleMediaSessionType type) +{ + GtkWidget *volume_widget, *progress_parent, *volume, *progress; + double value; + + if (type & PURPLE_MEDIA_SEND_AUDIO) { + value = purple_prefs_get_int( + "/pidgin/media/audio/volume/input"); + } else if (type & PURPLE_MEDIA_RECV_AUDIO) { + value = purple_prefs_get_int( + "/pidgin/media/audio/volume/output"); + } else + g_return_val_if_reached(NULL); + +#if GTK_CHECK_VERSION(2,12,0) + /* Setup widget structure */ + volume_widget = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + progress_parent = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(volume_widget), + progress_parent, TRUE, TRUE, 0); + + /* Volume button */ + volume = gtk_volume_button_new(); + gtk_scale_button_set_value(GTK_SCALE_BUTTON(volume), value/100.0); + gtk_box_pack_end(GTK_BOX(volume_widget), + volume, FALSE, FALSE, 0); +#else + /* Setup widget structure */ + volume_widget = gtk_vbox_new(FALSE, 0); + progress_parent = volume_widget; + + /* Volume slider */ + volume = gtk_hscale_new_with_range(0.0, 100.0, 5.0); + gtk_range_set_increments(GTK_RANGE(volume), 5.0, 25.0); + gtk_range_set_value(GTK_RANGE(volume), value); + gtk_scale_set_draw_value(GTK_SCALE(volume), FALSE); + gtk_box_pack_end(GTK_BOX(volume_widget), + volume, TRUE, FALSE, 0); +#endif + + /* Volume level indicator */ + progress = gtk_progress_bar_new(); + gtk_widget_set_size_request(progress, 250, 10); + gtk_box_pack_end(GTK_BOX(progress_parent), progress, TRUE, FALSE, 0); + + if (type & PURPLE_MEDIA_SEND_AUDIO) { + g_signal_connect (G_OBJECT(volume), "value-changed", + G_CALLBACK(pidgin_media_input_volume_changed), + gtkmedia->priv->media); + gtkmedia->priv->send_progress = progress; + } else if (type & PURPLE_MEDIA_RECV_AUDIO) { + g_signal_connect (G_OBJECT(volume), "value-changed", + G_CALLBACK(pidgin_media_output_volume_changed), + gtkmedia->priv->media); + gtkmedia->priv->recv_progress = progress; + } + + gtk_widget_show_all(volume_widget); + + return volume_widget; +} + +static void +pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia, const gchar *sid) +{ + PurpleMediaManager *manager = purple_media_get_manager(media); + GstElement *pipeline = purple_media_manager_get_pipeline(manager); + GtkWidget *send_widget = NULL, *recv_widget = NULL; + 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; + 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); + + 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(aspect), 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; + 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); + + 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(aspect), 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) { + gtk_box_pack_end(GTK_BOX(recv_widget), + pidgin_media_add_audio_widget(gtkmedia, + PURPLE_MEDIA_RECV_AUDIO), FALSE, FALSE, 0); + } + if (type & PURPLE_MEDIA_SEND_AUDIO) { + GstElement *media_src; + GtkWidget *hbox; + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_box_pack_end(GTK_BOX(send_widget), hbox, FALSE, FALSE, 0); + gtkmedia->priv->mute = + gtk_toggle_button_new_with_mnemonic("_Mute"); + g_signal_connect(gtkmedia->priv->mute, "toggled", + G_CALLBACK(pidgin_media_mute_toggled), + gtkmedia); + gtk_box_pack_end(GTK_BOX(hbox), gtkmedia->priv->mute, + FALSE, FALSE, 0); + gtk_widget_show(gtkmedia->priv->mute); + gtk_widget_show(GTK_WIDGET(hbox)); + + media_src = purple_media_get_src(media, sid); + gtkmedia->priv->send_level = gst_bin_get_by_name( + GST_BIN(media_src), "sendlevel"); + + gtk_box_pack_end(GTK_BOX(send_widget), + pidgin_media_add_audio_widget(gtkmedia, + PURPLE_MEDIA_SEND_AUDIO), FALSE, FALSE, 0); + + 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; + + if (purple_media_is_initiator(media, sid, NULL) == FALSE) { + if (gtkmedia->priv->timeout_id != 0) + g_source_remove(gtkmedia->priv->timeout_id); + gtkmedia->priv->request_type |= type; + gtkmedia->priv->timeout_id = g_timeout_add(500, + (GSourceFunc)pidgin_request_timeout_cb, + gtkmedia); + } + + 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, gboolean local, + 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: + { + if (media->priv->media) + g_object_unref(media->priv->media); + media->priv->media = g_value_get_object(value); + g_object_ref(media->priv->media); + + if (purple_media_is_initiator(media->priv->media, + NULL, NULL) == 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; + } +} + +static 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, + PurpleAccount *account, gchar *screenname, gpointer nul) +{ + PidginMedia *gtkmedia = PIDGIN_MEDIA( + pidgin_media_new(media, screenname)); + PurpleBuddy *buddy = purple_find_buddy(account, screenname); + const gchar *alias = buddy ? + purple_buddy_get_contact_alias(buddy) : screenname; + gtk_window_set_title(GTK_WINDOW(gtkmedia), alias); + + if (purple_media_is_initiator(media, NULL, NULL) == TRUE) + gtk_widget_show(GTK_WIDGET(gtkmedia)); + + return TRUE; +} + +static GstElement * +create_default_video_src(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ + GstElement *sendbin, *src, *videoscale, *capsfilter; + GstPad *pad; + GstPad *ghost; + GstCaps *caps; + + src = gst_element_factory_make("gconfvideosrc", NULL); + if (src == NULL) + src = gst_element_factory_make("autovideosrc", NULL); + if (src == NULL) + src = gst_element_factory_make("v4l2src", NULL); + if (src == NULL) + src = gst_element_factory_make("v4lsrc", NULL); + if (src == NULL) + src = gst_element_factory_make("ksvideosrc", NULL); + if (src == NULL) + src = gst_element_factory_make("dshowvideosrc", NULL); + if (src == NULL) { + purple_debug_error("gtkmedia", "Unable to find a suitable " + "element for the default video source.\n"); + return NULL; + } + + sendbin = gst_bin_new("pidgindefaultvideosrc"); + 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,352] , " + "height=[200,288] , framerate=[1/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); + + 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); + + return sendbin; +} + +static GstElement * +create_default_video_sink(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ + GstElement *sink = gst_element_factory_make("gconfvideosink", NULL); + if (sink == NULL) + sink = gst_element_factory_make("autovideosink", NULL); + if (sink == NULL) + purple_debug_error("gtkmedia", "Unable to find a suitable " + "element for the default video sink.\n"); + return sink; +} + +static GstElement * +create_default_audio_src(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ + GstElement *bin, *src, *volume, *level; + GstPad *pad, *ghost; + double input_volume = purple_prefs_get_int( + "/pidgin/media/audio/volume/input")/10.0; + + src = gst_element_factory_make("gconfaudiosrc", NULL); + if (src == NULL) + src = gst_element_factory_make("autoaudiosrc", NULL); + if (src == NULL) + src = gst_element_factory_make("alsasrc", NULL); + if (src == NULL) + src = gst_element_factory_make("osssrc", NULL); + if (src == NULL) + src = gst_element_factory_make("dshowaudiosrc", NULL); + if (src == NULL) { + purple_debug_error("gtkmedia", "Unable to find a suitable " + "element for the default audio source.\n"); + return NULL; + } + + bin = gst_bin_new("pidgindefaultaudiosrc"); + 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); + + return bin; +} + +static GstElement * +create_default_audio_sink(PurpleMedia *media, + const gchar *session_id, const gchar *participant) +{ + GstElement *bin, *sink, *volume, *level, *queue; + GstPad *pad, *ghost; + double output_volume = purple_prefs_get_int( + "/pidgin/media/audio/volume/output")/10.0; + + sink = gst_element_factory_make("gconfaudiosink", NULL); + if (sink == NULL) + sink = gst_element_factory_make("autoaudiosink",NULL); + if (sink == NULL) { + purple_debug_error("gtkmedia", "Unable to find a suitable " + "element for the default audio sink.\n"); + return NULL; + } + + bin = gst_bin_new("pidginrecvaudiobin"); + 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; +} +#endif /* USE_VV */ + +void +pidgin_medias_init(void) +{ +#ifdef USE_VV + PurpleMediaManager *manager = purple_media_manager_get(); + PurpleMediaElementInfo *default_video_src = + g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, + "id", "pidgindefaultvideosrc", + "name", "Pidgin Default Video Source", + "type", PURPLE_MEDIA_ELEMENT_VIDEO + | PURPLE_MEDIA_ELEMENT_SRC + | PURPLE_MEDIA_ELEMENT_ONE_SRC + | PURPLE_MEDIA_ELEMENT_UNIQUE, + "create-cb", create_default_video_src, NULL); + PurpleMediaElementInfo *default_video_sink = + g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, + "id", "pidgindefaultvideosink", + "name", "Pidgin Default Video Sink", + "type", PURPLE_MEDIA_ELEMENT_VIDEO + | PURPLE_MEDIA_ELEMENT_SINK + | PURPLE_MEDIA_ELEMENT_ONE_SINK, + "create-cb", create_default_video_sink, NULL); + PurpleMediaElementInfo *default_audio_src = + g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, + "id", "pidgindefaultaudiosrc", + "name", "Pidgin Default Audio Source", + "type", PURPLE_MEDIA_ELEMENT_AUDIO + | PURPLE_MEDIA_ELEMENT_SRC + | PURPLE_MEDIA_ELEMENT_ONE_SRC + | PURPLE_MEDIA_ELEMENT_UNIQUE, + "create-cb", create_default_audio_src, NULL); + PurpleMediaElementInfo *default_audio_sink = + g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, + "id", "pidgindefaultaudiosink", + "name", "Pidgin Default Audio Sink", + "type", PURPLE_MEDIA_ELEMENT_AUDIO + | PURPLE_MEDIA_ELEMENT_SINK + | PURPLE_MEDIA_ELEMENT_ONE_SINK, + "create-cb", create_default_audio_sink, NULL); + + g_signal_connect(G_OBJECT(manager), "init-media", + G_CALLBACK(pidgin_media_new_cb), NULL); + + purple_media_manager_set_ui_caps(manager, + PURPLE_MEDIA_CAPS_AUDIO | + PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION | + PURPLE_MEDIA_CAPS_VIDEO | + PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION | + PURPLE_MEDIA_CAPS_AUDIO_VIDEO); + + 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); + + purple_prefs_add_none("/pidgin/media"); + purple_prefs_add_none("/pidgin/media/audio"); + purple_prefs_add_none("/pidgin/media/audio/volume"); + purple_prefs_add_int("/pidgin/media/audio/volume/input", 10); + purple_prefs_add_int("/pidgin/media/audio/volume/output", 10); +#endif +} + diff -r 78ef23551355 -r 872d30754311 pidgin/gtkmedia.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkmedia.h Mon Apr 20 00:10:51 2009 +0000 @@ -0,0 +1,36 @@ +/** + * @file gtkmedia.h Pidgin Media API + * @ingroup pidgin + */ + +/* 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_ + +G_BEGIN_DECLS + +void pidgin_medias_init(void); + +G_END_DECLS + +#endif /* __GTKMEDIA_H_ */ diff -r 78ef23551355 -r 872d30754311 pidgin/gtkmenutray.c --- a/pidgin/gtkmenutray.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkmenutray.c Mon Apr 20 00:10:51 2009 +0000 @@ -21,9 +21,7 @@ #include "gtkmenutray.h" -#include -#include -#include +#include /****************************************************************************** * Enums diff -r 78ef23551355 -r 872d30754311 pidgin/gtkmenutray.h --- a/pidgin/gtkmenutray.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkmenutray.h Mon Apr 20 00:10:51 2009 +0000 @@ -24,16 +24,14 @@ #ifndef PIDGIN_MENU_TRAY_H #define PIDGIN_MENU_TRAY_H -#include -#include -#include +#include -#define PIDGIN_TYPE_MENU_TRAY (pidgin_menu_tray_get_gtype()) -#define PIDGIN_MENU_TRAY(obj) (GTK_CHECK_CAST((obj), PIDGIN_TYPE_MENU_TRAY, PidginMenuTray)) -#define PIDGIN_MENU_TRAY_CLASS(klass) (GTK_CHECK_CLASS_CAST((klass), PIDGIN_TYPE_MENU_TRAY, PidginMenuTrayClass)) -#define PIDGIN_IS_MENU_TRAY(obj) (GTK_CHECK_TYPE((obj), PIDGIN_TYPE_MENU_TRAY)) -#define PIDGIN_IS_MENU_TRAY_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), PIDGIN_TYPE_MENU_TRAY)) -#define PIDGIN_MENU_TRAY_GET_CLASS(obj) (GTK_CHECK_GET_CLASS((obj), PIDGIN_TYPE_MENU_TRAY, PidginMenuTrayClass)) +#define PIDGIN_TYPE_MENU_TRAY (pidgin_menu_tray_get_gtype()) +#define PIDGIN_MENU_TRAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PIDGIN_TYPE_MENU_TRAY, PidginMenuTray)) +#define PIDGIN_MENU_TRAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PIDGIN_TYPE_MENU_TRAY, PidginMenuTrayClass)) +#define PIDGIN_IS_MENU_TRAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PIDGIN_TYPE_MENU_TRAY)) +#define PIDGIN_IS_MENU_TRAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PIDGIN_TYPE_MENU_TRAY)) +#define PIDGIN_MENU_TRAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PIDGIN_TYPE_MENU_TRAY, PidginMenuTrayClass)) typedef struct _PidginMenuTray PidginMenuTray; typedef struct _PidginMenuTrayClass PidginMenuTrayClass; diff -r 78ef23551355 -r 872d30754311 pidgin/gtkplugin.c --- a/pidgin/gtkplugin.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkplugin.c Mon Apr 20 00:10:51 2009 +0000 @@ -135,7 +135,13 @@ gtk_list_store_append (ls, &iter); - name = g_markup_escape_text(plug->info->name ? _(plug->info->name) : g_basename(plug->path), -1); + if (plug->info->name) { + name = g_markup_escape_text(_(plug->info->name), -1); + } else { + char *tmp = g_path_get_basename(plug->path); + name = g_markup_escape_text(tmp, -1); + g_free(tmp); + } version = g_markup_escape_text(purple_plugin_get_version(plug), -1); summary = g_markup_escape_text(purple_plugin_get_summary(plug), -1); diff -r 78ef23551355 -r 872d30754311 pidgin/gtkprefs.c --- a/pidgin/gtkprefs.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkprefs.c Mon Apr 20 00:10:51 2009 +0000 @@ -145,6 +145,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) { @@ -618,7 +638,7 @@ gtk_list_store_set(prefs_sound_themes, &iter, 0, pixbuf, 2, purple_theme_get_name(theme), -1); if (pixbuf != NULL) - gdk_pixbuf_unref(pixbuf); + g_object_unref(G_OBJECT(pixbuf)); } else if (PIDGIN_IS_BLIST_THEME(theme) || PIDGIN_IS_STATUS_ICON_THEME(theme)){ GtkListStore *store; @@ -645,7 +665,7 @@ g_free(markup); if (pixbuf != NULL) - gdk_pixbuf_unref(pixbuf); + g_object_unref(G_OBJECT(pixbuf)); } } @@ -682,7 +702,7 @@ gtk_list_store_set(prefs_status_icon_themes, &iter, 0, pixbuf, 1, "(Default) - None\n" "The default Pidgin status icon theme", 2, "", -1); - gdk_pixbuf_unref(pixbuf); + g_object_unref(G_OBJECT(pixbuf)); } /* builds a theme combo box from a list store with colums: icon preview, markup, theme name */ @@ -1406,6 +1426,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 +1513,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 +1600,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); diff -r 78ef23551355 -r 872d30754311 pidgin/gtkprefs.h --- a/pidgin/gtkprefs.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkprefs.h Mon Apr 20 00:10:51 2009 +0000 @@ -81,6 +81,24 @@ 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. + * + * @since 2.6.0 + */ +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 78ef23551355 -r 872d30754311 pidgin/gtksmiley.c --- a/pidgin/gtksmiley.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtksmiley.c Mon Apr 20 00:10:51 2009 +0000 @@ -74,7 +74,7 @@ gtk_widget_destroy(smiley->parent); g_free(smiley->filename); if (smiley->custom_pixbuf) - gdk_pixbuf_unref(smiley->custom_pixbuf); + g_object_unref(G_OBJECT(smiley->custom_pixbuf)); g_free(smiley); } @@ -344,7 +344,7 @@ pixbuf = gdk_pixbuf_new_from_file_at_scale(filename, 64, 64, FALSE, NULL); gtk_image_set_from_pixbuf(GTK_IMAGE(s->smiley_image), pixbuf); if (pixbuf) - gdk_pixbuf_unref(pixbuf); + g_object_unref(G_OBJECT(pixbuf)); gtk_widget_grab_focus(s->smile); } @@ -459,8 +459,8 @@ pidgin_smiley_editor_set_image(PidginSmiley *editor, GdkPixbuf *image) { if (editor->custom_pixbuf) - gdk_pixbuf_unref(editor->custom_pixbuf); - editor->custom_pixbuf = image ? gdk_pixbuf_ref(image) : NULL; + g_object_unref(G_OBJECT(editor->custom_pixbuf)); + editor->custom_pixbuf = image ? g_object_ref(G_OBJECT(image)) : NULL; if (image) gtk_image_set_from_pixbuf(GTK_IMAGE(editor->smiley_image), image); } diff -r 78ef23551355 -r 872d30754311 pidgin/gtksound.c --- a/pidgin/gtksound.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtksound.c Mon Apr 20 00:10:51 2009 +0000 @@ -226,9 +226,9 @@ account_signon_cb(PurpleConnection *gc, gpointer data) { if (mute_login_sounds_timeout != 0) - g_source_remove(mute_login_sounds_timeout); + purple_timeout_remove(mute_login_sounds_timeout); mute_login_sounds = TRUE; - mute_login_sounds_timeout = purple_timeout_add(10000, unmute_login_sounds_cb, NULL); + mute_login_sounds_timeout = purple_timeout_add_seconds(10, unmute_login_sounds_cb, NULL); } const char * diff -r 78ef23551355 -r 872d30754311 pidgin/gtksourceiter.h --- a/pidgin/gtksourceiter.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtksourceiter.h Mon Apr 20 00:10:51 2009 +0000 @@ -28,7 +28,7 @@ #ifndef _PIDGINSOURCEITER_H_ #define _PIDGINSOURCEITER_H_ -#include +#include G_BEGIN_DECLS diff -r 78ef23551355 -r 872d30754311 pidgin/gtksourceundomanager.h --- a/pidgin/gtksourceundomanager.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtksourceundomanager.h Mon Apr 20 00:10:51 2009 +0000 @@ -26,14 +26,14 @@ #ifndef __GTK_SOURCE_UNDO_MANAGER_H__ #define __GTK_SOURCE_UNDO_MANAGER_H__ -#include +#include -#define GTK_SOURCE_TYPE_UNDO_MANAGER (gtk_source_undo_manager_get_type ()) -#define GTK_SOURCE_UNDO_MANAGER(obj) (GTK_CHECK_CAST ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManager)) -#define GTK_SOURCE_UNDO_MANAGER_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass)) -#define GTK_SOURCE_IS_UNDO_MANAGER(obj) (GTK_CHECK_TYPE ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER)) -#define GTK_SOURCE_IS_UNDO_MANAGER_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_SOURCE_TYPE_UNDO_MANAGER)) -#define GTK_SOURCE_UNDO_MANAGER_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass)) +#define GTK_SOURCE_TYPE_UNDO_MANAGER (gtk_source_undo_manager_get_type()) +#define GTK_SOURCE_UNDO_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManager)) +#define GTK_SOURCE_UNDO_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass)) +#define GTK_SOURCE_IS_UNDO_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_SOURCE_TYPE_UNDO_MANAGER)) +#define GTK_SOURCE_IS_UNDO_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_SOURCE_TYPE_UNDO_MANAGER)) +#define GTK_SOURCE_UNDO_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass)) typedef struct _GtkSourceUndoManager GtkSourceUndoManager; diff -r 78ef23551355 -r 872d30754311 pidgin/gtkstatusbox.c --- a/pidgin/gtkstatusbox.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkstatusbox.c Mon Apr 20 00:10:51 2009 +0000 @@ -67,7 +67,8 @@ # endif #endif -#define TYPING_TIMEOUT 4000 +/* Timeout for typing notifications in seconds */ +#define TYPING_TIMEOUT 4 static void imhtml_changed_cb(GtkTextBuffer *buffer, void *data); static void imhtml_format_changed_cb(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons, void *data); @@ -535,12 +536,12 @@ for (i = 0; i < G_N_ELEMENTS(statusbox->connecting_pixbufs); i++) { if (statusbox->connecting_pixbufs[i] != NULL) - gdk_pixbuf_unref(statusbox->connecting_pixbufs[i]); + g_object_unref(G_OBJECT(statusbox->connecting_pixbufs[i])); } for (i = 0; i < G_N_ELEMENTS(statusbox->typing_pixbufs); i++) { if (statusbox->typing_pixbufs[i] != NULL) - gdk_pixbuf_unref(statusbox->typing_pixbufs[i]); + g_object_unref(G_OBJECT(statusbox->typing_pixbufs[i])); } g_object_unref(G_OBJECT(statusbox->store)); @@ -1155,7 +1156,7 @@ /* Reset the status if Escape was pressed */ if (event->keyval == GDK_Escape) { - g_source_remove(status_box->typing); + purple_timeout_remove(status_box->typing); status_box->typing = 0; if (status_box->account != NULL) update_to_reflect_account_status(status_box, status_box->account, @@ -1168,8 +1169,8 @@ } pidgin_status_box_pulse_typing(status_box); - g_source_remove(status_box->typing); - status_box->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box); + purple_timeout_remove(status_box->typing); + status_box->typing = purple_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box); return FALSE; } @@ -1201,7 +1202,7 @@ for (i = 0; i < G_N_ELEMENTS(status_box->connecting_pixbufs); i++) { if (status_box->connecting_pixbufs[i] != NULL) - gdk_pixbuf_unref(status_box->connecting_pixbufs[i]); + g_object_unref(G_OBJECT(status_box->connecting_pixbufs[i])); } status_box->connecting_index = 0; @@ -1224,7 +1225,7 @@ for (i = 0; i < G_N_ELEMENTS(status_box->typing_pixbufs); i++) { if (status_box->typing_pixbufs[i] != NULL) - gdk_pixbuf_unref(status_box->typing_pixbufs[i]); + g_object_unref(G_OBJECT(status_box->typing_pixbufs[i])); } status_box->typing_index = 0; @@ -2596,7 +2597,7 @@ return; } - g_source_remove(status_box->typing); + purple_timeout_remove(status_box->typing); status_box->typing = 0; activate_currently_selected_status(status_box); @@ -2624,7 +2625,7 @@ DATA_COLUMN, &data, -1); if (status_box->typing != 0) - g_source_remove(status_box->typing); + purple_timeout_remove(status_box->typing); status_box->typing = 0; if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box))) @@ -2692,7 +2693,7 @@ GtkTextIter start, end; GtkTextBuffer *buffer; gtk_widget_show_all(status_box->vbox); - status_box->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box); + status_box->typing = purple_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box); gtk_widget_grab_focus(status_box->imhtml); buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box->imhtml)); gtk_text_buffer_get_bounds(buffer, &start, &end); @@ -2741,9 +2742,9 @@ { if (status_box->typing != 0) { pidgin_status_box_pulse_typing(status_box); - g_source_remove(status_box->typing); + purple_timeout_remove(status_box->typing); } - status_box->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box); + status_box->typing = purple_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box); } pidgin_status_box_refresh(status_box); } diff -r 78ef23551355 -r 872d30754311 pidgin/gtkstatusbox.h --- a/pidgin/gtkstatusbox.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkstatusbox.h Mon Apr 20 00:10:51 2009 +0000 @@ -34,8 +34,6 @@ #include "imgstore.h" #include "savedstatuses.h" #include "status.h" -#include -#include G_BEGIN_DECLS diff -r 78ef23551355 -r 872d30754311 pidgin/gtkutils.c --- a/pidgin/gtkutils.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkutils.c Mon Apr 20 00:10:51 2009 +0000 @@ -358,7 +358,7 @@ } GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str, - GtkSignalFunc sf, gpointer data, gboolean checked) + GCallback cb, gpointer data, gboolean checked) { GtkWidget *menuitem; menuitem = gtk_check_menu_item_new_with_mnemonic(str); @@ -368,8 +368,8 @@ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked); - if (sf) - g_signal_connect(G_OBJECT(menuitem), "activate", sf, data); + if (cb) + g_signal_connect(G_OBJECT(menuitem), "activate", cb, data); gtk_widget_show_all(menuitem); @@ -439,7 +439,7 @@ } -GtkWidget *pidgin_new_item_from_stock(GtkWidget *menu, const char *str, const char *icon, GtkSignalFunc sf, gpointer data, guint accel_key, guint accel_mods, char *mod) +GtkWidget *pidgin_new_item_from_stock(GtkWidget *menu, const char *str, const char *icon, GCallback cb, gpointer data, guint accel_key, guint accel_mods, char *mod) { GtkWidget *menuitem; /* @@ -456,8 +456,8 @@ if (menu) gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - if (sf) - g_signal_connect(G_OBJECT(menuitem), "activate", sf, data); + if (cb) + g_signal_connect(G_OBJECT(menuitem), "activate", cb, data); if (icon != NULL) { image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); @@ -950,7 +950,7 @@ "accel changed, scheduling save.\n"); if (!accels_save_timer) - accels_save_timer = g_timeout_add(5000, pidgin_save_accels, + accels_save_timer = purple_timeout_add_seconds(5, pidgin_save_accels, NULL); } @@ -1627,7 +1627,7 @@ _("Set as buddy icon"), DND_BUDDY_ICON, (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE), NULL); - gdk_pixbuf_unref(pb); + g_object_unref(G_OBJECT(pb)); return; } diff -r 78ef23551355 -r 872d30754311 pidgin/gtkutils.h --- a/pidgin/gtkutils.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkutils.h Mon Apr 20 00:10:51 2009 +0000 @@ -233,14 +233,14 @@ * * @param menu The menu to which to append the check menu item. * @param str The title to use for the newly created menu item. - * @param sf A function to call when the menu item is activated. + * @param cb A function to call when the menu item is activated. * @param data Data to pass to the signal function. * @param checked The initial state of the check item * * @return The newly created menu item. */ GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str, - GtkSignalFunc sf, gpointer data, gboolean checked); + GCallback cb, gpointer data, gboolean checked); /** * Creates a menu item. @@ -249,7 +249,7 @@ * @param str The title for the menu item. * @param icon An icon to place to the left of the menu item, * or @c NULL for no icon. - * @param sf A function to call when the menu item is activated. + * @param cb A function to call when the menu item is activated. * @param data Data to pass to the signal function. * @param accel_key Something. * @param accel_mods Something. @@ -258,7 +258,7 @@ * @return The newly created menu item. */ GtkWidget *pidgin_new_item_from_stock(GtkWidget *menu, const char *str, - const char *icon, GtkSignalFunc sf, + const char *icon, GCallback cb, gpointer data, guint accel_key, guint accel_mods, char *mod); diff -r 78ef23551355 -r 872d30754311 pidgin/gtkwhiteboard.c --- a/pidgin/gtkwhiteboard.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/gtkwhiteboard.c Mon Apr 20 00:10:51 2009 +0000 @@ -624,7 +624,7 @@ update_rect.x, update_rect.y, update_rect.width, update_rect.height); - gdk_gc_unref(gfx_con); + g_object_unref(G_OBJECT(gfx_con)); } /* Uses Bresenham's algorithm (as provided by Wikipedia) */ diff -r 78ef23551355 -r 872d30754311 pidgin/minidialog.c --- a/pidgin/minidialog.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/minidialog.c Mon Apr 20 00:10:51 2009 +0000 @@ -26,8 +26,7 @@ #include "internal.h" -#include -#include +#include #include "libpurple/prefs.h" diff -r 78ef23551355 -r 872d30754311 pidgin/minidialog.h --- a/pidgin/minidialog.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/minidialog.h Mon Apr 20 00:10:51 2009 +0000 @@ -28,8 +28,7 @@ #define __PIDGIN_MINI_DIALOG_H__ #include -#include -#include +#include G_BEGIN_DECLS diff -r 78ef23551355 -r 872d30754311 pidgin/pidginstock.c --- a/pidgin/pidginstock.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/pidginstock.c Mon Apr 20 00:10:51 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, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, +#endif }; const SizedStockIcon sized_status_icons [] = { diff -r 78ef23551355 -r 872d30754311 pidgin/pidginstock.h --- a/pidgin/pidginstock.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/pidginstock.h Mon Apr 20 00:10:51 2009 +0000 @@ -23,7 +23,7 @@ * 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 "gtkstatus-icon-theme.h" #ifndef _PIDGIN_STOCK_H_ @@ -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 78ef23551355 -r 872d30754311 pidgin/pixmaps/Makefile.am --- a/pidgin/pixmaps/Makefile.am Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/pixmaps/Makefile.am Mon Apr 20 00:10:51 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 78ef23551355 -r 872d30754311 pidgin/pixmaps/toolbar/16/audio-call.png Binary file pidgin/pixmaps/toolbar/16/audio-call.png has changed diff -r 78ef23551355 -r 872d30754311 pidgin/pixmaps/toolbar/16/video-call.png Binary file pidgin/pixmaps/toolbar/16/video-call.png has changed diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/Makefile.am diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/cap/Makefile.am diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/cap/cap.c --- a/pidgin/plugins/cap/cap.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/plugins/cap/cap.c Mon Apr 20 00:10:51 2009 +0000 @@ -135,7 +135,7 @@ /* g_free(stats->hourly_usage); */ /* g_free(stats->daily_usage); */ if (stats->timeout_source_id != 0) - g_source_remove(stats->timeout_source_id); + purple_timeout_remove(stats->timeout_source_id); g_free(stats); } @@ -352,7 +352,7 @@ if (buddy == NULL) return; - interval = purple_prefs_get_int("/plugins/gtk/cap/max_msg_difference") * 1000 * 60; + interval = purple_prefs_get_int("/plugins/gtk/cap/max_msg_difference") * 60; words = word_count(message); stats = get_stats_for(buddy); @@ -361,9 +361,9 @@ stats->last_message = time(NULL); stats->last_message_status_id = purple_status_get_id(get_status_for(buddy)); if(stats->timeout_source_id != 0) - g_source_remove(stats->timeout_source_id); + purple_timeout_remove(stats->timeout_source_id); - stats->timeout_source_id = g_timeout_add(interval, max_message_difference_cb, stats); + stats->timeout_source_id = purple_timeout_add_seconds(interval, max_message_difference_cb, stats); } /* received-im-msg */ @@ -386,7 +386,7 @@ * then cancel the timeout callback. */ if(stats->timeout_source_id != 0) { purple_debug_info("cap", "Cancelling timeout callback\n"); - g_source_remove(stats->timeout_source_id); + purple_timeout_remove(stats->timeout_source_id); stats->timeout_source_id = 0; } @@ -697,7 +697,7 @@ static void cancel_conversation_timeouts(gpointer key, gpointer value, gpointer user_data) { CapStatistics *stats = value; if(stats->timeout_source_id != 0) { - g_source_remove(stats->timeout_source_id); + purple_timeout_remove(stats->timeout_source_id); stats->timeout_source_id = 0; } } diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/contact_priority.c --- a/pidgin/plugins/contact_priority.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/plugins/contact_priority.c Mon Apr 20 00:10:51 2009 +0000 @@ -31,7 +31,7 @@ select_account(GtkWidget *widget, PurpleAccount *account, gpointer data) { gtk_spin_button_set_value(GTK_SPIN_BUTTON(data), - (gdouble)purple_account_get_int(account, "score", 0)); + (gdouble)purple_account_get_int(account, "score", 0)); } static void @@ -142,18 +142,18 @@ spin = gtk_spin_button_new((GtkAdjustment *)adj, 1, 0); optmenu = pidgin_account_option_menu_new(NULL, TRUE, - G_CALLBACK(select_account), - NULL, spin); + G_CALLBACK(select_account), + NULL, spin); gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0); /* this is where we set up the spin button we made above */ account = g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu))))), - "account"); + "account"); gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), - (gdouble)purple_account_get_int(account, "score", 0)); + (gdouble)purple_account_get_int(account, "score", 0)); gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(spin), GTK_ADJUSTMENT(adj)); g_signal_connect(G_OBJECT(spin), "value-changed", - G_CALLBACK(account_update), optmenu); + G_CALLBACK(account_update), optmenu); gtk_box_pack_start(GTK_BOX(hbox), spin, FALSE, FALSE, 0); gtk_widget_show_all(ret); @@ -178,29 +178,29 @@ PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, - PURPLE_PLUGIN_STANDARD, /**< type */ + PURPLE_PLUGIN_STANDARD, /**< type */ PIDGIN_PLUGIN_TYPE, /**< ui_requirement */ - 0, /**< flags */ - NULL, /**< dependencies */ - PURPLE_PRIORITY_DEFAULT, /**< priority */ + 0, /**< flags */ + NULL, /**< dependencies */ + PURPLE_PRIORITY_DEFAULT, /**< priority */ - CONTACT_PRIORITY_PLUGIN_ID, /**< id */ - N_("Contact Priority"), /**< name */ - DISPLAY_VERSION, /**< version */ + CONTACT_PRIORITY_PLUGIN_ID, /**< id */ + N_("Contact Priority"), /**< name */ + DISPLAY_VERSION, /**< version */ /**< summary */ N_("Allows for controlling the values associated with different buddy states."), /**< description */ N_("Allows for changing the point values of idle/away/offline states for buddies in contact priority computations."), - "Etan Reisner ", /**< author */ - PURPLE_WEBSITE, /**< homepage */ + "Etan Reisner ", /**< author */ + PURPLE_WEBSITE, /**< homepage */ - NULL, /**< load */ - NULL, /**< unload */ - NULL, /**< destroy */ - &ui_info, /**< ui_info */ - NULL, /**< extra_info */ - NULL, /**< prefs_info */ - NULL, /**< actions */ + NULL, /**< load */ + NULL, /**< unload */ + NULL, /**< destroy */ + &ui_info, /**< ui_info */ + NULL, /**< extra_info */ + NULL, /**< prefs_info */ + NULL, /**< actions */ /* padding */ NULL, diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/gestures/Makefile.am diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/gevolution/Makefile.am diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/mailchk.c --- a/pidgin/plugins/mailchk.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/plugins/mailchk.c Mon Apr 20 00:10:51 2009 +0000 @@ -13,7 +13,7 @@ #define UNREAD_MAIL 0x02 #define NEW_MAIL 0x04 -static guint32 timer = 0; +static guint timer = 0; static GtkWidget *mail = NULL; static gint @@ -93,7 +93,7 @@ PurpleBuddyList *list = purple_get_blist(); if (list && PURPLE_IS_GTK_BLIST(list) && !timer) { check_timeout(NULL); /* we want the box to be drawn immediately */ - timer = g_timeout_add(2000, check_timeout, NULL); + timer = purple_timeout_add_seconds(2, check_timeout, NULL); } } @@ -102,7 +102,7 @@ { PurpleBuddyList *list = purple_get_blist(); if ((!list || !PURPLE_IS_GTK_BLIST(list) || !PIDGIN_BLIST(list)->vbox) && timer) { - g_source_remove(timer); + purple_timeout_remove(timer); timer = 0; } } @@ -123,7 +123,7 @@ } if (list && PURPLE_IS_GTK_BLIST(list) && PIDGIN_BLIST(list)->vbox) - timer = g_timeout_add(2000, check_timeout, NULL); + timer = purple_timeout_add_seconds(2, check_timeout, NULL); purple_signal_connect(conn_handle, "signed-on", plugin, PURPLE_CALLBACK(signon_cb), NULL); @@ -137,7 +137,7 @@ plugin_unload(PurplePlugin *plugin) { if (timer) - g_source_remove(timer); + purple_timeout_remove(timer); timer = 0; if (mail) gtk_widget_destroy(mail); diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/markerline.c --- a/pidgin/plugins/markerline.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/plugins/markerline.c Mon Apr 20 00:10:51 2009 +0000 @@ -84,7 +84,7 @@ gdk_gc_set_rgb_fg_color(gc, &red); gdk_draw_line(event->window, gc, 0, y, visible_rect.width, y); - gdk_gc_unref(gc); + g_object_unref(G_OBJECT(gc)); } return FALSE; } diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/musicmessaging/Makefile.am diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/musicmessaging/musicmessaging.c --- a/pidgin/plugins/musicmessaging/musicmessaging.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/plugins/musicmessaging/musicmessaging.c Mon Apr 20 00:10:51 2009 +0000 @@ -529,7 +529,7 @@ args[1] = "-session_id"; session_id = g_string_new(""); - g_string_sprintfa(session_id, "%d", mmconv_from_conv_loc(mmconv->conv)); + g_string_append_printf(session_id, "%d", mmconv_from_conv_loc(mmconv->conv)); args[2] = session_id->str; args[3] = NULL; diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/ticker/Makefile.am diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/ticker/gtkticker.c --- a/pidgin/plugins/ticker/gtkticker.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/plugins/ticker/gtkticker.c Mon Apr 20 00:10:51 2009 +0000 @@ -41,7 +41,7 @@ gboolean include_internals, GtkCallback callback, gpointer callback_data); -static GtkType gtk_ticker_child_type (GtkContainer *container); +static GType gtk_ticker_child_type (GtkContainer *container); static GtkContainerClass *parent_class = NULL; @@ -97,7 +97,7 @@ widget_class = (GtkWidgetClass*) class; container_class = (GtkContainerClass*) class; - parent_class = gtk_type_class (GTK_TYPE_CONTAINER); + parent_class = g_type_class_ref (GTK_TYPE_CONTAINER); gobject_class->finalize = gtk_ticker_finalize; @@ -112,7 +112,7 @@ container_class->child_type = gtk_ticker_child_type; } -static GtkType gtk_ticker_child_type (GtkContainer *container) +static GType gtk_ticker_child_type (GtkContainer *container) { return GTK_TYPE_WIDGET; } diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/ticker/gtkticker.h --- a/pidgin/plugins/ticker/gtkticker.h Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/plugins/ticker/gtkticker.h Mon Apr 20 00:10:51 2009 +0000 @@ -26,19 +26,18 @@ #include -#include -#include +#include #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ -#define GTK_TYPE_TICKER (gtk_ticker_get_type ()) -#define GTK_TICKER(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_TICKER, GtkTicker)) -#define GTK_TICKER_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_TICKER, GtkTickerClass)) -#define GTK_IS_TICKER(obj) (GTK_CHECK_TYPE ((obj), GTK_TYPE_TICKER)) -#define GTK_IS_TICKER_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TICKER)) +#define GTK_TYPE_TICKER (gtk_ticker_get_type()) +#define GTK_TICKER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_TICKER, GtkTicker)) +#define GTK_TICKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_TICKER, GtkTickerClass)) +#define GTK_IS_TICKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_TICKER)) +#define GTK_IS_TICKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_TICKER)) typedef struct _GtkTicker GtkTicker; @@ -73,7 +72,7 @@ }; -GtkType gtk_ticker_get_type (void); +GType gtk_ticker_get_type (void); GtkWidget* gtk_ticker_new (void); void gtk_ticker_add (GtkTicker *ticker, GtkWidget *widget); diff -r 78ef23551355 -r 872d30754311 pidgin/plugins/win32/transparency/win2ktrans.c --- a/pidgin/plugins/win32/transparency/win2ktrans.c Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/plugins/win32/transparency/win2ktrans.c Mon Apr 20 00:10:51 2009 +0000 @@ -182,7 +182,7 @@ /* On slider val change, update window's transparency level */ g_signal_connect(GTK_OBJECT(slider), "value-changed", - GTK_SIGNAL_FUNC(change_alpha), win); + G_CALLBACK(change_alpha), win); gtk_box_pack_start(GTK_BOX(hbox), slider, FALSE, TRUE, 5); @@ -563,7 +563,7 @@ button = pidgin_prefs_checkbox(_("_IM window transparency"), OPT_WINTRANS_IM_ENABLED, imtransbox); g_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(update_convs_wintrans), + G_CALLBACK(update_convs_wintrans), (gpointer) OPT_WINTRANS_IM_ENABLED); trans_box = gtk_vbox_new(FALSE, 18); @@ -572,12 +572,12 @@ gtk_widget_show(trans_box); g_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(pidgin_toggle_sensitive), trans_box); + G_CALLBACK(pidgin_toggle_sensitive), trans_box); button = pidgin_prefs_checkbox(_("_Show slider bar in IM window"), OPT_WINTRANS_IM_SLIDER, trans_box); g_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(update_convs_wintrans), + G_CALLBACK(update_convs_wintrans), (gpointer) OPT_WINTRANS_IM_SLIDER); button = pidgin_prefs_checkbox( @@ -587,7 +587,7 @@ button = pidgin_prefs_checkbox(_("Always on top"), OPT_WINTRANS_IM_ONTOP, trans_box); g_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(update_convs_wintrans), + G_CALLBACK(update_convs_wintrans), (gpointer) OPT_WINTRANS_IM_ONTOP); gtk_box_pack_start(GTK_BOX(imtransbox), trans_box, FALSE, FALSE, 5); @@ -604,9 +604,9 @@ gtk_widget_set_usize(GTK_WIDGET(slider), 200, -1); g_signal_connect(GTK_OBJECT(slider), "value-changed", - GTK_SIGNAL_FUNC(alpha_change), NULL); + G_CALLBACK(alpha_change), NULL); g_signal_connect(GTK_OBJECT(slider), "focus-out-event", - GTK_SIGNAL_FUNC(alpha_pref_set_int), + G_CALLBACK(alpha_pref_set_int), (gpointer) OPT_WINTRANS_IM_ALPHA); gtk_box_pack_start(GTK_BOX(hbox), slider, FALSE, TRUE, 5); @@ -620,7 +620,7 @@ button = pidgin_prefs_checkbox(_("_Buddy List window transparency"), OPT_WINTRANS_BL_ENABLED, bltransbox); g_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(set_blist_trans), + G_CALLBACK(set_blist_trans), (gpointer) OPT_WINTRANS_BL_ENABLED); trans_box = gtk_vbox_new(FALSE, 18); @@ -628,14 +628,14 @@ gtk_widget_set_sensitive(GTK_WIDGET(trans_box), FALSE); gtk_widget_show(trans_box); g_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(pidgin_toggle_sensitive), trans_box); + G_CALLBACK(pidgin_toggle_sensitive), trans_box); button = pidgin_prefs_checkbox( _("Remove Buddy List window transparency on focus"), OPT_WINTRANS_BL_ONFOCUS, trans_box); button = pidgin_prefs_checkbox(_("Always on top"), OPT_WINTRANS_BL_ONTOP, trans_box); g_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(set_blist_trans), + G_CALLBACK(set_blist_trans), (gpointer) OPT_WINTRANS_BL_ONTOP); gtk_box_pack_start(GTK_BOX(bltransbox), trans_box, FALSE, FALSE, 5); @@ -652,9 +652,9 @@ gtk_widget_set_usize(GTK_WIDGET(slider), 200, -1); g_signal_connect(GTK_OBJECT(slider), "value-changed", - GTK_SIGNAL_FUNC(bl_alpha_change), NULL); + G_CALLBACK(bl_alpha_change), NULL); g_signal_connect(GTK_OBJECT(slider), "focus-out-event", - GTK_SIGNAL_FUNC(alpha_pref_set_int), + G_CALLBACK(alpha_pref_set_int), (gpointer) OPT_WINTRANS_BL_ALPHA); gtk_box_pack_start(GTK_BOX(hbox), slider, FALSE, TRUE, 5); diff -r 78ef23551355 -r 872d30754311 pidgin/win32/nsis/pidgin-installer.nsi --- a/pidgin/win32/nsis/pidgin-installer.nsi Mon Apr 20 00:05:54 2009 +0000 +++ b/pidgin/win32/nsis/pidgin-installer.nsi Mon Apr 20 00:10:51 2009 +0000 @@ -813,6 +813,7 @@ ; Shortcuts.. Delete "$DESKTOP\Pidgin.lnk" + Delete "$SMPROGRAMS\Pidgin.lnk" Goto done diff -r 78ef23551355 -r 872d30754311 po/POTFILES.in --- a/po/POTFILES.in Mon Apr 20 00:05:54 2009 +0000 +++ b/po/POTFILES.in Mon Apr 20 00:10:51 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 @@ -36,6 +37,7 @@ finch/plugins/gnthistory.c finch/plugins/grouping.c finch/plugins/lastlog.c +finch/plugins/gnttinyurl.c libpurple/account.c libpurple/blist.c libpurple/certificate.c @@ -202,6 +204,7 @@ pidgin/gtkimhtmltoolbar.c pidgin/gtklog.c pidgin/gtkmain.c +pidgin/gtkmedia.c pidgin/gtknotify.c pidgin/gtkplugin.c pidgin/gtkpounce.c diff -r 78ef23551355 -r 872d30754311 po/de.po --- a/po/de.po Mon Apr 20 00:05:54 2009 +0000 +++ b/po/de.po Mon Apr 20 00:10:51 2009 +0000 @@ -11,9 +11,9 @@ msgstr "" "Project-Id-Version: de\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-03-28 16:59+0100\n" -"PO-Revision-Date: 2009-03-28 16:53+0100\n" -"Last-Translator: Jochen Kemnade \n" +"POT-Creation-Date: 2009-04-16 16:28+0200\n" +"PO-Revision-Date: 2009-04-16 16:27+0200\n" +"Last-Translator: Björn Voigt \n" "Language-Team: German \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -614,19 +614,6 @@ msgid "Send To" msgstr "Senden an" -msgid "Invite message" -msgstr "Einladungsnachricht" - -msgid "Invite" -msgstr "Einladen" - -msgid "" -"Please enter the name of the user you wish to invite,\n" -"along with an optional invite message." -msgstr "" -"Bitte geben Sie den Benutzernamen der Person ein, die Sie einladen möchten " -"zusammen mit einer optionalen Einladungsnachricht." - msgid "Conversation" msgstr "Unterhaltung" @@ -889,6 +876,39 @@ msgid "System Log" msgstr "System-Mitschnitt" +msgid "Calling ... " +msgstr "Anrufen..." + +msgid "Hangup" +msgstr "Auflegen" + +#. Number of actions +msgid "Accept" +msgstr "Akzeptieren" + +msgid "Reject" +msgstr "Ablehnen" + +msgid "Call in progress." +msgstr "Anruf läuft." + +msgid "The call has been terminated." +msgstr "Der Anruf wurde beendet." + +#, c-format +msgid "%s wishes to start an audio session with you." +msgstr "%s möchte eine Audio-Sitzung mit Ihnen starten." + +#, c-format +msgid "%s is trying to start an unsupported media session type with you." +msgstr "" + +msgid "You have rejected the call." +msgstr "Sie haben den Anruf abgelehnt." + +msgid "call: Make an audio call." +msgstr "call: Einen Audio-Anruf tätigen." + msgid "Emails" msgstr "E-Mails" @@ -923,6 +943,9 @@ msgid "IM" msgstr "Nachricht" +msgid "Invite" +msgstr "Einladen" + msgid "(none)" msgstr "(kein)" @@ -1092,7 +1115,7 @@ #, c-format msgid "%s has paused while typing to you (%s)" -msgstr "%s hat beim Schreiben an Sie (%s) angehalten" +msgstr "%s hat beim Tippen an Sie (%s) angehalten" #, c-format msgid "%s has signed on (%s)" @@ -1537,6 +1560,27 @@ msgid "Lastlog plugin." msgstr "Verlauf-Plugin." +msgid "" +"\n" +"Fetching TinyURL..." +msgstr "" + +msgid "Only create TinyURL for urls of this length or greater" +msgstr "" + +msgid "TinyURL (or other) address prefix" +msgstr "" + +#, fuzzy +msgid "TinyURL" +msgstr "URL anpassen" + +msgid "TinyURL plugin" +msgstr "" + +msgid "When receiving a message with URL(s), TinyURL for easier copying" +msgstr "" + msgid "accounts" msgstr "Konten" @@ -1638,13 +1682,6 @@ msgid "SSL Certificate Verification" msgstr "SSL-Zertifikatsüberprüfung" -#. Number of actions -msgid "Accept" -msgstr "Akzeptieren" - -msgid "Reject" -msgstr "Ablehnen" - msgid "_View Certificate..." msgstr "Ze_rtifikat ansehen..." @@ -1792,6 +1829,18 @@ msgid "%s left the room (%s)." msgstr "%s hat den Raum verlassen (%s)." +#, fuzzy +msgid "Invite to chat" +msgstr "Zur Konferenz einladen" + +#. Put our happy label in it. +msgid "" +"Please enter the name of the user you wish to invite, along with an optional " +"invite message." +msgstr "" +"Bitte geben Sie den Benutzernamen der Person ein, die Sie einladen möchten " +"zusammen mit einer optionalen Einladungsnachricht." + #, c-format msgid "Failed to get connection: %s" msgstr "Kann keine Verbindung herstellen: %s" @@ -2631,7 +2680,7 @@ msgstr "Nicht nachfragen. Immer als Alarm sichern." msgid "One Time Password" -msgstr "" +msgstr "Einmalpasswort" #. *< type #. *< ui_requirement @@ -2640,13 +2689,13 @@ #. *< priority #. *< id msgid "One Time Password Support" -msgstr "" +msgstr "Unterstützung für Einmalpasswörter" #. *< name #. *< version #. * summary msgid "Enforce that passwords are used only once." -msgstr "" +msgstr "Erzwinge, dass Passwörter nur einmal verwendet werden." #. * description msgid "" @@ -3748,6 +3797,10 @@ msgid "Operating System" msgstr "Betriebssystem" +#, fuzzy +msgid "Local Time" +msgstr "Lokale Datei:" + msgid "Last Activity" msgstr "Letzte Aktivität" @@ -4493,6 +4546,37 @@ msgid "%s has buzzed you!" msgstr "%s hat bei Ihnen angeklopft!" +#, fuzzy, c-format +msgid "Unable to initiate media with %s: invalid JID" +msgstr "Kann die Nachricht an %s nicht senden, ungültige JID" + +#, fuzzy, c-format +msgid "Unable to initiate media with %s: user is not online" +msgstr "Kann die Datei nicht an %s senden, Benutzer ist nicht online" + +#, fuzzy, c-format +msgid "Unable to initiate media with %s: not subscribed to user presence" +msgstr "" +"Kann die Datei nicht an %s senden, Anwesenheit des Benutzers nicht abonniert" + +#, fuzzy +msgid "Media Initiation Failed" +msgstr "Registrierung fehlgeschlagen" + +#, fuzzy, c-format +msgid "" +"Please select the resource of %s with which you would like to start a media " +"session." +msgstr "" +"Bitte wählen Sie die Ressource von %s, an die Sie eine Datei schicken möchten" + +msgid "Select a Resource" +msgstr "Wählen Sie eine Ressource" + +#, fuzzy +msgid "Initiate Media" +msgstr "Initiiere _Chat" + msgid "config: Configure a chat room." msgstr "config: Konfiguriere einen Chatraum." @@ -4687,9 +4771,6 @@ msgstr "" "Bitte wählen Sie die Ressource von %s, an die Sie eine Datei schicken möchten" -msgid "Select a Resource" -msgstr "Wählen Sie eine Ressource" - msgid "Edit User Mood" msgstr "Benutzerstimmung ändern" @@ -7308,9 +7389,8 @@ msgid "Change his/her memo as you like" msgstr "" -#, fuzzy msgid "_Modify" -msgstr "Bearbeiten" +msgstr "_Bearbeiten" #, fuzzy msgid "Memo Modify" @@ -10345,6 +10425,16 @@ msgid "I_M" msgstr "I_M" +#, fuzzy +msgid "_Audio Call" +msgstr "Chat _hinzufügen" + +msgid "Audio/_Video Call" +msgstr "Audio/_Video-Anruf" + +msgid "_Video Call" +msgstr "_Video-Anruf" + msgid "_Send File..." msgstr "_Datei versenden..." @@ -10775,14 +10865,6 @@ msgid "Invite Buddy Into Chat Room" msgstr "Buddy in einen Chatraum einladen" -#. Put our happy label in it. -msgid "" -"Please enter the name of the user you wish to invite, along with an optional " -"invite message." -msgstr "" -"Bitte geben Sie den Benutzernamen der Person ein, die Sie einladen möchten " -"zusammen mit einer optionalen Einladungsnachricht." - msgid "_Buddy:" msgstr "_Buddy:" @@ -10857,6 +10939,22 @@ msgid "/Conversation/Clea_r Scrollback" msgstr "/Unterhaltung/_Leeren" +#, fuzzy +msgid "/Conversation/M_edia" +msgstr "/Unterhaltung/Me_hr" + +#, fuzzy +msgid "/Conversation/Media/_Audio Call" +msgstr "/Unterhaltung/Me_hr" + +#, fuzzy +msgid "/Conversation/Media/_Video Call" +msgstr "/Unterhaltung/Me_hr" + +#, fuzzy +msgid "/Conversation/Media/Audio\\/Video _Call" +msgstr "/Unterhaltung/Betrachte _Mitschnitt" + msgid "/Conversation/Se_nd File..." msgstr "/Unterhaltung/Datei _senden..." @@ -10929,6 +11027,18 @@ msgid "/Conversation/View Log" msgstr "/Unterhaltung/Betrachte Mitschnitt" +#, fuzzy +msgid "/Conversation/Media/Audio Call" +msgstr "/Unterhaltung/Mehr" + +#, fuzzy +msgid "/Conversation/Media/Video Call" +msgstr "/Unterhaltung/Betrachte Mitschnitt" + +#, fuzzy +msgid "/Conversation/Media/Audio\\/Video Call" +msgstr "/Unterhaltung/Mehr" + msgid "/Conversation/Send File..." msgstr "/Unterhaltung/Datei senden ..." @@ -12073,6 +12183,23 @@ msgid "Exiting because another libpurple client is already running.\n" msgstr "Wird geschlossen, da bereits ein anderer libpurple-Client läuft\n" +msgid "/_Media" +msgstr "/_Medien" + +msgid "/Media/_Hangup" +msgstr "/Medien/_Auflegen" + +msgid "Calling..." +msgstr "Anrufen..." + +#, c-format +msgid "%s wishes to start an audio/video session with you." +msgstr "" + +#, c-format +msgid "%s wishes to start a video session with you." +msgstr "" + #, c-format msgid "%s has %d new message." msgid_plural "%s has %d new messages." @@ -12240,43 +12367,33 @@ msgid "Pounce Target" msgstr "Alarm-Ziel" -#, fuzzy msgid "Started typing" -msgstr "zu tippen beginnt" - -#, fuzzy +msgstr "Beginnt zu tippen" + msgid "Paused while typing" -msgstr "beim Tippen anhält" - -#, fuzzy +msgstr "Hat beim Tippen angehalten" + msgid "Signed on" -msgstr "sich anmeldet" - -#, fuzzy +msgstr "Hat sich anmeldet" + msgid "Returned from being idle" -msgstr "%s ist nicht mehr inaktiv (%s)" - -#, fuzzy +msgstr "Ist nicht mehr inaktiv" + msgid "Returned from being away" -msgstr "wieder anwesend ist" - -#, fuzzy +msgstr "Ist wieder anwesend" + msgid "Stopped typing" -msgstr "Tippen gestoppt" - -#, fuzzy +msgstr "Hat das Tippen gestoppt" + msgid "Signed off" -msgstr "sich abmeldet" - -#, fuzzy +msgstr "Hat sich abmeldet" + msgid "Became idle" -msgstr "untätig wird" - -#, fuzzy +msgstr "Wurde untätig" + msgid "Went away" -msgstr "Bei Abwesenheit" - -#, fuzzy +msgstr "Ging hinaus" + msgid "Sent a message" msgstr "Eine Nachricht senden" @@ -12420,9 +12537,6 @@ msgid "Cannot start browser configuration program." msgstr "Kann das Browser-Konfigurationsprogramm nicht starten." -msgid "ST_UN server:" -msgstr "ST_UN Server:" - msgid "Example: stunserver.org" msgstr "Beispiel: stunserver.org" @@ -12447,6 +12561,10 @@ msgid "_End port:" msgstr "_End-Port:" +#. TURN server +msgid "Relay Server (TURN)" +msgstr "Relay-Server (TURN)" + msgid "Proxy Server & Browser" msgstr "Proxy-Server & Browser" @@ -12477,7 +12595,7 @@ #. This is a global option that affects SOCKS4 usage even with account-specific proxy settings msgid "Use remote DNS with SOCKS4 proxies" -msgstr "Remote-DNS mit SOCKS4-Proxys benuten" +msgstr "Remote-DNS mit SOCKS4-Proxys benutzen" msgid "_User:" msgstr "_Benutzer:" @@ -14003,3 +14121,16 @@ msgid "This plugin is useful for debbuging XMPP servers or clients." msgstr "" "Dieses Plugin ist nützlich zur Fehlersuche in XMPP-Servern oder -Clients." + +#~ msgid "Invite message" +#~ msgstr "Einladungsnachricht" + +#~ msgid "" +#~ "Please enter the name of the user you wish to invite,\n" +#~ "along with an optional invite message." +#~ msgstr "" +#~ "Bitte geben Sie den Benutzernamen der Person ein, die Sie einladen " +#~ "möchten zusammen mit einer optionalen Einladungsnachricht." + +#~ msgid "ST_UN server:" +#~ msgstr "ST_UN Server:"