]> git.0d.be Git - empathy.git/commitdiff
Merge branch 'call'
authorEmilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
Thu, 9 Jun 2011 09:28:44 +0000 (10:28 +0100)
committerEmilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
Thu, 9 Jun 2011 09:28:44 +0000 (10:28 +0100)
45 files changed:
.gitmodules [new file with mode: 0644]
Makefile.am
autogen.sh
configure.ac
data/.gitignore
data/Empathy.Call.client [new file with mode: 0644]
data/Makefile.am
data/org.freedesktop.Telepathy.Client.Empathy.Call.service.in [new file with mode: 0644]
libempathy-gtk/Makefile.am
libempathy-gtk/empathy-call-utils.c [new file with mode: 0644]
libempathy-gtk/empathy-call-utils.h [new file with mode: 0644]
libempathy-gtk/empathy-contact-menu.c
libempathy-gtk/empathy-individual-menu.c
libempathy-gtk/empathy-new-call-dialog.c
libempathy/Makefile.am
libempathy/empathy-channel-factory.c
libempathy/empathy-contact.c
libempathy/empathy-request-util.h
libempathy/empathy-utils.c
libempathy/empathy-utils.h
m4/ax_config_dir.m4 [new file with mode: 0644]
po/POTFILES.in
src/.gitignore
src/Makefile.am
src/empathy-audio-sink.c
src/empathy-audio-sink.h
src/empathy-call-factory.c [new file with mode: 0644]
src/empathy-call-factory.h [new file with mode: 0644]
src/empathy-call-handler.c [new file with mode: 0644]
src/empathy-call-handler.h [new file with mode: 0644]
src/empathy-call-window-fullscreen.c [new file with mode: 0644]
src/empathy-call-window-fullscreen.h [new file with mode: 0644]
src/empathy-call-window.c [new file with mode: 0644]
src/empathy-call-window.h [new file with mode: 0644]
src/empathy-call-window.ui
src/empathy-call.c [new file with mode: 0644]
src/empathy-event-manager.c
src/empathy-event-manager.h
src/empathy-notifications-approver.c
src/empathy-streamed-media-handler.c
src/empathy-streamed-media-window.c
src/empathy-video-src.c
src/empathy.c
src/ev-sidebar.c
telepathy-yell [new submodule]

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..a61f973
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "telepathy-yell"]
+       path = telepathy-yell
+       url = git://anongit.freedesktop.org/telepathy/telepathy-yell.git
index 4d4462d2eb63a305c053b9a66490dfb734978558..c1bed60f6a37a96b60a5da004bdaa7775f8f9ca2 100644 (file)
@@ -1,4 +1,4 @@
-MY_SUBDIRS = tools extensions po data libempathy libempathy-gtk src help tests
+MY_SUBDIRS = tools extensions po data telepathy-yell libempathy libempathy-gtk src help tests
 NST_SUBDIRS = nautilus-sendto-plugin
 
 DIST_SUBDIRS = $(MY_SUBDIRS) $(NST_SUBDIRS)
index bb0d1f831fd005a6cf45607d940c71c8076bbabf..7c73adb9fbbc51328780cc6ca7deb1a67b7243da 100755 (executable)
@@ -17,6 +17,18 @@ which gnome-autogen.sh || {
     echo "You need to install gnome-common from the GNOME GIT"
     exit 1
 }
-USE_GNOME2_MACROS=1 USE_COMMON_DOC_BUILD=yes . gnome-autogen.sh
 
+# Fetch submodules if needed
+if test ! -f telepathy-yell/autogen.sh;
+then
+  echo "+ Setting up submodules"
+  git submodule init
+fi
+git submodule update
+
+# launch tp-yell's autogen.sh
+cd telepathy-yell
+sh autogen.sh --no-configure
+cd ..
 
+USE_GNOME2_MACROS=1 USE_COMMON_DOC_BUILD=yes . gnome-autogen.sh
index 3e998de911d7132a52110012d4f27e0774168a00..a8a272be188d325a0d9420760594b02f759e076d 100644 (file)
@@ -35,6 +35,7 @@ FOLKS_REQUIRED=0.5.1
 GLIB_REQUIRED=2.28.0
 GNUTLS_REQUIRED=2.8.5
 GTK_REQUIRED=3.0.2
+GSTREAMER_REQUIRED=0.10.32
 KEYRING_REQUIRED=2.26.0
 GCR_REQUIRED=2.91.4
 LIBCANBERRA_GTK_REQUIRED=0.25
@@ -62,6 +63,15 @@ GNOME_CONTROL_CENTER_REQUIRED=2.31.4
 #ifelse(empathy_released, 1, [], [enable_maintainer_mode="yes"])
 #GNOME_MAINTAINER_MODE_DEFINES
 
+# telepathy-yell
+prev_top_build_prefix=$ac_top_build_prefix
+prev_ac_configure_args=$ac_configure_args
+ac_configure_args="$ac_configure_args --disable-shared-library"
+AX_CONFIG_DIR([telepathy-yell])
+ac_top_build_prefix=$prev_top_build_prefix
+ac_configure_args=$prev_ac_configure_args
+export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:"$ac_top_build_prefix"telepathy-yell/telepathy-yell
+
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_SRCDIR([configure.ac])
@@ -154,7 +164,7 @@ PKG_CHECK_MODULES(EMPATHY,
    gmodule-export-2.0
    gobject-2.0
    gsettings-desktop-schemas
-   gstreamer-0.10
+   gstreamer-0.10 >= $GSTREAMER_REQUIRED
    gstreamer-interfaces-0.10
    libxml-2.0
    telepathy-glib >= $TELEPATHY_GLIB_REQUIRED
@@ -166,12 +176,40 @@ PKG_CHECK_MODULES(EMPATHY,
    gcr-3 >= $GCR_REQUIRED
 ])
 
+PKG_CHECK_MODULES(YELL, [telepathy-yell])
+
 PKG_CHECK_MODULES(EMPATHY_AV,
 [
    farsight2-0.10
    telepathy-farsight >= $TELEPATHY_FARSIGHT_REQUIRED
 ])
 
+# -----------------------------------------------------------
+# Call interface
+# -----------------------------------------------------------
+AC_ARG_WITH(call,
+            AC_HELP_STRING([--enable-call=@<:@no/yes/auto@:>@],
+            [build with Call interface support]),,
+            [with_call=auto])
+if test "x$with_call" != "xno" ; then
+   PKG_CHECK_MODULES(EMPATHY_CALL,
+   [
+      farsight2-0.10
+      telepathy-farstream
+   ], have_farstream="yes", have_farstream="no" )
+
+   if test "x$have_farstream" = "xyes"; then
+      AC_DEFINE(HAVE_CALL, 1, [Define if you have Call channel support])
+   fi
+else
+  have_farstream=no
+fi
+
+if test "x$with_call" = "xyes" -a "x$have_farstream" != "xyes"; then
+   AC_MSG_ERROR([Could not find Call handler dependencies.])
+fi
+AM_CONDITIONAL(HAVE_CALL, test "x$have_farstream" = "xyes")
+
 # -----------------------------------------------------------
 # evolution-data-server (about-me)
 # -----------------------------------------------------------
@@ -556,7 +594,7 @@ Configure summary:
        Location awareness (Geoclue):  ${have_geoclue}
        Geocode support (Geoclue):     ${have_geocode}
        Adium themes (Webkit).......:  ${have_webkit}
-       Meego widgets ..............:  ${have_meego}
+       Meego widgets...............:  ${have_meego}
        Control center embedding....:  ${have_control_center_embedding}
        Cheese webcam support ......:  ${have_cheese}
 
@@ -567,4 +605,5 @@ Configure summary:
     Extras:
        Nautilus-sendto plugin......:  ${have_nst}
        Salut E-D-S support.........:  ${with_eds}
+       Exp. Call channel handler...:  ${have_farstream}
 "
index 9033e3d363ceff935f8725be139e062554fa646c..dffae84662b2ab0a9c86295e3c53c908d349a061 100644 (file)
@@ -4,6 +4,7 @@ org.gnome.Empathy.gschema.valid
 org.gnome.Empathy.gschema.xml
 org.freedesktop.Telepathy.Client.Empathy.Chat.service
 org.freedesktop.Telepathy.Client.Empathy.AudioVideo.service
+org.freedesktop.Telepathy.Client.Empathy.Call.service
 empathy-accounts.desktop
 empathy-accounts.desktop.in
 org.freedesktop.Telepathy.Client.Empathy.Auth.service
diff --git a/data/Empathy.Call.client b/data/Empathy.Call.client
new file mode 100644 (file)
index 0000000..259025d
--- /dev/null
@@ -0,0 +1,21 @@
+[org.freedesktop.Telepathy.Client]
+Interfaces=org.freedesktop.Telepathy.Client.Handler
+
+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 0]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Call.DRAFT
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+
+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 1]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Call.DRAFT
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+org.freedesktop.Telepathy.Channel.Type.Call.DRAFT.InitialAudio b=true
+
+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 2]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Call.DRAFT
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+org.freedesktop.Telepathy.Channel.Type.Call.DRAFT.InitialVideo b=true
+
+[org.freedesktop.Telepathy.Client.Handler.Capabilities]
+org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/ice-udp=true
+org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/gtalk-p2p=true
+org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/h264=true
index d7e416a832691ff8f3ea9df58fb8d6c9718ca307..24a7fb9d8013f14bd08571fc9d20d209cababb94 100644 (file)
@@ -28,7 +28,8 @@ servicefiledir = $(datadir)/dbus-1/services
 servicefile_in_files = \
        org.freedesktop.Telepathy.Client.Empathy.Chat.service.in \
        org.freedesktop.Telepathy.Client.Empathy.AudioVideo.service.in \
-       org.freedesktop.Telepathy.Client.Empathy.Auth.service.in
+       org.freedesktop.Telepathy.Client.Empathy.Auth.service.in \
+       org.freedesktop.Telepathy.Client.Empathy.Call.service.in
 
 servicefile_DATA = $(servicefile_in_files:.service.in=.service)
 
@@ -41,11 +42,15 @@ org.freedesktop.Telepathy.Client.Empathy.AudioVideo.service: org.freedesktop.Tel
 org.freedesktop.Telepathy.Client.Empathy.Auth.service: org.freedesktop.Telepathy.Client.Empathy.Auth.service.in
        $(AM_V_GEN)sed -e "s|[@]libexecdir[@]|$(libexecdir)|" $< > $@
 
+org.freedesktop.Telepathy.Client.Empathy.Call.service: org.freedesktop.Telepathy.Client.Empathy.Call.service.in
+       $(AM_V_GEN)sed -e "s|[@]libexecdir[@]|$(libexecdir)|" $< > $@
+
 clientfiledir = $(datarootdir)/telepathy/clients
 clientfile_DATA = \
        Empathy.Chat.client \
        Empathy.AudioVideo.client \
-       Empathy.Auth.client
+       Empathy.Auth.client \
+       Empathy.Call.client
 
 htmldir = $(datadir)/empathy
 html_DATA = Template.html
diff --git a/data/org.freedesktop.Telepathy.Client.Empathy.Call.service.in b/data/org.freedesktop.Telepathy.Client.Empathy.Call.service.in
new file mode 100644 (file)
index 0000000..0496d51
--- /dev/null
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.Client.Empathy.Call
+Exec=@libexecdir@/empathy-call
index bb2565885a81d559dffc5d5e84b1c7b5daf463cc..7023ecfe53e004f5f61dc50bb4c978648b206369 100644 (file)
@@ -8,6 +8,7 @@ AM_CPPFLAGS =                                           \
        -DPKGDATADIR=\""$(pkgdatadir)"\"                \
        -DGCR_API_SUBJECT_TO_CHANGE                     \
        $(EMPATHY_CFLAGS)                               \
+       $(YELL_CFLAGS)                                  \
        $(ENCHANT_CFLAGS)                               \
        $(LIBCHAMPLAIN_CFLAGS)                          \
        $(GEOCLUE_CFLAGS)                               \
@@ -35,6 +36,7 @@ libempathy_gtk_handwritten_source =                   \
        empathy-account-widget.c                \
        empathy-avatar-chooser.c                \
        empathy-avatar-image.c                  \
+       empathy-call-utils.c                    \
        empathy-cell-renderer-activatable.c     \
        empathy-cell-renderer-expander.c        \
        empathy-cell-renderer-text.c            \
@@ -95,6 +97,7 @@ libempathy_gtk_headers =                      \
        empathy-account-widget.h                \
        empathy-avatar-chooser.h                \
        empathy-avatar-image.h                  \
+       empathy-call-utils.h                    \
        empathy-cell-renderer-activatable.h     \
        empathy-cell-renderer-expander.h        \
        empathy-cell-renderer-text.h            \
@@ -159,6 +162,7 @@ nodist_libempathy_gtk_la_SOURCES =\
        $(BUILT_SOURCES)
 
 libempathy_gtk_la_LIBADD =                     \
+       $(EMPATHY_LIBS)                 \
        $(EMPATHY_LIBS)                 \
        $(GTK_LIBS)                     \
        $(LIBNOTIFY_LIBS)                       \
diff --git a/libempathy-gtk/empathy-call-utils.c b/libempathy-gtk/empathy-call-utils.c
new file mode 100644 (file)
index 0000000..4e8f6d2
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * The code contained in this file is free software; you can redistribute
+ * it and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either version
+ * 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this code; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-yell/telepathy-yell.h>
+
+#include "empathy-call-utils.h"
+
+#include <libempathy/empathy-request-util.h>
+
+#define DEBUG_FLAG EMPATHY_DEBUG_OTHER
+#include <libempathy/empathy-debug.h>
+
+static const gchar *
+get_error_display_message (GError *error)
+{
+  if (error->domain != TP_ERROR)
+    return _("There was an error starting the call");
+
+  switch (error->code)
+    {
+      case TP_ERROR_NETWORK_ERROR:
+        return _("Network error");
+      case TP_ERROR_NOT_CAPABLE:
+        return _("The specified contact doesn't support calls");
+      case TP_ERROR_OFFLINE:
+        return _("The specified contact is offline");
+      case TP_ERROR_INVALID_HANDLE:
+        return _("The specified contact is not valid");
+      case TP_ERROR_EMERGENCY_CALLS_NOT_SUPPORTED:
+        return _("Emergency calls are not supported on this protocol");
+    }
+
+  return _("There was an error starting the call");
+}
+
+static void
+show_call_error (GError *error)
+{
+  GtkWidget *dialog;
+
+  dialog = gtk_message_dialog_new (NULL, 0,
+      GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+      "%s", get_error_display_message (error));
+
+  g_signal_connect_swapped (dialog, "response",
+      G_CALLBACK (gtk_widget_destroy),
+      dialog);
+
+  gtk_widget_show (dialog);
+}
+
+GHashTable *
+empathy_call_create_call_request (const gchar *contact,
+    gboolean initial_audio,
+    gboolean initial_video)
+{
+  return tp_asv_new (
+    TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+      TPY_IFACE_CHANNEL_TYPE_CALL,
+    TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+      TP_HANDLE_TYPE_CONTACT,
+    TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING,
+      contact,
+    TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN,
+      initial_audio,
+    TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, G_TYPE_BOOLEAN,
+      initial_video,
+    NULL);
+}
+
+GHashTable *
+empathy_call_create_streamed_media_request (const gchar *contact,
+    gboolean initial_audio,
+    gboolean initial_video)
+{
+  return tp_asv_new (
+    TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+      TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+    TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+      TP_HANDLE_TYPE_CONTACT,
+    TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING,
+      contact,
+    TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO, G_TYPE_BOOLEAN,
+      initial_audio,
+    TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO, G_TYPE_BOOLEAN,
+      initial_video,
+    NULL);
+}
+
+static void
+create_call_channel_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  GError *error = NULL;
+
+  if (!tp_account_channel_request_create_channel_finish (
+           TP_ACCOUNT_CHANNEL_REQUEST (source),
+           result,
+           &error))
+    {
+      DEBUG ("Failed to create Call channel: %s", error->message);
+      show_call_error (error);
+      g_error_free (error);
+    }
+}
+
+static void
+create_streamed_media_channel_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  TpAccountChannelRequest *call_req = user_data;
+  GError *error = NULL;
+
+  if (tp_account_channel_request_create_channel_finish (
+      TP_ACCOUNT_CHANNEL_REQUEST (source), result, &error))
+    {
+      g_object_unref (call_req);
+      return;
+    }
+
+  DEBUG ("Failed to create StreamedMedia channel: %s", error->message);
+
+  if (error->code != TP_ERROR_NOT_IMPLEMENTED)
+    {
+      show_call_error (error);
+      return;
+    }
+
+  DEBUG ("Let's try with a Call channel");
+  g_error_free (error);
+  tp_account_channel_request_create_channel_async (call_req,
+      EMPATHY_CALL_BUS_NAME, NULL,
+      create_call_channel_cb,
+      NULL);
+}
+
+void
+empathy_call_new_with_streams (const gchar *contact,
+    TpAccount *account,
+    gboolean initial_audio,
+    gboolean initial_video,
+    gint64 timestamp)
+{
+  GHashTable *call_request, *streamed_media_request;
+  TpAccountChannelRequest *call_req, *streamed_media_req;
+
+  call_request = empathy_call_create_call_request (contact,
+      initial_audio,
+      initial_video);
+
+  streamed_media_request = empathy_call_create_streamed_media_request (
+      contact, initial_audio, initial_video);
+
+  call_req = tp_account_channel_request_new (account, call_request, timestamp);
+  streamed_media_req = tp_account_channel_request_new (account,
+      streamed_media_request,
+      timestamp);
+
+  tp_account_channel_request_create_channel_async (streamed_media_req,
+      EMPATHY_AV_BUS_NAME, NULL,
+      create_streamed_media_channel_cb,
+      call_req);
+
+  g_hash_table_unref (call_request);
+  g_hash_table_unref (streamed_media_request);
+  g_object_unref (streamed_media_req);
+}
diff --git a/libempathy-gtk/empathy-call-utils.h b/libempathy-gtk/empathy-call-utils.h
new file mode 100644 (file)
index 0000000..99a97f2
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * The code contained in this file is free software; you can redistribute
+ * it and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either version
+ * 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this code; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_CALL_UTILS_H__
+#define __EMPATHY_CALL_UTILS_H__
+
+G_BEGIN_DECLS
+
+/* Calls */
+void empathy_call_new_with_streams (const gchar *contact,
+    TpAccount *account,
+    gboolean initial_audio,
+    gboolean initial_video,
+    gint64 timestamp);
+
+GHashTable * empathy_call_create_call_request (const gchar *contact,
+    gboolean initial_audio,
+    gboolean initial_video);
+
+GHashTable * empathy_call_create_streamed_media_request (const gchar *contact,
+    gboolean initial_audio,
+    gboolean initial_video);
+
+G_END_DECLS
+
+#endif /*  __EMPATHY_CALL_UTILS_H__ */
index ecdbee1ba53cb5e19973ef3c45047eb31b20af23..60e7052ea3711e7974ee177910c31afec838f94c 100644 (file)
@@ -39,6 +39,7 @@
 #include "empathy-contact-dialogs.h"
 #include "empathy-ui-utils.h"
 #include "empathy-share-my-desktop.h"
+#include "empathy-call-utils.h"
 
 static GtkWidget *empathy_contact_block_menu_item_new (EmpathyContact *);
 
@@ -343,7 +344,9 @@ static void
 empathy_contact_audio_call_menu_item_activated (GtkMenuItem *item,
        EmpathyContact *contact)
 {
-       empathy_call_new_with_streams (contact, TRUE, FALSE,
+       empathy_call_new_with_streams (empathy_contact_get_id (contact),
+               empathy_contact_get_account (contact),
+               TRUE, FALSE,
                empathy_get_current_action_time ());
 }
 
@@ -374,7 +377,9 @@ static void
 empathy_contact_video_call_menu_item_activated (GtkMenuItem *item,
        EmpathyContact *contact)
 {
-       empathy_call_new_with_streams (contact, TRUE, TRUE,
+       empathy_call_new_with_streams (empathy_contact_get_id (contact),
+               empathy_contact_get_account (contact),
+               TRUE, TRUE,
                empathy_get_current_action_time ());
 }
 
index 8a2654b8f0cc7085511614c4741723a8071e1067..a92e5bcd980b97911fc6437003a756d2220b2bd6 100644 (file)
@@ -46,6 +46,7 @@
 #include "empathy-ui-utils.h"
 #include "empathy-share-my-desktop.h"
 #include "empathy-linking-dialog.h"
+#include "empathy-call-utils.h"
 
 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualMenu)
 
@@ -617,7 +618,9 @@ empathy_individual_audio_call_menu_item_activated (GtkMenuItem *item,
 {
   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
 
-  empathy_call_new_with_streams (contact, TRUE, FALSE,
+  empathy_call_new_with_streams (empathy_contact_get_id (contact),
+      empathy_contact_get_account (contact),
+      TRUE, FALSE,
       empathy_get_current_action_time ());
 }
 
@@ -659,7 +662,9 @@ empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
 {
   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
 
-  empathy_call_new_with_streams (contact, TRUE, TRUE,
+  empathy_call_new_with_streams (empathy_contact_get_id (contact),
+      empathy_contact_get_account (contact),
+      TRUE, TRUE,
       empathy_get_current_action_time ());
 }
 
index 86430cd2416c230156b03a2be9966b45a2abe0e3..139eb1de576ed80522bf656a7e8cb29c37f388b7 100644 (file)
@@ -28,6 +28,8 @@
 
 #include <telepathy-glib/interfaces.h>
 
+#include <telepathy-yell/telepathy-yell.h>
+
 #include <libempathy/empathy-tp-contact-factory.h>
 #include <libempathy/empathy-contact-manager.h>
 #include <libempathy/empathy-utils.h>
@@ -41,6 +43,7 @@
 
 #include "empathy-new-call-dialog.h"
 #include "empathy-account-chooser.h"
+#include "empathy-call-utils.h"
 
 static EmpathyNewCallDialog *dialog_singleton = NULL;
 
@@ -62,21 +65,6 @@ struct _EmpathyNewCallDialogPriv {
   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_NEW_CALL_DIALOG, \
     EmpathyNewCallDialogPriv))
 
-static void
-create_media_channel_cb (GObject *source,
-    GAsyncResult *result,
-    gpointer user_data)
-{
-  GError *error = NULL;
-
-  if (!tp_account_channel_request_create_channel_finish (
-        TP_ACCOUNT_CHANNEL_REQUEST (source), result, &error))
-    {
-      DEBUG ("Failed to create media channel: %s", error->message);
-      g_error_free (error);
-    }
-}
-
 /**
  * SECTION:empathy-new-call-dialog
  * @title: EmpathyNewCallDialog
@@ -87,34 +75,6 @@ create_media_channel_cb (GObject *source,
  * to be started with any contact on any enabled account.
  */
 
-static void
-call_contact (TpAccount *account,
-    const gchar *contact_id,
-    gboolean video,
-    gint64 timestamp)
-{
-  GHashTable *request;
-  TpAccountChannelRequest *req;
-
-  request = tp_asv_new (
-      TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
-        TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
-      TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
-      TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, contact_id,
-      TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO, G_TYPE_BOOLEAN,
-        TRUE,
-      TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO, G_TYPE_BOOLEAN,
-        video,
-      NULL);
-
-  req = tp_account_channel_request_new (account, request, timestamp);
-
-  tp_account_channel_request_create_channel_async (req, EMPATHY_AV_BUS_NAME,
-      NULL, create_media_channel_cb, NULL);
-
-  g_object_unref (req);
-}
-
 static void
 empathy_new_call_dialog_response (GtkDialog *dialog, int response_id)
 {
@@ -134,7 +94,8 @@ empathy_new_call_dialog_response (GtkDialog *dialog, int response_id)
    * we return from this function. */
   video = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->check_video));
 
-  call_contact (account, contact_id, video,
+  empathy_call_new_with_streams (contact_id,
+      account, TRUE, video,
       empathy_get_current_action_time ());
 
 out:
@@ -169,7 +130,8 @@ conn_prepared_cb (GObject *conn,
 
       chan_type = tp_asv_get_string (fixed, TP_PROP_CHANNEL_CHANNEL_TYPE);
 
-      if (tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
+      if (tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA)
+          && tp_strdiff (chan_type, TPY_IFACE_CHANNEL_TYPE_CALL))
         continue;
 
       if (tp_asv_get_uint32 (fixed, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) !=
index 41cec88fde3f3735de79b740d9b6bc0ffa465f64..48846311f0ea756323ee397128f3207cfa7fe211 100644 (file)
@@ -10,6 +10,7 @@ AM_CPPFLAGS =                                           \
        -DG_LOG_DOMAIN=\"empathy\"                      \
        -DGCR_API_SUBJECT_TO_CHANGE                     \
        $(EMPATHY_CFLAGS)                               \
+       $(YELL_CFLAGS)                                  \
        $(GEOCLUE_CFLAGS)                               \
        $(GEOCODE_CFLAGS)                               \
        $(NETWORK_MANAGER_CFLAGS)                       \
@@ -110,6 +111,7 @@ nodist_libempathy_la_SOURCES =\
 
 libempathy_la_LIBADD =         \
        $(top_builddir)/extensions/libemp-extensions.la \
+       $(top_builddir)/telepathy-yell/telepathy-yell/libtelepathy-yell.la \
        $(GCR_LIBS) \
        $(EMPATHY_LIBS) \
        $(GEOCLUE_LIBS) \
@@ -134,12 +136,12 @@ empathy-marshal.list: $(libempathy_la_SOURCES) Makefile.am
                mv $@.tmp $@; \
        fi
 
-%-marshal.h: %-marshal.list Makefile
-       $(AM_V_GEN)$(GLIB_GENMARSHAL) --header --prefix=_$(subst -,_,$*)_marshal $< > $*-marshal.h
+empathy-marshal.h: empathy-marshal.list Makefile
+       $(AM_V_GEN)$(GLIB_GENMARSHAL) --header --prefix=_empathy_marshal $< > empathy-marshal.h
 
-%-marshal.c: %-marshal.list Makefile
+empathy-marshal.c: empathy-marshal.list Makefile
        $(AM_V_GEN)echo "#include \"empathy-marshal.h\"" > $@ && \
-       $(GLIB_GENMARSHAL) --body --prefix=_$(subst -,_,$*)_marshal $< >> $*-marshal.c
+       $(GLIB_GENMARSHAL) --body --prefix=_empathy_marshal $< >> empathy-marshal.c
 
 empathy-enum-types.h: stamp-empathy-enum-types.h
        $(AM_V_GEN)true
index d1da3047d785afee84ebaa085709ec0716a4dbf9..d8b1cd941b3ce6f10c49f2e5b7b9c2e5e5a772ec 100644 (file)
@@ -19,6 +19,8 @@
  * Authors: Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
  */
 
+#include <config.h>
+
 #include "empathy-channel-factory.h"
 
 #include "empathy-tp-chat.h"
@@ -26,6 +28,8 @@
 
 #include <telepathy-glib/telepathy-glib.h>
 
+#include <telepathy-yell/telepathy-yell.h>
+
 static void factory_iface_init (gpointer, gpointer);
 
 G_DEFINE_TYPE_WITH_CODE (EmpathyChannelFactory, empathy_channel_factory,
@@ -113,6 +117,11 @@ empathy_channel_factory_create_channel (
 
       return TP_CHANNEL (empathy_tp_chat_new (account, conn, path, properties));
     }
+  else if (!tp_strdiff (chan_type, TPY_IFACE_CHANNEL_TYPE_CALL))
+    {
+      return TP_CHANNEL (tpy_call_channel_new (conn, path, properties,
+            error));
+    }
 
   return tp_client_channel_factory_create_channel (
       self->priv->automatic_factory, conn, path, properties, error);
index a90b20eced50ed5c2b5a9622fe5223dcf2ff4f40..55bc40bdd5cae52fa6ef5d2994c770014bc6329b 100644 (file)
@@ -28,6 +28,7 @@
 #include <telepathy-glib/account-manager.h>
 #include <telepathy-glib/interfaces.h>
 #include <telepathy-glib/util.h>
+#include <telepathy-yell/telepathy-yell.h>
 
 #include <telepathy-logger/log-manager.h>
 
@@ -1660,6 +1661,29 @@ tp_caps_to_capabilities (TpCapabilities *caps)
                 TP_PROP_CHANNEL_INTERFACE_SMS_SMS_CHANNEL, NULL))
             capabilities |= EMPATHY_CAPABILITIES_SMS;
         }
+      else if (!tp_strdiff (chan_type,
+        TPY_IFACE_CHANNEL_TYPE_CALL))
+        {
+          guint j;
+
+          if (tp_asv_get_boolean (fixed_prop,
+              TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, NULL))
+            capabilities |= EMPATHY_CAPABILITIES_AUDIO;
+
+          if (tp_asv_get_boolean (fixed_prop,
+              TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, NULL))
+            capabilities |= EMPATHY_CAPABILITIES_VIDEO;
+
+          for (j = 0; allowed_prop[j] != NULL; j++)
+            {
+              if (!tp_strdiff (allowed_prop[j],
+                    TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO))
+                capabilities |= EMPATHY_CAPABILITIES_AUDIO;
+              else if (!tp_strdiff (allowed_prop[j],
+                    TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO))
+                capabilities |= EMPATHY_CAPABILITIES_VIDEO;
+            }
+        }
     }
 
   return capabilities;
index 49b98a43364e8e910a1eb605159f08434b35a589..70a607e73c41d35614805d7140d19ec221868376 100644 (file)
@@ -36,6 +36,9 @@ G_BEGIN_DECLS
 #define EMPATHY_AV_BUS_NAME_SUFFIX "Empathy.AudioVideo"
 #define EMPATHY_AV_BUS_NAME TP_CLIENT_BUS_NAME_BASE EMPATHY_AV_BUS_NAME_SUFFIX
 
+#define EMPATHY_CALL_BUS_NAME_SUFFIX "Empathy.Call"
+#define EMPATHY_CALL_BUS_NAME TP_CLIENT_BUS_NAME_BASE EMPATHY_CALL_BUS_NAME_SUFFIX
+
 #define EMPATHY_FT_BUS_NAME_SUFFIX "Empathy.FileTransfer"
 #define EMPATHY_FT_BUS_NAME TP_CLIENT_BUS_NAME_BASE EMPATHY_FT_BUS_NAME_SUFFIX
 
index 4d6506b6e6ad00a3ef45771313c212b75f6062bd..d35f4bcedfd8d75ebf60d0a7bc91266a050c14ad 100644 (file)
@@ -238,65 +238,6 @@ empathy_xml_node_find_child_prop_value (xmlNodePtr   node,
        return found;
 }
 
-GHashTable *
-empathy_call_create_streamed_media_request (EmpathyContact *contact,
-                                           gboolean initial_audio,
-                                           gboolean initial_video)
-{
-       return tp_asv_new (
-               TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
-                       TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
-               TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
-                       TP_HANDLE_TYPE_CONTACT,
-               TP_PROP_CHANNEL_TARGET_HANDLE, G_TYPE_UINT,
-                       empathy_contact_get_handle (contact),
-               TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO, G_TYPE_BOOLEAN,
-                       initial_audio,
-               TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO, G_TYPE_BOOLEAN,
-                       initial_video,
-               NULL);
-}
-
-static void
-create_media_channel_cb (GObject *source,
-                        GAsyncResult *result,
-                        gpointer user_data)
-{
-       GError *error = NULL;
-
-       if (!tp_account_channel_request_create_channel_finish (TP_ACCOUNT_CHANNEL_REQUEST (source),
-                                                              result,
-                                                              &error)) {
-               DEBUG ("Failed to create StreamedMedia channel: %s", error->message);
-               g_error_free (error);
-       }
-}
-
-void
-empathy_call_new_with_streams (EmpathyContact *contact,
-                              gboolean initial_audio,
-                              gboolean initial_video,
-                              gint64 timestamp)
-{
-       GHashTable *request;
-       TpAccount *account;
-       TpAccountChannelRequest *req;
-
-       request = empathy_call_create_streamed_media_request (contact,
-                                                             initial_audio,
-                                                             initial_video);
-
-       account = empathy_contact_get_account (contact);
-
-       req = tp_account_channel_request_new (account, request, timestamp);
-
-       tp_account_channel_request_create_channel_async (req, EMPATHY_AV_BUS_NAME,
-                                                        NULL, create_media_channel_cb, NULL);
-
-       g_hash_table_unref (request);
-       g_object_unref (req);
-}
-
 const gchar *
 empathy_presence_get_default_message (TpConnectionPresenceType presence)
 {
index 157569453a2e9baa08c35a86ddf23f5bb982fa6e..9a2f3b92485ff8e7c0018078b3ee6883a0077534 100644 (file)
@@ -66,15 +66,6 @@ xmlNodePtr   empathy_xml_node_find_child_prop_value (xmlNodePtr       node,
                                                    const gchar     *prop_name,
                                                    const gchar     *prop_value);
 
-/* Calls */
-void         empathy_call_new_with_streams             (EmpathyContact *contact,
-                                                        gboolean initial_audio,
-                                                        gboolean initial_video,
-                                                        gint64 timestamp);
-GHashTable * empathy_call_create_streamed_media_request (EmpathyContact *contact,
-                                                        gboolean initial_audio,
-                                                        gboolean initial_video);
-
 /* Others */
 const gchar * empathy_presence_get_default_message  (TpConnectionPresenceType presence);
 const gchar * empathy_presence_to_str               (TpConnectionPresenceType presence);
diff --git a/m4/ax_config_dir.m4 b/m4/ax_config_dir.m4
new file mode 100644 (file)
index 0000000..0ba313f
--- /dev/null
@@ -0,0 +1,109 @@
+dnl Copied from Audacity 1.3.10 which itself is licensed under the GPL v2 or
+dnl any later version
+
+dnl Function to configure a sub-library now, because we need to know the result
+dnl of the configuration now in order to take decisions.
+dnl We don't worry about whether the configuration worked or not - it is
+dnl assumed that the next thing after this will be a package-specific check to
+dnl see if the package is actually available. (Hint: use pkg-config and
+dnl -uninstalled.pc files if available).
+dnl code based on a simplification of _AC_OUTPUT_SUBDIRS in 
+dnl /usr/share/autoconf/autoconf/status.m4 which implements part of 
+dnl AC_CONFIG_SUBDIRS
+
+AC_DEFUN([AX_CONFIG_DIR],
+[AC_REQUIRE([AC_DISABLE_OPTION_CHECKING])]
+[m4_append([_AC_LIST_SUBDIRS], [$1], [])]
+[
+  # Remove --cache-file and --srcdir arguments so they do not pile up.
+  ax_sub_configure_args=
+  ax_prev=
+  eval "set x $ac_configure_args"
+  shift
+  for ax_arg
+  do
+    if test -n "$ax_prev"; then
+      ax_prev=
+      continue
+    fi
+    case $ax_arg in
+    -cache-file | --cache-file | --cache-fil | --cache-fi \
+    | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+      ax_prev=cache_file ;;
+    -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+    | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* \
+    | --c=*)
+      ;;
+    --config-cache | -C)
+      ;;
+    -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+      ax_prev=srcdir ;;
+    -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+      ;;
+    -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+      ax_prev=prefix ;;
+    -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+      ;;
+    *)
+      case $ax_arg in
+      *\'*) ax_arg=`echo "$ax_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+      esac
+      ax_sub_configure_args="$ax_sub_configure_args '$ax_arg'" ;;
+    esac
+  done
+
+  # Always prepend --prefix to ensure using the same prefix
+  # in subdir configurations.
+  ax_arg="--prefix=$prefix"
+  case $ax_arg in
+  *\'*) ax_arg=`echo "$ax_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+  esac
+  ax_sub_configure_args="'$ax_arg' $ax_sub_configure_args"
+
+  # Pass --silent
+  if test "$silent" = yes; then
+    ax_sub_configure_args="--silent $ax_sub_configure_args"
+  fi
+
+  ax_popdir=`pwd`
+  AC_MSG_NOTICE([Configuring sources in $1])
+  dnl for out-of-place builds srcdir and builddir will be different, and
+  dnl builddir may not exist, so we must create it
+  AS_MKDIR_P(["$1"])
+  dnl and also set the variables. As this isn't autoconf, the following may be
+  dnl risky:
+  _AC_SRCDIRS(["$1"])
+  cd "$1"
+
+  # Check for guested configure; otherwise get Cygnus style configure.
+  if test -f "configure.gnu"; then
+    ax_sub_configure=$ac_srcdir/configure.gnu
+  elif test -f "$ac_srcdir/configure"; then
+    ax_sub_configure=$ac_srcdir/configure
+  elif test -f "$ac_srcdir/configure.in"; then
+    # This should be Cygnus configure.
+       ax_sub_configure=$ac_aux_dir/configure
+  else
+    AC_MSG_WARN([no configuration information is in $1])
+    ax_sub_configure=
+  fi
+
+  # The recursion is here.
+  if test -n "$ax_sub_configure"; then
+    # Make the cache file name correct relative to the subdirectory.
+    case $cache_file in
+    [[\\/]]* | ?:[[\\/]]* ) ax_sub_cache_file=$cache_file ;;
+    *) # Relative name.
+       ax_sub_cache_file=$ac_top_build_prefix$cache_file ;;
+    esac
+
+    AC_MSG_NOTICE([running $SHELL $ax_sub_configure $ax_sub_configure_args --cache-file=$ax_sub_cache_file --srcdir=$ac_srcdir])
+    # The eval makes quoting arguments work.
+    eval "\$SHELL \"\$ax_sub_configure\" $ax_sub_configure_args \
+          --cache-file=\"\$ax_sub_cache_file\" --srcdir=\"\$ax_srcdir\""
+  fi
+
+  cd "$ax_popdir"
+  AC_MSG_NOTICE([Done configuring in $1])
+])
+
index 7a78f2558c885b195451c0aec5f5692a7ea89a40..a8821f2f3cd84fb1f38fb064f42e68d66cbbc57e 100644 (file)
@@ -27,6 +27,7 @@ libempathy-gtk/empathy-account-widget-sip.c
 [type: gettext/glade]libempathy-gtk/empathy-account-widget-yahoo.ui
 libempathy-gtk/empathy-avatar-chooser.c
 libempathy-gtk/empathy-avatar-image.c
+libempathy-gtk/empathy-call-utils.c
 libempathy-gtk/empathy-chat.c
 [type: gettext/glade]libempathy-gtk/empathy-chat.ui
 libempathy-gtk/empathy-contact-blocking-dialog.c
@@ -116,4 +117,6 @@ src/empathy-debugger.c
 src/empathy-chat.c
 src/empathy-notifications-approver.c
 src/empathy-call-observer.c
+src/empathy-call-window.c
+src/empathy-call.c
 libempathy-gtk/empathy-search-bar.c
index 510316854ae04166e9f6e230a1c61dab80d71d30..8047735ac26ab85a219a973de713ba011aa312ca 100644 (file)
@@ -3,4 +3,5 @@ empathy-accounts
 empathy-debugger
 empathy-av
 empathy-auth-client
+empathy-call
 empathy-chat
index cf3efb0b3ec5553afdc298bbac2f0227eb415893..7fcb9e43518bfc566980cf05487db6fcebd7f4db 100644 (file)
@@ -1,6 +1,7 @@
 include $(top_srcdir)/tools/flymake.mk
 
 CPPFLAGS_COMMON =                                      \
+       $(YELL_CFLAGS)                                  \
        $(EMPATHY_CFLAGS)                               \
        $(EDS_CFLAGS)                                   \
        $(ERROR_CFLAGS)                                 \
@@ -24,6 +25,7 @@ LDADD =                                                               \
        $(top_builddir)/extensions/libemp-extensions.la         \
        $(GCR_LIBS)                                             \
        $(EMPATHY_LIBS)                                         \
+       $(YELL_LIBS)                                            \
        $(LIBCHAMPLAIN_LIBS)                                    \
        $(WEBKIT_LIBS)
 
@@ -143,6 +145,38 @@ empathy_chat_SOURCES =                                             \
        gedit-close-button.c gedit-close-button.h \
        $(NULL)
 
+if HAVE_CALL
+
+libexec_PROGRAMS += empathy-call
+
+empathy_call_SOURCES = \
+       empathy-call.c \
+       empathy-call-factory.c \
+       empathy-call-factory.h \
+       empathy-call-handler.c \
+       empathy-call-handler.h \
+       empathy-call-window.c \
+       empathy-call-window.h \
+       empathy-call-window-fullscreen.c \
+       empathy-call-window-fullscreen.h \
+       empathy-audio-sink.c \
+       empathy-audio-sink.h \
+       empathy-audio-src.c \
+       empathy-audio-src.h \
+       empathy-video-src.c \
+       empathy-video-src.h \
+       empathy-video-widget.c \
+       empathy-video-widget.h \
+       ev-sidebar.c \
+       ev-sidebar.h
+
+nodist_empathy_call_SOURCES = $(BUILT_SOURCES)
+
+empathy_call_CFLAGS = $(EMPATHY_CALL_CFLAGS)
+empathy_call_LDFLAGS = $(EMPATHY_CALL_LIBS)
+
+endif
+
 empathy_handwritten_source = \
        empathy-about-dialog.c empathy-about-dialog.h                   \
        empathy-chat-window.c empathy-chat-window.h                     \
index 1d2169593f73739dad371b861164a48239a6f575..c410d7a306d34a874d578916008ba03058348a1d 100644 (file)
@@ -22,6 +22,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include <gst/audio/audio.h>
 #include <gst/farsight/fs-element-added-notifier.h>
 
 #include "empathy-audio-sink.h"
@@ -38,20 +39,61 @@ enum
 
 static guint signals[LAST_SIGNAL] = {0};
 #endif
+typedef struct {
+  GstPad *pad;
+  GstElement *bin;
+  GstElement *volume;
+  GstElement *sink;
+} AudioBin;
+
+static AudioBin *
+audio_bin_new (GstPad *pad,
+    GstElement *bin,
+    GstElement *volume,
+    GstElement *sink)
+{
+  AudioBin *result = g_slice_new0 (AudioBin);
+
+  result->pad = pad;
+  result->bin = bin;
+  result->volume = gst_object_ref (volume);
+  result->sink = sink;
+
+  return result;
+}
+
+static void
+audio_bin_free (AudioBin *bin)
+{
+  gst_object_unref (bin->volume);
+  g_slice_free (AudioBin, bin);
+}
+
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE(
+    "sink%d",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS ( GST_AUDIO_INT_PAD_TEMPLATE_CAPS " ; "
+        GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS)
+);
 
 enum {
   PROP_VOLUME = 1,
 };
 
-/* private structure */
-typedef struct _EmpathyGstAudioSinkPrivate EmpathyGstAudioSinkPrivate;
-
 struct _EmpathyGstAudioSinkPrivate
 {
   gboolean dispose_has_run;
-  GstElement *sink;
-  GstElement *volume;
   FsElementAddedNotifier *notifier;
+
+  gdouble volume;
+
+  /* Pad -> *owned* subbin hash */
+  GHashTable *audio_bins;
+
+  /* Mutex to hold while change the hash table */
+  GMutex *audio_bins_lock;
 };
 
 #define EMPATHY_GST_AUDIO_SINK_GET_PRIVATE(o) \
@@ -66,54 +108,72 @@ empathy_audio_sink_element_added_cb (FsElementAddedNotifier *notifier,
 
   if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "volume"))
     {
-      gdouble volume;
-
-      volume = empathy_audio_sink_get_volume (self);
-      empathy_audio_sink_set_volume (self, 1.0);
-
-      if (priv->volume != NULL)
-        g_object_unref (priv->volume);
-      priv->volume = g_object_ref (element);
-
-      if (volume != 1.0)
-        empathy_audio_sink_set_volume (self, volume);
+      /* An element was added with a volume property, lets find its subbin and
+       * update the volume in it */
+      GHashTableIter iter;
+      AudioBin *audio_bin = NULL;
+      gpointer value;
+
+      g_mutex_lock (self->priv->audio_bins_lock);
+      g_hash_table_iter_init (&iter, priv->audio_bins);
+
+      while (g_hash_table_iter_next (&iter, NULL, &value))
+        {
+          AudioBin *b = value;
+
+          if (gst_object_has_ancestor (GST_OBJECT (element),
+              GST_OBJECT (b->bin)))
+            {
+              audio_bin = b;
+              break;
+            }
+        }
+
+      if (audio_bin == NULL)
+        {
+          g_warning ("Element added that doesn't belong to us ?");
+          return;
+        }
+
+      /* Set the old volume to 1 and the new volume to the volume */
+      g_object_set (audio_bin->volume, "volume", 1.0, NULL);
+      gst_object_unref (audio_bin->volume);
+
+      audio_bin->volume = gst_object_ref (element);
+      g_object_set (audio_bin->volume, "volume", self->priv->volume, NULL);
+      g_mutex_unlock (self->priv->audio_bins_lock);
     }
 }
 
 static void
-empathy_audio_sink_init (EmpathyGstAudioSink *obj)
+empathy_audio_sink_init (EmpathyGstAudioSink *self)
 {
-  EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (obj);
-  GstElement *resample;
-  GstPad *ghost, *sink;
-
-  priv->notifier = fs_element_added_notifier_new ();
-  g_signal_connect (priv->notifier, "element-added",
-    G_CALLBACK (empathy_audio_sink_element_added_cb), obj);
-
-  resample = gst_element_factory_make ("audioresample", NULL);
+  EmpathyGstAudioSinkPrivate *priv;
 
-  priv->volume = gst_element_factory_make ("volume", NULL);
-  g_object_ref (priv->volume);
+  priv = self->priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
 
-  priv->sink = gst_element_factory_make ("gconfaudiosink", NULL);
+  priv->volume = 1.0;
 
-  fs_element_added_notifier_add (priv->notifier, GST_BIN (priv->sink));
+  priv->audio_bins = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+    NULL, (GDestroyNotify) audio_bin_free);
 
-  gst_bin_add_many (GST_BIN (obj), resample, priv->volume, priv->sink, NULL);
-  gst_element_link_many (resample, priv->volume, priv->sink, NULL);
+  priv->audio_bins_lock = g_mutex_new ();
 
-  sink = gst_element_get_static_pad (resample, "sink");
-
-  ghost = gst_ghost_pad_new ("sink", sink);
-  gst_element_add_pad (GST_ELEMENT (obj), ghost);
-
-  gst_object_unref (G_OBJECT (sink));
+  priv->notifier = fs_element_added_notifier_new ();
+  g_signal_connect (priv->notifier, "element-added",
+    G_CALLBACK (empathy_audio_sink_element_added_cb), self);
 }
 
 static void empathy_audio_sink_dispose (GObject *object);
 static void empathy_audio_sink_finalize (GObject *object);
 
+static GstPad * empathy_audio_sink_request_new_pad (GstElement *self,
+  GstPadTemplate *templ,
+  const gchar* name);
+
+static void empathy_audio_sink_release_pad (GstElement *self,
+  GstPad *pad);
+
 static void
 empathy_audio_sink_set_property (GObject *object,
   guint property_id, const GValue *value, GParamSpec *pspec)
@@ -149,8 +209,13 @@ empathy_audio_sink_class_init (EmpathyGstAudioSinkClass
   *empathy_audio_sink_class)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_sink_class);
+  GstElementClass *element_class =
+    GST_ELEMENT_CLASS (empathy_audio_sink_class);
   GParamSpec *param_spec;
 
+  gst_element_class_add_pad_template (element_class,
+    gst_static_pad_template_get (&sink_template));
+
   g_type_class_add_private (empathy_audio_sink_class,
     sizeof (EmpathyGstAudioSinkPrivate));
 
@@ -160,6 +225,9 @@ empathy_audio_sink_class_init (EmpathyGstAudioSinkClass
   object_class->set_property = empathy_audio_sink_set_property;
   object_class->get_property = empathy_audio_sink_get_property;
 
+  element_class->request_new_pad = empathy_audio_sink_request_new_pad;
+  element_class->release_pad = empathy_audio_sink_release_pad;
+
   param_spec = g_param_spec_double ("volume", "Volume", "volume control",
     0.0, 5.0, 1.0,
     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
@@ -181,9 +249,13 @@ empathy_audio_sink_dispose (GObject *object)
     g_object_unref (priv->notifier);
   priv->notifier = NULL;
 
-  if (priv->volume != NULL)
-    g_object_unref (priv->volume);
-  priv->volume = NULL;
+  if (priv->audio_bins != NULL)
+    g_hash_table_unref (priv->audio_bins);
+  priv->audio_bins = NULL;
+
+  if (priv->audio_bins_lock != NULL)
+    g_mutex_free (priv->audio_bins_lock);
+  priv->audio_bins_lock = NULL;
 
   if (G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose)
     G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose (object);
@@ -219,17 +291,157 @@ void
 empathy_audio_sink_set_volume (EmpathyGstAudioSink *sink, gdouble volume)
 {
   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink);
+  GHashTableIter iter;
+  gpointer value;
+
+  priv->volume = volume;
+
+  g_mutex_lock (priv->audio_bins_lock);
 
-  g_object_set (G_OBJECT (priv->volume), "volume", volume, NULL);
+  g_hash_table_iter_init (&iter, priv->audio_bins);
+  while (g_hash_table_iter_next (&iter, NULL, &value))
+    {
+      AudioBin *b = value;
+      g_object_set (b->volume, "volume", volume, NULL);
+    }
+
+  g_mutex_unlock (priv->audio_bins_lock);
 }
 
 gdouble
 empathy_audio_sink_get_volume (EmpathyGstAudioSink *sink)
 {
   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink);
-  gdouble volume;
+  return priv->volume;
+}
+
+static GstPad *
+empathy_audio_sink_request_new_pad (GstElement *element,
+  GstPadTemplate *templ,
+  const gchar* name)
+{
+  EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element);
+  GstElement *bin, *sink, *volume, *resample, *audioconvert0, *audioconvert1;
+  GstPad *pad = NULL;
+  GstPad *subpad, *filterpad;
+  AudioBin *audiobin;
+
+  bin = gst_bin_new (NULL);
+
+  audioconvert0 = gst_element_factory_make ("audioconvert", NULL);
+  if (audioconvert0 == NULL)
+    goto error;
+
+  gst_bin_add (GST_BIN (bin), audioconvert0);
+
+  resample = gst_element_factory_make ("audioresample", NULL);
+  if (resample == NULL)
+    goto error;
+
+  gst_bin_add (GST_BIN (bin), resample);
+
+  audioconvert1 = gst_element_factory_make ("audioconvert", NULL);
+  if (audioconvert1 == NULL)
+    goto error;
+
+  gst_bin_add (GST_BIN (bin), audioconvert1);
+
+  volume = gst_element_factory_make ("volume", NULL);
+  if (volume == NULL)
+    goto error;
+
+  gst_bin_add (GST_BIN (bin), volume);
+
+  sink = gst_element_factory_make ("gconfaudiosink", NULL);
+  if (sink == NULL)
+    goto error;
+
+  gst_bin_add (GST_BIN (bin), sink);
+  fs_element_added_notifier_add (self->priv->notifier, GST_BIN (sink));
+
+  if (!gst_element_link_many (audioconvert0, resample, audioconvert1,
+      volume, sink, NULL))
+    goto error;
+
+  filterpad = gst_element_get_static_pad (audioconvert0, "sink");
+
+  if (filterpad == NULL)
+    goto error;
+
+  subpad = gst_ghost_pad_new ("sink", filterpad);
+  if (!gst_element_add_pad (GST_ELEMENT (bin), subpad))
+    goto error;
+
+
+  /* Ensure that state changes only happen _after_ the element has been added
+   * to the hash table. But add it to the bin first so we can create our
+   * ghostpad (if we create the ghostpad before adding it to the bin it will
+   * get unlinked) */
+  gst_element_set_locked_state (GST_ELEMENT (bin), TRUE);
+  gst_bin_add (GST_BIN (self), bin);
+
+  pad = gst_ghost_pad_new (name, subpad);
+  g_assert (pad != NULL);
+
+  audiobin = audio_bin_new (pad, bin, volume, sink);
+
+  g_mutex_lock (self->priv->audio_bins_lock);
+  g_hash_table_insert (self->priv->audio_bins, pad, audiobin);
+  g_mutex_unlock (self->priv->audio_bins_lock);
+
+  gst_element_set_locked_state (GST_ELEMENT (bin), FALSE);
+
+  if (!gst_element_sync_state_with_parent (bin))
+    goto error;
+
+  if (!gst_pad_set_active (pad, TRUE))
+    goto error;
+
+  if (!gst_element_add_pad (GST_ELEMENT (self), pad))
+    goto error;
+
+
+  return pad;
+
+error:
+  if (pad != NULL)
+    {
+      g_mutex_lock (self->priv->audio_bins_lock);
+      g_hash_table_remove (self->priv->audio_bins, pad);
+      g_mutex_unlock (self->priv->audio_bins_lock);
+
+      gst_object_unref (pad);
+    }
+
+  gst_object_unref (bin);
+  g_warning ("Failed to create output subpipeline");
+  return NULL;
+}
+
+static void
+empathy_audio_sink_release_pad (GstElement *element,
+  GstPad *pad)
+{
+  EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element);
+  AudioBin *abin;
+
+  g_mutex_lock (self->priv->audio_bins_lock);
+  abin = g_hash_table_lookup (self->priv->audio_bins, pad);
+  g_hash_table_steal (self->priv->audio_bins, pad);
+  g_mutex_unlock (self->priv->audio_bins_lock);
+
+  if (abin == NULL)
+    {
+      g_warning ("Releasing a pad that doesn't belong to us ?");
+      return;
+    }
+
+  gst_pad_set_active (pad, FALSE);
+  gst_element_remove_pad (element, pad);
 
-  g_object_get (G_OBJECT (priv->volume), "volume", &volume, NULL);
+  gst_element_set_locked_state (abin->bin, TRUE);
+  gst_element_set_state (abin->bin, GST_STATE_NULL);
+  gst_bin_remove (GST_BIN (self), abin->bin);
 
-  return volume;
+  audio_bin_free (abin);
 }
index 21ebf2b5addd76bd710c1f264073cf735a4030b0..cc21fc4675d7ae691b79f4ef343577127f3c651b 100644 (file)
@@ -28,6 +28,7 @@ G_BEGIN_DECLS
 
 typedef struct _EmpathyGstAudioSink EmpathyGstAudioSink;
 typedef struct _EmpathyGstAudioSinkClass EmpathyGstAudioSinkClass;
+typedef struct _EmpathyGstAudioSinkPrivate EmpathyGstAudioSinkPrivate;
 
 struct _EmpathyGstAudioSinkClass {
     GstBinClass parent_class;
@@ -35,6 +36,7 @@ struct _EmpathyGstAudioSinkClass {
 
 struct _EmpathyGstAudioSink {
     GstBin parent;
+    EmpathyGstAudioSinkPrivate *priv;
 };
 
 GType empathy_audio_sink_get_type (void);
diff --git a/src/empathy-call-factory.c b/src/empathy-call-factory.c
new file mode 100644 (file)
index 0000000..46ef01a
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * empathy-call-factory.c - Source for EmpathyCallFactory
+ * Copyright (C) 2008 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <telepathy-glib/account-channel-request.h>
+#include <telepathy-glib/simple-handler.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#include <telepathy-yell/telepathy-yell.h>
+
+#include <libempathy/empathy-channel-factory.h>
+#include <libempathy/empathy-request-util.h>
+#include <libempathy/empathy-tp-contact-factory.h>
+#include <libempathy/empathy-utils.h>
+
+#include "empathy-call-factory.h"
+#include "empathy-call-handler.h"
+#include "src-marshal.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
+#include <libempathy/empathy-debug.h>
+
+G_DEFINE_TYPE(EmpathyCallFactory, empathy_call_factory, G_TYPE_OBJECT)
+
+static void handle_channels_cb (TpSimpleHandler *handler,
+    TpAccount *account,
+    TpConnection *connection,
+    GList *channels,
+    GList *requests_satisfied,
+    gint64 user_action_time,
+    TpHandleChannelsContext *context,
+    gpointer user_data);
+
+/* signal enum */
+enum
+{
+    NEW_CALL_HANDLER,
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* private structure */
+typedef struct {
+  TpBaseClient *handler;
+  gboolean dispose_has_run;
+} EmpathyCallFactoryPriv;
+
+#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyCallFactory)
+
+static GObject *call_factory = NULL;
+
+static void
+empathy_call_factory_init (EmpathyCallFactory *obj)
+{
+  EmpathyCallFactoryPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (obj,
+    EMPATHY_TYPE_CALL_FACTORY, EmpathyCallFactoryPriv);
+  TpDBusDaemon *dbus;
+  EmpathyChannelFactory *factory;
+  GError *error = NULL;
+
+  obj->priv = priv;
+
+  dbus = tp_dbus_daemon_dup (&error);
+  if (dbus == NULL)
+    {
+      g_warning ("Failed to get TpDBusDaemon: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  priv->handler = tp_simple_handler_new (dbus, FALSE, FALSE,
+      EMPATHY_CALL_BUS_NAME_SUFFIX, FALSE, handle_channels_cb, obj, NULL);
+
+  factory = empathy_channel_factory_new ();
+  tp_base_client_set_channel_factory (priv->handler,
+      TP_CLIENT_CHANNEL_FACTORY (factory));
+  g_object_unref (factory);
+
+  tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
+        TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+          TPY_IFACE_CHANNEL_TYPE_CALL,
+        TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+          G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+        NULL));
+
+  tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
+        TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+          TPY_IFACE_CHANNEL_TYPE_CALL,
+        TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+          G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+        TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN, TRUE,
+        NULL));
+
+  tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
+        TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+          TPY_IFACE_CHANNEL_TYPE_CALL,
+        TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+          G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+        TPY_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, G_TYPE_BOOLEAN, TRUE,
+        NULL));
+
+  tp_base_client_add_handler_capabilities_varargs (priv->handler,
+    "org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/ice-udp",
+    "org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/gtalk-p2p",
+    "org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/video/h264",
+    NULL);
+
+  g_object_unref (dbus);
+}
+
+static GObject *
+empathy_call_factory_constructor (GType type, guint n_construct_params,
+  GObjectConstructParam *construct_params)
+{
+  g_return_val_if_fail (call_factory == NULL, NULL);
+
+  call_factory = G_OBJECT_CLASS (empathy_call_factory_parent_class)->constructor
+          (type, n_construct_params, construct_params);
+  g_object_add_weak_pointer (call_factory, (gpointer)&call_factory);
+
+  return call_factory;
+}
+
+static void
+empathy_call_factory_finalize (GObject *object)
+{
+  /* free any data held directly by the object here */
+
+  if (G_OBJECT_CLASS (empathy_call_factory_parent_class)->finalize)
+    G_OBJECT_CLASS (empathy_call_factory_parent_class)->finalize (object);
+}
+
+static void
+empathy_call_factory_dispose (GObject *object)
+{
+  EmpathyCallFactoryPriv *priv = GET_PRIV (object);
+
+  if (priv->dispose_has_run)
+    return;
+
+  priv->dispose_has_run = TRUE;
+
+  tp_clear_object (&priv->handler);
+
+  if (G_OBJECT_CLASS (empathy_call_factory_parent_class)->dispose)
+    G_OBJECT_CLASS (empathy_call_factory_parent_class)->dispose (object);
+}
+
+static void
+empathy_call_factory_class_init (
+  EmpathyCallFactoryClass *empathy_call_factory_class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_factory_class);
+
+  g_type_class_add_private (empathy_call_factory_class,
+    sizeof (EmpathyCallFactoryPriv));
+
+  object_class->constructor = empathy_call_factory_constructor;
+  object_class->dispose = empathy_call_factory_dispose;
+  object_class->finalize = empathy_call_factory_finalize;
+
+  signals[NEW_CALL_HANDLER] =
+    g_signal_new ("new-call-handler",
+      G_TYPE_FROM_CLASS (empathy_call_factory_class),
+      G_SIGNAL_RUN_LAST, 0,
+      NULL, NULL,
+      _src_marshal_VOID__OBJECT_BOOLEAN,
+      G_TYPE_NONE,
+      2, EMPATHY_TYPE_CALL_HANDLER, G_TYPE_BOOLEAN);
+}
+
+EmpathyCallFactory *
+empathy_call_factory_initialise (void)
+{
+  g_return_val_if_fail (call_factory == NULL, NULL);
+
+  return EMPATHY_CALL_FACTORY (g_object_new (EMPATHY_TYPE_CALL_FACTORY, NULL));
+}
+
+EmpathyCallFactory *
+empathy_call_factory_get (void)
+{
+  g_return_val_if_fail (call_factory != NULL, NULL);
+
+  return EMPATHY_CALL_FACTORY (call_factory);
+}
+
+static void
+call_channel_got_contact (TpConnection *connection,
+  EmpathyContact *contact,
+  const GError *error,
+  gpointer user_data,
+  GObject *weak_object)
+{
+  EmpathyCallFactory *factory = EMPATHY_CALL_FACTORY (weak_object);
+  EmpathyCallHandler *handler;
+  TpyCallChannel *call = TPY_CALL_CHANNEL (user_data);
+
+  if (contact == NULL)
+    {
+      /* FIXME use hangup with an appropriate error */
+      tp_channel_close_async (TP_CHANNEL (call), NULL, NULL);
+      return;
+    }
+
+  handler = empathy_call_handler_new_for_channel (call, contact);
+
+  g_signal_emit (factory, signals[NEW_CALL_HANDLER], 0,
+    handler, FALSE);
+
+  g_object_unref (handler);
+}
+
+static void
+call_channel_ready (EmpathyCallFactory *factory,
+  TpyCallChannel *call)
+{
+  TpChannel *channel = TP_CHANNEL (call);
+  const gchar *id;
+
+  id = tp_channel_get_identifier (channel);
+
+  /* The ready callback has a reference, so pass that on */
+  empathy_tp_contact_factory_get_from_id (
+    tp_channel_borrow_connection (channel),
+    id,
+    call_channel_got_contact,
+    channel,
+    g_object_unref,
+    (GObject *) factory);
+}
+
+static void
+call_channel_ready_cb (TpyCallChannel *call,
+  GParamSpec *spec,
+  EmpathyCallFactory *factory)
+{
+  gboolean ready;
+
+  g_object_get (call, "ready", &ready, NULL);
+  if (!ready)
+    return;
+
+  call_channel_ready (factory, call);
+}
+
+
+static void
+handle_channels_cb (TpSimpleHandler *handler,
+    TpAccount *account,
+    TpConnection *connection,
+    GList *channels,
+    GList *requests_satisfied,
+    gint64 user_action_time,
+    TpHandleChannelsContext *context,
+    gpointer user_data)
+{
+  EmpathyCallFactory *self = user_data;
+  GList *l;
+
+  for (l = channels; l != NULL; l = g_list_next (l))
+    {
+      TpChannel *channel = l->data;
+      TpyCallChannel *call;
+      gboolean ready;
+
+      if (tp_proxy_get_invalidated (channel) != NULL)
+        continue;
+
+      if (tp_channel_get_channel_type_id (channel) !=
+          TPY_IFACE_QUARK_CHANNEL_TYPE_CALL)
+        continue;
+
+      if (!TPY_IS_CALL_CHANNEL (channel))
+        continue;
+
+      call = TPY_CALL_CHANNEL (channel);
+
+      /* Take a ref to keep while hopping through the async callbacks */
+      g_object_ref (call);
+      g_object_get (call, "ready", &ready, NULL);
+
+      if (!ready)
+        tp_g_signal_connect_object (call, "notify::ready",
+          G_CALLBACK (call_channel_ready_cb), self, 0);
+      else
+        call_channel_ready (self, call);
+    }
+
+  tp_handle_channels_context_accept (context);
+}
+
+gboolean
+empathy_call_factory_register (EmpathyCallFactory *self,
+    GError **error)
+{
+  EmpathyCallFactoryPriv *priv = GET_PRIV (self);
+
+  return tp_base_client_register (priv->handler, error);
+}
diff --git a/src/empathy-call-factory.h b/src/empathy-call-factory.h
new file mode 100644 (file)
index 0000000..0e52762
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * empathy-call-factory.h - Header for EmpathyCallFactory
+ * Copyright (C) 2008 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __EMPATHY_CALL_FACTORY_H__
+#define __EMPATHY_CALL_FACTORY_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _EmpathyCallFactory EmpathyCallFactory;
+typedef struct _EmpathyCallFactoryClass EmpathyCallFactoryClass;
+
+struct _EmpathyCallFactoryClass {
+    GObjectClass parent_class;
+};
+
+struct _EmpathyCallFactory {
+    GObject parent;
+    gpointer priv;
+};
+
+GType empathy_call_factory_get_type (void);
+
+/* TYPE MACROS */
+#define EMPATHY_TYPE_CALL_FACTORY \
+  (empathy_call_factory_get_type ())
+#define EMPATHY_CALL_FACTORY(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), EMPATHY_TYPE_CALL_FACTORY, \
+    EmpathyCallFactory))
+#define EMPATHY_CALL_FACTORY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), EMPATHY_TYPE_CALL_FACTORY, \
+    EmpathyCallFactoryClass))
+#define EMPATHY_IS_CALL_FACTORY(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EMPATHY_TYPE_CALL_FACTORY))
+#define EMPATHY_IS_CALL_FACTORY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), EMPATHY_TYPE_CALL_FACTORY))
+#define EMPATHY_CALL_FACTORY_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_CALL_FACTORY, \
+    EmpathyCallFactoryClass))
+
+
+EmpathyCallFactory *empathy_call_factory_initialise (void);
+
+EmpathyCallFactory *empathy_call_factory_get (void);
+
+gboolean empathy_call_factory_register (EmpathyCallFactory *self,
+    GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __EMPATHY_CALL_FACTORY_H__*/
diff --git a/src/empathy-call-handler.c b/src/empathy-call-handler.c
new file mode 100644 (file)
index 0000000..1db5a34
--- /dev/null
@@ -0,0 +1,1087 @@
+/*
+ * empathy-call-handler.c - Source for EmpathyCallHandler
+ * Copyright (C) 2008-2009 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <telepathy-glib/account-channel-request.h>
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/interfaces.h>
+
+#include <telepathy-yell/telepathy-yell.h>
+
+#include <telepathy-farstream/telepathy-farstream.h>
+
+#include <libempathy/empathy-channel-factory.h>
+#include <libempathy/empathy-utils.h>
+#include <libempathy/empathy-tp-contact-factory.h>
+
+#include <libempathy-gtk/empathy-call-utils.h>
+
+#include "empathy-call-handler.h"
+#include "src-marshal.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
+#include <libempathy/empathy-debug.h>
+
+G_DEFINE_TYPE(EmpathyCallHandler, empathy_call_handler, G_TYPE_OBJECT)
+
+/* signal enum */
+enum {
+  CONFERENCE_ADDED,
+  CONFERENCE_REMOVED,
+  SRC_PAD_ADDED,
+  SINK_PAD_ADDED,
+  SINK_PAD_REMOVED,
+  CLOSED,
+  CANDIDATES_CHANGED,
+  STATE_CHANGED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+enum {
+  PROP_CALL_CHANNEL = 1,
+  PROP_GST_BUS,
+  PROP_CONTACT,
+  PROP_MEMBERS,
+  PROP_INITIAL_AUDIO,
+  PROP_INITIAL_VIDEO,
+  PROP_SEND_AUDIO_CODEC,
+  PROP_SEND_VIDEO_CODEC,
+  PROP_RECV_AUDIO_CODECS,
+  PROP_RECV_VIDEO_CODECS,
+  PROP_AUDIO_REMOTE_CANDIDATE,
+  PROP_VIDEO_REMOTE_CANDIDATE,
+  PROP_AUDIO_LOCAL_CANDIDATE,
+  PROP_VIDEO_LOCAL_CANDIDATE,
+};
+
+/* private structure */
+
+struct _EmpathyCallHandlerPriv {
+  TpyCallChannel *call;
+
+  EmpathyContact *contact;
+  /* GArray of TpContacts */
+  GArray *members;
+  TfChannel *tfchannel;
+  gboolean initial_audio;
+  gboolean initial_video;
+
+  FsCodec *send_audio_codec;
+  FsCodec *send_video_codec;
+  GList *recv_audio_codecs;
+  GList *recv_video_codecs;
+  FsCandidate *audio_remote_candidate;
+  FsCandidate *video_remote_candidate;
+  FsCandidate *audio_local_candidate;
+  FsCandidate *video_local_candidate;
+};
+
+#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyCallHandler)
+
+static void
+empathy_call_handler_dispose (GObject *object)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (object);
+
+  tp_clear_object (&priv->tfchannel);
+  tp_clear_object (&priv->call);
+  tp_clear_object (&priv->contact);
+
+  tp_clear_pointer (&priv->members, g_array_unref);
+
+  G_OBJECT_CLASS (empathy_call_handler_parent_class)->dispose (object);
+}
+
+static void
+empathy_call_handler_finalize (GObject *object)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (object);
+
+  fs_codec_destroy (priv->send_audio_codec);
+  fs_codec_destroy (priv->send_video_codec);
+  fs_codec_list_destroy (priv->recv_audio_codecs);
+  fs_codec_list_destroy (priv->recv_video_codecs);
+  fs_candidate_destroy (priv->audio_remote_candidate);
+  fs_candidate_destroy (priv->video_remote_candidate);
+  fs_candidate_destroy (priv->audio_local_candidate);
+  fs_candidate_destroy (priv->video_local_candidate);
+
+  G_OBJECT_CLASS (empathy_call_handler_parent_class)->finalize (object);
+}
+
+static void
+empathy_call_handler_init (EmpathyCallHandler *obj)
+{
+  EmpathyCallHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (obj,
+    EMPATHY_TYPE_CALL_HANDLER, EmpathyCallHandlerPriv);
+
+  obj->priv = priv;
+}
+
+static void
+on_get_contacts_cb (TpConnection *connection,
+    guint n_contacts,
+    EmpathyContact * const * contacts,
+    guint n_failed,
+    const TpHandle *failed,
+    const GError *error,
+    gpointer user_data,
+    GObject *weak_object)
+{
+  EmpathyCallHandler *self = EMPATHY_CALL_HANDLER (weak_object);
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+  guint i;
+
+  if (n_failed > 0)
+    g_warning ("Failed to get %d EmpathyContacts: %s",
+        n_failed, error->message);
+
+  priv->members = g_array_sized_new (FALSE, TRUE,
+      sizeof (EmpathyContact *), n_contacts);
+
+  for (i = 0; i < n_contacts; i++)
+    g_object_ref (contacts[i]);
+
+  g_array_append_vals (priv->members, contacts, n_contacts);
+
+  g_object_notify (G_OBJECT (self), "members");
+}
+
+static void
+on_call_invalidated_cb (TpyCallChannel *call,
+    guint domain,
+    gint code,
+    gchar *message,
+    EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = self->priv;
+
+  if (priv->call == call)
+    {
+      /* Invalidated unexpectedly? Fake call ending */
+      g_signal_emit (self, signals[STATE_CHANGED], 0,
+          TPY_CALL_STATE_ENDED, NULL);
+      tp_clear_object (&priv->call);
+      tp_clear_object (&priv->tfchannel);
+    }
+}
+
+static void
+on_call_state_changed_cb (TpyCallChannel *call,
+  TpyCallState state,
+  TpyCallFlags flags,
+   const GValueArray *call_state_reason,
+  GHashTable *call_state_details,
+  EmpathyCallHandler *handler)
+{
+  EmpathyCallHandlerPriv *priv = handler->priv;
+
+  g_signal_emit (handler, signals[STATE_CHANGED], 0, state);
+
+  if (state == TPY_CALL_STATE_ENDED)
+    {
+      tp_channel_close_async (TP_CHANNEL (call), NULL, NULL);
+
+      tp_clear_object (&priv->call);
+      tp_clear_object (&priv->tfchannel);
+    }
+}
+
+static void
+on_members_changed_cb (TpyCallChannel *call,
+    GHashTable *members,
+    EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+  GHashTableIter iter;
+  gpointer key, value;
+  TpHandle *handles;
+  guint n_handles;
+  guint i = 0;
+
+  if (members == NULL)
+    return;
+
+  n_handles = g_hash_table_size (members);
+  if (n_handles == 0)
+    return;
+
+  handles = g_new0 (TpHandle, n_handles);
+
+  g_hash_table_iter_init (&iter, members);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    handles[i++] = GPOINTER_TO_UINT (key);
+
+  empathy_tp_contact_factory_get_from_handles (
+      tp_channel_borrow_connection (TP_CHANNEL (priv->call)),
+      n_handles, handles,
+      on_get_contacts_cb,
+      NULL, NULL, G_OBJECT (self));
+
+  g_free (handles);
+}
+
+static void
+empathy_call_handler_constructed (GObject *object)
+{
+  EmpathyCallHandler *self = EMPATHY_CALL_HANDLER (object);
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+//  GHashTable *members;
+
+  g_signal_connect (priv->call, "members-changed",
+      G_CALLBACK (on_members_changed_cb), object);
+
+/* FIXME
+  g_object_get (priv->call, "members", &members, NULL);
+
+  if (members)
+    on_members_changed_cb (priv->call, members, self);
+*/
+}
+
+static void
+empathy_call_handler_set_property (GObject *object,
+  guint property_id, const GValue *value, GParamSpec *pspec)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (object);
+
+  switch (property_id)
+    {
+      case PROP_CONTACT:
+        priv->contact = g_value_dup_object (value);
+        break;
+      case PROP_MEMBERS:
+        priv->members = g_value_get_boxed (value);
+        break;
+      case PROP_CALL_CHANNEL:
+        g_return_if_fail (priv->call == NULL);
+
+        priv->call = g_value_dup_object (value);
+
+        tp_g_signal_connect_object (priv->call, "state-changed",
+          G_CALLBACK (on_call_state_changed_cb), object, 0);
+        tp_g_signal_connect_object (priv->call, "invalidated",
+          G_CALLBACK (on_call_invalidated_cb), object, 0);
+        break;
+      case PROP_INITIAL_AUDIO:
+        priv->initial_audio = g_value_get_boolean (value);
+        break;
+      case PROP_INITIAL_VIDEO:
+        priv->initial_video = g_value_get_boolean (value);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+empathy_call_handler_get_property (GObject *object,
+  guint property_id, GValue *value, GParamSpec *pspec)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (object);
+
+  switch (property_id)
+    {
+      case PROP_CONTACT:
+        g_value_set_object (value, priv->contact);
+        break;
+      case PROP_MEMBERS:
+        g_value_set_boxed (value, priv->members);
+        break;
+      case PROP_CALL_CHANNEL:
+        g_value_set_object (value, priv->call);
+        break;
+      case PROP_INITIAL_AUDIO:
+        g_value_set_boolean (value, priv->initial_audio);
+        break;
+      case PROP_INITIAL_VIDEO:
+        g_value_set_boolean (value, priv->initial_video);
+        break;
+      case PROP_SEND_AUDIO_CODEC:
+        g_value_set_boxed (value, priv->send_audio_codec);
+        break;
+      case PROP_SEND_VIDEO_CODEC:
+        g_value_set_boxed (value, priv->send_video_codec);
+        break;
+      case PROP_RECV_AUDIO_CODECS:
+        g_value_set_boxed (value, priv->recv_audio_codecs);
+        break;
+      case PROP_RECV_VIDEO_CODECS:
+        g_value_set_boxed (value, priv->recv_video_codecs);
+        break;
+      case PROP_AUDIO_REMOTE_CANDIDATE:
+        g_value_set_boxed (value, priv->audio_remote_candidate);
+        break;
+      case PROP_VIDEO_REMOTE_CANDIDATE:
+        g_value_set_boxed (value, priv->video_remote_candidate);
+        break;
+      case PROP_AUDIO_LOCAL_CANDIDATE:
+        g_value_set_boxed (value, priv->audio_local_candidate);
+        break;
+      case PROP_VIDEO_LOCAL_CANDIDATE:
+        g_value_set_boxed (value, priv->video_local_candidate);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+
+static void
+empathy_call_handler_class_init (EmpathyCallHandlerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GParamSpec *param_spec;
+
+  g_type_class_add_private (klass, sizeof (EmpathyCallHandlerPriv));
+
+  object_class->constructed = empathy_call_handler_constructed;
+  object_class->set_property = empathy_call_handler_set_property;
+  object_class->get_property = empathy_call_handler_get_property;
+  object_class->dispose = empathy_call_handler_dispose;
+  object_class->finalize = empathy_call_handler_finalize;
+
+  param_spec = g_param_spec_object ("target-contact",
+    "TargetContact", "The contact",
+    EMPATHY_TYPE_CONTACT,
+    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
+
+  param_spec = g_param_spec_boxed ("members",
+    "call members", "The call participants",
+    G_TYPE_ARRAY,
+    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_MEMBERS, param_spec);
+
+  param_spec = g_param_spec_object ("call-channel",
+    "call channel", "The call channel",
+    TPY_TYPE_CALL_CHANNEL,
+    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_CALL_CHANNEL, param_spec);
+
+  param_spec = g_param_spec_boolean ("initial-audio",
+    "initial-audio", "Whether the call should start with audio",
+    TRUE,
+    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_INITIAL_AUDIO,
+      param_spec);
+
+  param_spec = g_param_spec_boolean ("initial-video",
+    "initial-video", "Whether the call should start with video",
+    FALSE,
+    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_INITIAL_VIDEO,
+    param_spec);
+
+  param_spec = g_param_spec_boxed ("send-audio-codec",
+    "send audio codec", "Codec used to encode the outgoing video stream",
+    FS_TYPE_CODEC,
+    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_SEND_AUDIO_CODEC,
+    param_spec);
+
+  param_spec = g_param_spec_boxed ("send-video-codec",
+    "send video codec", "Codec used to encode the outgoing video stream",
+    FS_TYPE_CODEC,
+    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_SEND_VIDEO_CODEC,
+    param_spec);
+
+  param_spec = g_param_spec_boxed ("recv-audio-codecs",
+    "recvs audio codec", "Codecs used to decode the incoming audio stream",
+    FS_TYPE_CODEC_LIST,
+    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_RECV_AUDIO_CODECS,
+    param_spec);
+
+  param_spec = g_param_spec_boxed ("recv-video-codecs",
+    "recvs video codec", "Codecs used to decode the incoming video stream",
+    FS_TYPE_CODEC_LIST,
+    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_RECV_VIDEO_CODECS,
+    param_spec);
+
+  param_spec = g_param_spec_boxed ("audio-remote-candidate",
+    "audio remote candidate",
+    "Remote candidate used for the audio stream",
+    FS_TYPE_CANDIDATE,
+    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+      PROP_AUDIO_REMOTE_CANDIDATE, param_spec);
+
+  param_spec = g_param_spec_boxed ("video-remote-candidate",
+    "video remote candidate",
+    "Remote candidate used for the video stream",
+    FS_TYPE_CANDIDATE,
+    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+      PROP_VIDEO_REMOTE_CANDIDATE, param_spec);
+
+  param_spec = g_param_spec_boxed ("audio-local-candidate",
+    "audio local candidate",
+    "Local candidate used for the audio stream",
+    FS_TYPE_CANDIDATE,
+    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+      PROP_AUDIO_REMOTE_CANDIDATE, param_spec);
+
+  param_spec = g_param_spec_boxed ("video-local-candidate",
+    "video local candidate",
+    "Local candidate used for the video stream",
+    FS_TYPE_CANDIDATE,
+    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+      PROP_VIDEO_REMOTE_CANDIDATE, param_spec);
+
+  signals[CONFERENCE_ADDED] =
+    g_signal_new ("conference-added", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+      g_cclosure_marshal_VOID__OBJECT,
+      G_TYPE_NONE,
+      1, FS_TYPE_CONFERENCE);
+
+  signals[CONFERENCE_REMOVED] =
+    g_signal_new ("conference-removed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+      g_cclosure_marshal_VOID__OBJECT,
+      G_TYPE_NONE,
+      1, FS_TYPE_CONFERENCE);
+
+  signals[SRC_PAD_ADDED] =
+    g_signal_new ("src-pad-added", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+      _src_marshal_BOOLEAN__OBJECT_UINT,
+      G_TYPE_BOOLEAN,
+      2, GST_TYPE_PAD, G_TYPE_UINT);
+
+  signals[SINK_PAD_ADDED] =
+    g_signal_new ("sink-pad-added", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+      _src_marshal_BOOLEAN__OBJECT_UINT,
+      G_TYPE_BOOLEAN,
+      2, GST_TYPE_PAD, G_TYPE_UINT);
+
+  signals[SINK_PAD_REMOVED] =
+    g_signal_new ("sink-pad-removed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+      _src_marshal_BOOLEAN__OBJECT_UINT,
+      G_TYPE_BOOLEAN,
+      2, GST_TYPE_PAD, G_TYPE_UINT);
+
+  signals[CLOSED] =
+    g_signal_new ("closed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+      g_cclosure_marshal_VOID__VOID,
+      G_TYPE_NONE,
+      0);
+
+  signals[CANDIDATES_CHANGED] =
+    g_signal_new ("candidates-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+      g_cclosure_marshal_VOID__UINT,
+      G_TYPE_NONE, 1, G_TYPE_UINT);
+
+  signals[STATE_CHANGED] =
+    g_signal_new ("state-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+      g_cclosure_marshal_VOID__UINT,
+      G_TYPE_NONE, 1, G_TYPE_UINT);
+}
+
+EmpathyCallHandler *
+empathy_call_handler_new_for_channel (TpyCallChannel *call,
+  EmpathyContact *contact)
+{
+  return EMPATHY_CALL_HANDLER (g_object_new (EMPATHY_TYPE_CALL_HANDLER,
+    "call-channel", call,
+    "initial-video", tpy_call_channel_has_initial_video (call),
+    "target-contact", contact,
+    NULL));
+}
+
+static void
+update_sending_codec (EmpathyCallHandler *self,
+    FsCodec *codec,
+    FsSession *session)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+  FsMediaType type;
+
+  if (codec == NULL || session == NULL)
+    return;
+
+  g_object_get (session, "media-type", &type, NULL);
+
+  if (type == FS_MEDIA_TYPE_AUDIO)
+    {
+      priv->send_audio_codec = fs_codec_copy (codec);
+      g_object_notify (G_OBJECT (self), "send-audio-codec");
+    }
+  else if (type == FS_MEDIA_TYPE_VIDEO)
+    {
+      priv->send_video_codec = fs_codec_copy (codec);
+      g_object_notify (G_OBJECT (self), "send-video-codec");
+    }
+}
+
+static void
+update_receiving_codec (EmpathyCallHandler *self,
+    GList *codecs,
+    FsStream *stream)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+  FsSession *session;
+  FsMediaType type;
+
+  if (codecs == NULL || stream == NULL)
+    return;
+
+  g_object_get (stream, "session", &session, NULL);
+  if (session == NULL)
+    return;
+
+  g_object_get (session, "media-type", &type, NULL);
+
+  if (type == FS_MEDIA_TYPE_AUDIO)
+    {
+      priv->recv_audio_codecs = fs_codec_list_copy (codecs);
+      g_object_notify (G_OBJECT (self), "recv-audio-codecs");
+    }
+  else if (type == FS_MEDIA_TYPE_VIDEO)
+    {
+      priv->recv_video_codecs = fs_codec_list_copy (codecs);
+      g_object_notify (G_OBJECT (self), "recv-video-codecs");
+    }
+
+  g_object_unref (session);
+}
+
+static void
+update_candidates (EmpathyCallHandler *self,
+    FsCandidate *remote_candidate,
+    FsCandidate *local_candidate,
+    FsStream *stream)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+  FsSession *session;
+  FsMediaType type;
+
+  if (stream == NULL)
+    return;
+
+  g_object_get (stream, "session", &session, NULL);
+  if (session == NULL)
+    return;
+
+  g_object_get (session, "media-type", &type, NULL);
+
+  if (type == FS_MEDIA_TYPE_AUDIO)
+    {
+      if (remote_candidate != NULL)
+        {
+          fs_candidate_destroy (priv->audio_remote_candidate);
+          priv->audio_remote_candidate = fs_candidate_copy (remote_candidate);
+          g_object_notify (G_OBJECT (self), "audio-remote-candidate");
+        }
+
+      if (local_candidate != NULL)
+        {
+          fs_candidate_destroy (priv->audio_local_candidate);
+          priv->audio_local_candidate = fs_candidate_copy (local_candidate);
+          g_object_notify (G_OBJECT (self), "audio-local-candidate");
+        }
+
+      g_signal_emit (G_OBJECT (self), signals[CANDIDATES_CHANGED], 0,
+          FS_MEDIA_TYPE_AUDIO);
+    }
+  else if (type == FS_MEDIA_TYPE_VIDEO)
+    {
+      if (remote_candidate != NULL)
+        {
+          fs_candidate_destroy (priv->video_remote_candidate);
+          priv->video_remote_candidate = fs_candidate_copy (remote_candidate);
+          g_object_notify (G_OBJECT (self), "video-remote-candidate");
+        }
+
+      if (local_candidate != NULL)
+        {
+          fs_candidate_destroy (priv->video_local_candidate);
+          priv->video_local_candidate = fs_candidate_copy (local_candidate);
+          g_object_notify (G_OBJECT (self), "video-local-candidate");
+        }
+
+      g_signal_emit (G_OBJECT (self), signals[CANDIDATES_CHANGED], 0,
+          FS_MEDIA_TYPE_VIDEO);
+    }
+
+  g_object_unref (session);
+}
+
+void
+empathy_call_handler_bus_message (EmpathyCallHandler *handler,
+  GstBus *bus, GstMessage *message)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
+  const GstStructure *s = gst_message_get_structure (message);
+
+  if (priv->tfchannel == NULL)
+    return;
+
+  if (s != NULL &&
+      gst_structure_has_name (s, "farsight-send-codec-changed"))
+    {
+      const GValue *val;
+      FsCodec *codec;
+      FsSession *session;
+
+      DEBUG ("farsight-send-codec-changed");
+
+      val = gst_structure_get_value (s, "codec");
+      codec = g_value_get_boxed (val);
+
+      val = gst_structure_get_value (s, "session");
+      session = g_value_get_object (val);
+
+      update_sending_codec (handler, codec, session);
+    }
+  else if (s != NULL &&
+      gst_structure_has_name (s, "farsight-recv-codecs-changed"))
+    {
+      const GValue *val;
+      GList *codecs;
+      FsStream *stream;
+
+      DEBUG ("farsight-recv-codecs-changed");
+
+      val = gst_structure_get_value (s, "codecs");
+      codecs = g_value_get_boxed (val);
+
+      val = gst_structure_get_value (s, "stream");
+      stream = g_value_get_object (val);
+
+      update_receiving_codec (handler, codecs, stream);
+    }
+  else if (s != NULL &&
+      gst_structure_has_name (s, "farsight-new-active-candidate-pair"))
+    {
+      const GValue *val;
+      FsCandidate *remote_candidate, *local_candidate;
+      FsStream *stream;
+
+      DEBUG ("farsight-new-active-candidate-pair");
+
+      val = gst_structure_get_value (s, "remote-candidate");
+      remote_candidate = g_value_get_boxed (val);
+
+      val = gst_structure_get_value (s, "local-candidate");
+      local_candidate = g_value_get_boxed (val);
+
+      val = gst_structure_get_value (s, "stream");
+      stream = g_value_get_object (val);
+
+      update_candidates (handler, remote_candidate, local_candidate, stream);
+    }
+
+  tf_channel_bus_message (priv->tfchannel, message);
+}
+
+static void
+on_tf_channel_conference_added_cb (TfChannel *tfchannel,
+  GstElement *conference,
+  EmpathyCallHandler *self)
+{
+  g_signal_emit (G_OBJECT (self), signals[CONFERENCE_ADDED], 0,
+    conference);
+}
+
+static void
+on_tf_channel_conference_removed_cb (TfChannel *tfchannel,
+  FsConference *conference,
+  EmpathyCallHandler *self)
+{
+  g_signal_emit (G_OBJECT (self), signals[CONFERENCE_REMOVED], 0,
+    GST_ELEMENT (conference));
+}
+
+static gboolean
+src_pad_added_error_idle (gpointer data)
+{
+  TfContent *content = data;
+
+  tf_content_error (content, 0 /* FIXME */,
+      "Could not link sink", NULL);
+  g_object_unref (content);
+
+  return FALSE;
+}
+
+static void
+on_tf_content_src_pad_added_cb (TfContent *content,
+  guint handle,
+  FsStream *stream,
+  GstPad *pad,
+  FsCodec *codec,
+  EmpathyCallHandler *handler)
+{
+  guint media_type;
+  gboolean retval;
+
+  g_object_get (content, "media-type", &media_type, NULL);
+
+  g_signal_emit (G_OBJECT (handler), signals[SRC_PAD_ADDED], 0,
+      pad, media_type, &retval);
+
+  if (!retval)
+    g_idle_add (src_pad_added_error_idle, g_object_ref (content));
+}
+
+static void
+on_tf_channel_content_added_cb (TfChannel *tfchannel,
+  TfContent *content,
+  EmpathyCallHandler *handler)
+{
+  FsMediaType mtype;
+  GstPad *spad;
+  FsSession *session;
+//  FsStream *fs_stream;
+  FsCodec *codec;
+//  GList *codecs;
+  gboolean retval;
+
+  g_signal_connect (content, "src-pad-added",
+      G_CALLBACK (on_tf_content_src_pad_added_cb), handler);
+#if 0
+  g_signal_connect (content, "start-sending",
+      G_CALLBACK (on_tf_content_start_sending_cb), handler);
+  g_signal_connect (content, "stop-sending",
+      G_CALLBACK (on_tf_content_stop_sending_cb), handler);
+#endif
+
+  g_object_get (content, "media-type", &mtype,
+    "sink-pad", &spad, NULL);
+
+  g_signal_emit (G_OBJECT (handler), signals[SINK_PAD_ADDED], 0,
+      spad, mtype, &retval);
+
+ if (!retval)
+      tf_content_error (content, 0 /* FIXME */,
+          "Could not link source", NULL);
+
+ /* Get sending codec */
+ g_object_get (content, "fs-session", &session, NULL);
+ g_object_get (session, "current-send-codec", &codec, NULL);
+
+ update_sending_codec (handler, codec, session);
+
+ tp_clear_object (&session);
+ tp_clear_object (&codec);
+
+ /* Get receiving codec */
+/* FIXME
+ g_object_get (content, "fs-stream", &fs_stream, NULL);
+ g_object_get (fs_stream, "current-recv-codecs", &codecs, NULL);
+
+ update_receiving_codec (handler, codecs, fs_stream);
+
+ fs_codec_list_destroy (codecs);
+ tp_clear_object (&fs_stream);
+*/
+
+ gst_object_unref (spad);
+}
+
+static void
+on_tf_channel_content_removed_cb (TfChannel *tfchannel,
+  TfContent *content,
+  EmpathyCallHandler *handler)
+{
+  FsMediaType mtype;
+  GstPad *spad;
+  gboolean retval;
+
+  DEBUG ("removing content");
+
+  g_object_get (content, "media-type", &mtype,
+    "sink-pad", &spad, NULL);
+
+  g_signal_emit (G_OBJECT (handler), signals[SINK_PAD_REMOVED], 0,
+      spad, mtype, &retval);
+
+  if (!retval)
+    {
+      g_warning ("Could not remove content!");
+
+      tf_content_error (content, 0 /* FIXME */,
+          "Could not link source", NULL);
+    }
+}
+
+static void
+on_tf_channel_closed_cb (TfChannel *tfchannel,
+    EmpathyCallHandler *handler)
+{
+  g_signal_emit (G_OBJECT (handler), signals[CLOSED], 0);
+}
+
+static void
+on_tf_channel_ready (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  EmpathyCallHandler *self = EMPATHY_CALL_HANDLER (user_data);
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+  GError *error = NULL;
+
+  priv->tfchannel = TF_CHANNEL (g_async_initable_new_finish (
+      G_ASYNC_INITABLE (source), result, NULL));
+
+  if (priv->tfchannel == NULL)
+    {
+      g_warning ("Failed to create Farstream channel: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  /* Set up the telepathy farstream channel */
+  g_signal_connect (priv->tfchannel, "closed",
+      G_CALLBACK (on_tf_channel_closed_cb), self);
+  g_signal_connect (priv->tfchannel, "fs-conference-added",
+      G_CALLBACK (on_tf_channel_conference_added_cb), self);
+  g_signal_connect (priv->tfchannel, "fs-conference-removed",
+      G_CALLBACK (on_tf_channel_conference_removed_cb), self);
+  g_signal_connect (priv->tfchannel, "content-added",
+      G_CALLBACK (on_tf_channel_content_added_cb), self);
+  g_signal_connect (priv->tfchannel, "content-removed",
+      G_CALLBACK (on_tf_channel_content_removed_cb), self);
+}
+
+static void
+empathy_call_handler_start_tpfs (EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+
+  tf_channel_new_async (TP_CHANNEL (priv->call),
+      on_tf_channel_ready, self);
+}
+
+static void
+on_call_accepted_cb (GObject *source_object,
+    GAsyncResult *res,
+    gpointer user_data)
+{
+  TpyCallChannel *call = TPY_CALL_CHANNEL (source_object);
+  GError *error = NULL;
+
+  if (!tpy_call_channel_accept_finish (call, res, &error))
+    {
+      g_warning ("could not accept Call: %s", error->message);
+      g_error_free (error);
+    }
+}
+
+static void
+empathy_call_handler_request_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  EmpathyCallHandler *self = EMPATHY_CALL_HANDLER (user_data);
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+  TpChannel *channel;
+  GError *error = NULL;
+  TpAccountChannelRequest *req = TP_ACCOUNT_CHANNEL_REQUEST (source);
+
+  channel = tp_account_channel_request_create_and_handle_channel_finish (req,
+      result, NULL, &error);
+  if (channel == NULL)
+    {
+      DEBUG ("Failed to create the channel: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  if (!TPY_IS_CALL_CHANNEL (channel))
+    {
+      DEBUG ("The channel is not a Call channel!");
+      return;
+    }
+
+  priv->call = TPY_CALL_CHANNEL (channel);
+  tp_g_signal_connect_object (priv->call, "state-changed",
+    G_CALLBACK (on_call_state_changed_cb), self, 0);
+  tp_g_signal_connect_object (priv->call, "invalidated",
+    G_CALLBACK (on_call_invalidated_cb), self, 0);
+
+  g_object_notify (G_OBJECT (self), "call-channel");
+
+  empathy_call_handler_start_tpfs (self);
+  tpy_call_channel_accept_async (priv->call, on_call_accepted_cb, NULL);
+}
+
+void
+empathy_call_handler_start_call (EmpathyCallHandler *handler,
+    gint64 timestamp)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
+  EmpathyChannelFactory *channel_factory;
+  TpAccountChannelRequest *req;
+  TpAccount *account;
+  GHashTable *request;
+
+  if (priv->call != NULL)
+    {
+      empathy_call_handler_start_tpfs (handler);
+      tpy_call_channel_accept_async (priv->call, on_call_accepted_cb, NULL);
+      return;
+    }
+
+  /* No TpyCallChannel (we are redialing). Request a new call channel */
+  g_assert (priv->contact != NULL);
+
+  account = empathy_contact_get_account (priv->contact);
+  request = empathy_call_create_call_request (
+      empathy_contact_get_id (priv->contact),
+      priv->initial_audio, priv->initial_video);
+
+  req = tp_account_channel_request_new (account, request, timestamp);
+
+  channel_factory = empathy_channel_factory_dup ();
+  tp_account_channel_request_set_channel_factory (req,
+      TP_CLIENT_CHANNEL_FACTORY (channel_factory));
+  g_object_unref (channel_factory);
+
+  tp_account_channel_request_create_and_handle_channel_async (req, NULL,
+      empathy_call_handler_request_cb, handler);
+
+  g_object_unref (req);
+  g_hash_table_unref (request);
+}
+
+/**
+ * empathy_call_handler_stop_call:
+ * @handler: an #EmpathyCallHandler
+ *
+ * Closes the #EmpathyCallHandler's call and frees its resources.
+ */
+void
+empathy_call_handler_stop_call (EmpathyCallHandler *handler)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
+
+  if (priv->call != NULL)
+    {
+      tpy_call_channel_hangup_async (priv->call,
+          TPY_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+          "", "", NULL, NULL);
+      tp_channel_close_async (TP_CHANNEL (priv->call),
+        NULL, NULL);
+      tp_clear_object (&priv->call);
+      tp_clear_object (&priv->tfchannel);
+    }
+}
+
+/**
+ * empathy_call_handler_has_initial_video:
+ * @handler: an #EmpathyCallHandler
+ *
+ * Return %TRUE if the call managed by this #EmpathyCallHandler was
+ * created with video enabled
+ *
+ * Return value: %TRUE if the call was created as a video conversation.
+ */
+gboolean
+empathy_call_handler_has_initial_video (EmpathyCallHandler *handler)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
+
+  return priv->initial_video;
+}
+
+FsCodec *
+empathy_call_handler_get_send_audio_codec (EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+
+  return priv->send_audio_codec;
+}
+
+FsCodec *
+empathy_call_handler_get_send_video_codec (EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+
+  return priv->send_video_codec;
+}
+
+GList *
+empathy_call_handler_get_recv_audio_codecs (EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+
+  return priv->recv_audio_codecs;
+}
+
+GList *
+empathy_call_handler_get_recv_video_codecs (EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+
+  return priv->recv_video_codecs;
+}
+
+FsCandidate *
+empathy_call_handler_get_audio_remote_candidate (
+    EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+
+  return priv->audio_remote_candidate;
+}
+
+FsCandidate *
+empathy_call_handler_get_audio_local_candidate (
+    EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+
+  return priv->audio_local_candidate;
+}
+
+FsCandidate *
+empathy_call_handler_get_video_remote_candidate (
+    EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+
+  return priv->video_remote_candidate;
+}
+
+FsCandidate *
+empathy_call_handler_get_video_local_candidate (
+    EmpathyCallHandler *self)
+{
+  EmpathyCallHandlerPriv *priv = GET_PRIV (self);
+
+  return priv->video_local_candidate;
+}
diff --git a/src/empathy-call-handler.h b/src/empathy-call-handler.h
new file mode 100644 (file)
index 0000000..330d368
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * empathy-call-handler.h - Header for EmpathyCallHandler
+ * Copyright (C) 2008-2009 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __EMPATHY_CALL_HANDLER_H__
+#define __EMPATHY_CALL_HANDLER_H__
+
+#include <glib-object.h>
+
+#include <gst/gst.h>
+#include <gst/farsight/fs-conference-iface.h>
+
+#include <telepathy-yell/telepathy-yell.h>
+
+#include <libempathy/empathy-contact.h>
+
+G_BEGIN_DECLS
+
+typedef struct _EmpathyCallHandler EmpathyCallHandler;
+typedef struct _EmpathyCallHandlerClass EmpathyCallHandlerClass;
+typedef struct _EmpathyCallHandlerPriv EmpathyCallHandlerPriv;
+
+struct _EmpathyCallHandlerClass {
+    GObjectClass parent_class;
+};
+
+struct _EmpathyCallHandler {
+    GObject parent;
+    EmpathyCallHandlerPriv *priv;
+};
+
+GType empathy_call_handler_get_type (void);
+
+/* TYPE MACROS */
+#define EMPATHY_TYPE_CALL_HANDLER \
+  (empathy_call_handler_get_type ())
+#define EMPATHY_CALL_HANDLER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), EMPATHY_TYPE_CALL_HANDLER, \
+    EmpathyCallHandler))
+#define EMPATHY_CALL_HANDLER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), EMPATHY_TYPE_CALL_HANDLER, \
+  EmpathyCallHandlerClass))
+#define EMPATHY_IS_CALL_HANDLER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EMPATHY_TYPE_CALL_HANDLER))
+#define EMPATHY_IS_CALL_HANDLER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), EMPATHY_TYPE_CALL_HANDLER))
+#define EMPATHY_CALL_HANDLER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_CALL_HANDLER, \
+  EmpathyCallHandlerClass))
+
+EmpathyCallHandler * empathy_call_handler_new_for_contact (
+  EmpathyContact *contact);
+
+EmpathyCallHandler * empathy_call_handler_new_for_channel (
+  TpyCallChannel *call,
+  EmpathyContact *contact);
+
+void empathy_call_handler_start_call (EmpathyCallHandler *handler,
+    gint64 timestamp);
+void empathy_call_handler_stop_call (EmpathyCallHandler *handler);
+
+gboolean empathy_call_handler_has_initial_video (EmpathyCallHandler *handler);
+
+void empathy_call_handler_bus_message (EmpathyCallHandler *handler,
+  GstBus *bus, GstMessage *message);
+
+FsCodec * empathy_call_handler_get_send_audio_codec (
+    EmpathyCallHandler *self);
+
+FsCodec * empathy_call_handler_get_send_video_codec (
+    EmpathyCallHandler *self);
+
+GList * empathy_call_handler_get_recv_audio_codecs (
+    EmpathyCallHandler *self);
+
+GList * empathy_call_handler_get_recv_video_codecs (
+    EmpathyCallHandler *self);
+
+FsCandidate * empathy_call_handler_get_audio_remote_candidate (
+    EmpathyCallHandler *self);
+
+FsCandidate * empathy_call_handler_get_audio_local_candidate (
+    EmpathyCallHandler *self);
+
+FsCandidate * empathy_call_handler_get_video_remote_candidate (
+    EmpathyCallHandler *self);
+
+FsCandidate * empathy_call_handler_get_video_local_candidate (
+    EmpathyCallHandler *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __EMPATHY_CALL_HANDLER_H__*/
diff --git a/src/empathy-call-window-fullscreen.c b/src/empathy-call-window-fullscreen.c
new file mode 100644 (file)
index 0000000..1b99345
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * empathy-call-window-fullscreen.c - Source for EmpathyCallWindowFullscreen
+ * Copyright (C) 2009-2011 Collabora Ltd.
+ *
+ * Some code is based on the Totem Movie Player, especially
+ * totem-fullscreen.c which has the following copyright:
+ * Copyright (C) 2001-2007 Bastien Nocera <hadess@hadess.net>
+ * Copyright (C) 2007 Sunil Mohan Adapa <sunilmohan@gnu.org.in>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "empathy-call-window-fullscreen.h"
+
+#include <gtk/gtk.h>
+
+#include <libempathy/empathy-utils.h>
+#include <libempathy-gtk/empathy-ui-utils.h>
+
+/* The number of seconds for which the "leave fullscreen" popup should
+   be shown */
+#define FULLSCREEN_POPUP_TIMEOUT 5
+
+G_DEFINE_TYPE (EmpathyCallWindowFullscreen, empathy_call_window_fullscreen,
+    G_TYPE_OBJECT)
+
+/* private structure */
+typedef struct _EmpathyCallWindowFullscreenPriv
+    EmpathyCallWindowFullscreenPriv;
+
+struct _EmpathyCallWindowFullscreenPriv
+{
+  EmpathyCallWindow *parent_window;
+
+  GtkWidget *leave_fullscreen_popup;
+  GtkWidget *video_widget;
+
+  guint popup_timeout;
+  gboolean popup_creation_in_progress;
+  gboolean dispose_has_run;
+};
+
+#define GET_PRIV(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW_FULLSCREEN, \
+    EmpathyCallWindowFullscreenPriv))
+
+static void empathy_call_window_fullscreen_dispose (GObject *object);
+static void empathy_call_window_fullscreen_finalize (GObject *object);
+
+static gboolean empathy_call_window_fullscreen_hide_popup (
+    EmpathyCallWindowFullscreen *fs);
+
+static void
+empathy_call_window_fullscreen_set_cursor_visible (
+    EmpathyCallWindowFullscreen *fs,
+    gboolean show_cursor)
+{
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (fs);
+  GdkWindow *window;
+
+  if (priv->video_widget == NULL)
+    return;
+
+  window = gtk_widget_get_window (priv->video_widget);
+
+  if (!show_cursor)
+    gdk_window_set_cursor (window, gdk_cursor_new (GDK_BLANK_CURSOR));
+  else
+    gdk_window_set_cursor (window, NULL);
+}
+
+static void
+empathy_call_window_fullscreen_add_popup_timeout (
+    EmpathyCallWindowFullscreen *self)
+{
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (self);
+
+  if (priv->popup_timeout == 0)
+    {
+      priv->popup_timeout = g_timeout_add_seconds (FULLSCREEN_POPUP_TIMEOUT,
+          (GSourceFunc) empathy_call_window_fullscreen_hide_popup, self);
+    }
+}
+
+static void
+empathy_call_window_fullscreen_remove_popup_timeout (
+    EmpathyCallWindowFullscreen *self)
+{
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (self);
+
+  if (priv->popup_timeout != 0)
+    {
+      g_source_remove (priv->popup_timeout);
+      priv->popup_timeout = 0;
+    }
+}
+
+void
+empathy_call_window_fullscreen_show_popup (EmpathyCallWindowFullscreen *self)
+{
+  gint leave_fullscreen_width, leave_fullscreen_height;
+  GdkScreen *screen;
+  GdkRectangle fullscreen_rect;
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (self);
+
+  g_assert (self->is_fullscreen);
+
+  g_return_if_fail (priv->parent_window != NULL);
+
+  if (priv->popup_creation_in_progress)
+    return;
+
+  if (!gtk_window_is_active (GTK_WINDOW (priv->parent_window)))
+    return;
+
+  priv->popup_creation_in_progress = TRUE;
+
+  empathy_call_window_fullscreen_set_cursor_visible (self, TRUE);
+
+  /* Obtaining the screen rectangle */
+  screen = gtk_window_get_screen (GTK_WINDOW (priv->parent_window));
+  gdk_screen_get_monitor_geometry (screen,
+      gdk_screen_get_monitor_at_window (screen,
+          gtk_widget_get_window (GTK_WIDGET (priv->parent_window))),
+      &fullscreen_rect);
+
+  /* Getting the popup window sizes */
+  gtk_window_get_size (GTK_WINDOW (priv->leave_fullscreen_popup),
+      &leave_fullscreen_width, &leave_fullscreen_height);
+
+  /* Moving the popup to the top-right corner (if the direction is LTR) or the
+     top-left corner (if the direction is RTL).*/
+  if (gtk_widget_get_direction (priv->leave_fullscreen_popup)
+        == GTK_TEXT_DIR_LTR)
+    {
+      gtk_window_move (GTK_WINDOW (priv->leave_fullscreen_popup),
+          fullscreen_rect.width + fullscreen_rect.x - leave_fullscreen_width,
+          fullscreen_rect.y);
+
+    }
+  else
+    {
+      gtk_window_move (GTK_WINDOW (priv->leave_fullscreen_popup),
+          fullscreen_rect.x, fullscreen_rect.y);
+    }
+
+  gtk_widget_show_all (priv->leave_fullscreen_popup);
+  empathy_call_window_fullscreen_add_popup_timeout (self);
+
+  priv->popup_creation_in_progress = FALSE;
+}
+
+static gboolean
+empathy_call_window_fullscreen_hide_popup (EmpathyCallWindowFullscreen *fs)
+{
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (fs);
+
+  if (priv->video_widget == NULL || !fs->is_fullscreen)
+    return TRUE;
+
+  gtk_widget_hide (priv->leave_fullscreen_popup);
+  empathy_call_window_fullscreen_remove_popup_timeout (fs);
+
+  empathy_call_window_fullscreen_set_cursor_visible (fs, FALSE);
+
+  return FALSE;
+}
+
+static void
+empathy_call_window_fullscreen_init (EmpathyCallWindowFullscreen *self)
+{
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (self);
+  GtkBuilder *gui;
+  gchar *filename;
+
+  filename = empathy_file_lookup ("empathy-call-window-fullscreen.ui", "src");
+  gui = empathy_builder_get_file (filename,
+    "leave_fullscreen_window", &priv->leave_fullscreen_popup,
+    "leave_fullscreen_button", &self->leave_fullscreen_button,
+    NULL);
+
+  gtk_widget_add_events (priv->leave_fullscreen_popup, GDK_POINTER_MOTION_MASK);
+
+  g_object_unref (gui);
+  g_free (filename);
+}
+
+static void
+empathy_call_window_fullscreen_class_init (
+    EmpathyCallWindowFullscreenClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (EmpathyCallWindowFullscreenPriv));
+
+  object_class->dispose = empathy_call_window_fullscreen_dispose;
+  object_class->finalize = empathy_call_window_fullscreen_finalize;
+}
+
+void
+empathy_call_window_fullscreen_dispose (GObject *object)
+{
+  EmpathyCallWindowFullscreen *self = EMPATHY_CALL_WINDOW_FULLSCREEN (object);
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (self);
+
+  if (priv->dispose_has_run)
+    return;
+
+  priv->dispose_has_run = TRUE;
+
+  if (priv->leave_fullscreen_popup != NULL)
+    gtk_widget_destroy (priv->leave_fullscreen_popup);
+  priv->leave_fullscreen_popup = NULL;
+
+  if (G_OBJECT_CLASS (empathy_call_window_fullscreen_parent_class)->dispose)
+    {
+      G_OBJECT_CLASS (
+          empathy_call_window_fullscreen_parent_class)->dispose (object);
+    }
+}
+
+void
+empathy_call_window_fullscreen_finalize (GObject *object)
+{
+  EmpathyCallWindowFullscreen *self = EMPATHY_CALL_WINDOW_FULLSCREEN (object);
+
+  empathy_call_window_fullscreen_remove_popup_timeout (self);
+
+  G_OBJECT_CLASS (
+      empathy_call_window_fullscreen_parent_class)->finalize (object);
+}
+
+static void
+empathy_call_window_fullscreen_parent_window_notify (GtkWidget *parent_window,
+    GParamSpec *property, EmpathyCallWindowFullscreen *fs)
+{
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (fs);
+
+  if (!fs->is_fullscreen)
+    return;
+
+  if (parent_window == GTK_WIDGET (priv->parent_window) &&
+        !gtk_window_is_active (GTK_WINDOW (parent_window)))
+    {
+      empathy_call_window_fullscreen_hide_popup (fs);
+      empathy_call_window_fullscreen_set_cursor_visible (fs, TRUE);
+    }
+}
+
+EmpathyCallWindowFullscreen *
+empathy_call_window_fullscreen_new (EmpathyCallWindow *parent_window)
+{
+  EmpathyCallWindowFullscreen *self = EMPATHY_CALL_WINDOW_FULLSCREEN (
+    g_object_new (EMPATHY_TYPE_CALL_WINDOW_FULLSCREEN, NULL));
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (self);
+
+  priv->parent_window = parent_window;
+  g_signal_connect (G_OBJECT (priv->parent_window), "notify::is-active",
+    G_CALLBACK (empathy_call_window_fullscreen_parent_window_notify), self);
+
+  return self;
+}
+
+void
+empathy_call_window_fullscreen_set_fullscreen (EmpathyCallWindowFullscreen *fs,
+  gboolean set_fullscreen)
+{
+
+  if (set_fullscreen)
+      empathy_call_window_fullscreen_remove_popup_timeout (fs);
+  else
+      empathy_call_window_fullscreen_hide_popup (fs);
+
+  empathy_call_window_fullscreen_set_cursor_visible (fs, !set_fullscreen);
+  fs->is_fullscreen = set_fullscreen;
+}
+
+static void
+video_widget_destroy_cb (GtkWidget *widget,
+    EmpathyCallWindowFullscreen *self)
+{
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (self);
+
+  priv->video_widget = NULL;
+}
+
+void
+empathy_call_window_fullscreen_set_video_widget (
+    EmpathyCallWindowFullscreen *fs,
+    GtkWidget *video_widget)
+{
+  EmpathyCallWindowFullscreenPriv *priv = GET_PRIV (fs);
+  priv->video_widget = video_widget;
+
+  tp_g_signal_connect_object (video_widget, "destroy",
+      G_CALLBACK (video_widget_destroy_cb), fs, 0);
+}
diff --git a/src/empathy-call-window-fullscreen.h b/src/empathy-call-window-fullscreen.h
new file mode 100644 (file)
index 0000000..7e9bd63
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * empathy-call-window-fullscreen.h - Header for EmpathyCallWindowFullscreen
+ * Copyright (C) 2009-2011 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __EMPATHY_CALL_WINDOW_FULLSCREEN_H__
+#define __EMPATHY_CALL_WINDOW_FULLSCREEN_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "empathy-call-window.h"
+
+G_BEGIN_DECLS
+
+typedef struct _EmpathyCallWindowFullscreen EmpathyCallWindowFullscreen;
+typedef struct _EmpathyCallWindowFullscreenClass
+    EmpathyCallWindowFullscreenClass;
+
+struct _EmpathyCallWindowFullscreenClass {
+  GObjectClass parent_class;
+};
+
+struct _EmpathyCallWindowFullscreen {
+  GObject parent;
+  gboolean is_fullscreen;
+  GtkWidget *leave_fullscreen_button;
+};
+
+GType empathy_call_window_fullscreen_get_type (void);
+
+/* TYPE MACROS */
+#define EMPATHY_TYPE_CALL_WINDOW_FULLSCREEN \
+  (empathy_call_window_fullscreen_get_type ())
+#define EMPATHY_CALL_WINDOW_FULLSCREEN(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), EMPATHY_TYPE_CALL_WINDOW_FULLSCREEN, \
+    EmpathyCallWindowFullscreen))
+#define EMPATHY_CALL_WINDOW_FULLSCREEN_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), EMPATHY_TYPE_CALL_WINDOW_FULLSCREEN, \
+    EmpathyCallWindowClassFullscreen))
+#define EMPATHY_IS_CALL_WINDOW_FULLSCREEN(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EMPATHY_TYPE_CALL_WINDOW_FULLSCREEN))
+#define EMPATHY_IS_CALL_WINDOW_FULLSCREEN_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), EMPATHY_TYPE_CALL_WINDOW_FULLSCREEN))
+#define EMPATHY_CALL_WINDOW_FULLSCREEN_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_CALL_WINDOW_FULLSCREEN, \
+    EmpathyCallWindowFullscreenClass))
+
+EmpathyCallWindowFullscreen *empathy_call_window_fullscreen_new (
+    EmpathyCallWindow *parent);
+
+void empathy_call_window_fullscreen_set_fullscreen (
+    EmpathyCallWindowFullscreen *fs,
+    gboolean set_fullscreen);
+void empathy_call_window_fullscreen_set_video_widget (
+    EmpathyCallWindowFullscreen *fs,
+    GtkWidget *video_widget);
+void empathy_call_window_fullscreen_show_popup (
+    EmpathyCallWindowFullscreen *fs);
+
+G_END_DECLS
+
+#endif /* #ifndef __EMPATHY_CALL_WINDOW_FULLSCREEN_H__*/
diff --git a/src/empathy-call-window.c b/src/empathy-call-window.c
new file mode 100644 (file)
index 0000000..3dbac9e
--- /dev/null
@@ -0,0 +1,3278 @@
+/*
+ * empathy-call-window.c - Source for EmpathyCallWindow
+ * Copyright (C) 2008-2011 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <math.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gst/gst.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <telepathy-glib/util.h>
+#include <telepathy-farstream/telepathy-farstream.h>
+#include <telepathy-glib/util.h>
+
+#include <gst/farsight/fs-element-added-notifier.h>
+#include <gst/farsight/fs-utils.h>
+
+#include <libempathy/empathy-tp-contact-factory.h>
+#include <libempathy/empathy-utils.h>
+#include <libempathy-gtk/empathy-avatar-image.h>
+#include <libempathy-gtk/empathy-ui-utils.h>
+#include <libempathy-gtk/empathy-sound-manager.h>
+#include <libempathy-gtk/empathy-geometry.h>
+#include <libempathy-gtk/empathy-images.h>
+
+#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
+#include <libempathy/empathy-debug.h>
+
+#include "empathy-call-window.h"
+#include "empathy-call-window-fullscreen.h"
+#include "empathy-call-factory.h"
+#include "empathy-video-widget.h"
+#include "empathy-audio-src.h"
+#include "empathy-audio-sink.h"
+#include "empathy-video-src.h"
+#include "ev-sidebar.h"
+
+#define BUTTON_ID "empathy-call-dtmf-button-id"
+
+#define CONTENT_HBOX_BORDER_WIDTH 6
+#define CONTENT_HBOX_SPACING 3
+#define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
+
+#define SELF_VIDEO_SECTION_WIDTH 160
+#define SELF_VIDEO_SECTION_HEIGTH 120
+
+/* The avatar's default width and height are set to the same value because we
+   want a square icon. */
+#define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
+#define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
+  EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
+
+/* If an video input error occurs, the error message will start with "v4l" */
+#define VIDEO_INPUT_ERROR_PREFIX "v4l"
+
+/* The time interval in milliseconds between 2 outgoing rings */
+#define MS_BETWEEN_RING 500
+
+G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
+
+enum {
+  PROP_CALL_HANDLER = 1,
+};
+
+typedef enum {
+  CONNECTING,
+  CONNECTED,
+  HELD,
+  DISCONNECTED,
+  REDIALING
+} CallState;
+
+typedef enum {
+  CAMERA_STATE_OFF = 0,
+  CAMERA_STATE_PREVIEW,
+  CAMERA_STATE_ON,
+} CameraState;
+
+struct _EmpathyCallWindowPriv
+{
+  gboolean dispose_has_run;
+  EmpathyCallHandler *handler;
+
+  EmpathyContact *contact;
+
+  guint call_state;
+  gboolean outgoing;
+
+  GtkUIManager *ui_manager;
+  GtkWidget *errors_vbox;
+  /* widget displays the video received from the remote user. This widget is
+   * alive only during call. */
+  GtkWidget *video_output;
+  GtkWidget *video_preview;
+  GtkWidget *remote_user_avatar_widget;
+  GtkWidget *self_user_avatar_widget;
+  GtkWidget *sidebar;
+  GtkWidget *sidebar_button;
+  GtkWidget *statusbar;
+  GtkWidget *volume_button;
+  GtkWidget *redial_button;
+  GtkWidget *mic_button;
+  GtkWidget *toolbar;
+  GtkWidget *pane;
+  GtkAction *redial;
+  GtkAction *menu_dialpad;
+  GtkAction *menu_fullscreen;
+  GtkAction *action_camera_on;
+  GtkWidget *tool_button_camera_off;
+  GtkWidget *tool_button_camera_preview;
+  GtkWidget *tool_button_camera_on;
+
+  /* The frames and boxes that contain self and remote avatar and video
+     input/output. When we redial, we destroy and re-create the boxes */
+  GtkWidget *remote_user_output_frame;
+  GtkWidget *self_user_output_frame;
+  GtkWidget *remote_user_output_hbox;
+  GtkWidget *self_user_output_hbox;
+
+  /* We keep a reference on the hbox which contains the main content so we can
+     easilly repack everything when toggling fullscreen */
+  GtkWidget *content_hbox;
+
+  /* This vbox is contained in the content_hbox and it contains the
+     self_user_output_frame and the sidebar button. When toggling fullscreen,
+     it needs to be repacked. We keep a reference on it for easier access. */
+  GtkWidget *vbox;
+
+  gulong video_output_motion_handler_id;
+  guint bus_message_source_id;
+
+  gdouble volume;
+  GtkWidget *volume_scale;
+  GtkWidget *volume_progress_bar;
+  GtkAdjustment *audio_input_adj;
+
+  GtkWidget *dtmf_panel;
+
+  /* Details vbox */
+  GtkWidget *details_vbox;
+  GtkWidget *vcodec_encoding_label;
+  GtkWidget *acodec_encoding_label;
+  GtkWidget *vcodec_decoding_label;
+  GtkWidget *acodec_decoding_label;
+
+  GtkWidget *audio_remote_candidate_label;
+  GtkWidget *audio_local_candidate_label;
+  GtkWidget *video_remote_candidate_label;
+  GtkWidget *video_local_candidate_label;
+  GtkWidget *video_remote_candidate_info_img;
+  GtkWidget *video_local_candidate_info_img;
+  GtkWidget *audio_remote_candidate_info_img;
+  GtkWidget *audio_local_candidate_info_img;
+
+  GstElement *video_input;
+  GstElement *audio_input;
+  GstElement *audio_output;
+  GstElement *pipeline;
+  GstElement *video_tee;
+
+  GstElement *funnel;
+
+  GList *notifiers;
+
+  guint context_id;
+
+  GTimer *timer;
+  guint timer_id;
+
+  GtkWidget *video_contrast;
+  GtkWidget *video_brightness;
+  GtkWidget *video_gamma;
+
+  GMutex *lock;
+  gboolean call_started;
+  gboolean sending_video;
+  CameraState camera_state;
+
+  EmpathyCallWindowFullscreen *fullscreen;
+  gboolean is_fullscreen;
+
+  /* Those fields represent the state of the window before it actually was in
+     fullscreen mode. */
+  gboolean sidebar_was_visible_before_fs;
+  gint original_width_before_fs;
+  gint original_height_before_fs;
+
+  /* TRUE if the call should be started when the pipeline is playing */
+  gboolean start_call_when_playing;
+  /* TRUE if we requested to set the pipeline in the playing state */
+  gboolean pipeline_playing;
+
+  EmpathySoundManager *sound_mgr;
+};
+
+#define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv)
+
+static void empathy_call_window_realized_cb (GtkWidget *widget,
+  EmpathyCallWindow *window);
+
+static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
+  GdkEvent *event, EmpathyCallWindow *window);
+
+static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
+  GdkEventWindowState *event, EmpathyCallWindow *window);
+
+static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
+  EmpathyCallWindow *window);
+
+static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
+  CameraState state);
+
+static void empathy_call_window_mic_toggled_cb (
+  GtkToggleToolButton *toggle, EmpathyCallWindow *window);
+
+static void empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
+  EmpathyCallWindow *window);
+
+static void empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
+  EmpathyCallWindow *window);
+
+static void empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
+  const gchar *page,
+  EmpathyCallWindow *window);
+
+static void empathy_call_window_hangup_cb (gpointer object,
+  EmpathyCallWindow *window);
+
+static void empathy_call_window_fullscreen_cb (gpointer object,
+  EmpathyCallWindow *window);
+
+static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
+
+static gboolean empathy_call_window_video_button_press_cb (
+  GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
+
+static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
+  GdkEventKey *event, EmpathyCallWindow *window);
+
+static gboolean empathy_call_window_video_output_motion_notify (
+  GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
+
+static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
+  guint button);
+
+static void empathy_call_window_redial_cb (gpointer object,
+  EmpathyCallWindow *window);
+
+static void empathy_call_window_dialpad_cb (GtkToggleAction *menu,
+  EmpathyCallWindow *window);
+
+static void empathy_call_window_restart_call (EmpathyCallWindow *window);
+
+static void empathy_call_window_status_message (EmpathyCallWindow *window,
+  gchar *message);
+
+static gboolean empathy_call_window_bus_message (GstBus *bus,
+  GstMessage *message, gpointer user_data);
+
+static void
+empathy_call_window_volume_changed_cb (GtkScaleButton *button,
+  gdouble value, EmpathyCallWindow *window);
+
+static void block_camera_control_signals (EmpathyCallWindow *self);
+static void unblock_camera_control_signals (EmpathyCallWindow *self);
+
+static void
+empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GtkToolItem *tool_item;
+  GtkWidget *camera_off_icon;
+  GdkPixbuf *pixbuf, *modded_pixbuf;
+
+  /* set the icon of the 'camera off' button by greying off the webcam icon */
+  pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
+      GTK_ICON_SIZE_SMALL_TOOLBAR);
+
+  modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+      gdk_pixbuf_get_width (pixbuf),
+      gdk_pixbuf_get_height (pixbuf));
+
+  gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
+  g_object_unref (pixbuf);
+
+  camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
+  g_object_unref (modded_pixbuf);
+  gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
+        priv->tool_button_camera_off), camera_off_icon);
+
+  /* Add an empty expanded GtkToolItem so the volume button is at the end of
+   * the toolbar. */
+  tool_item = gtk_tool_item_new ();
+  gtk_tool_item_set_expand (tool_item, TRUE);
+  gtk_widget_show (GTK_WIDGET (tool_item));
+  gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
+
+  priv->volume_button = gtk_volume_button_new ();
+  /* FIXME listen to the audiosinks signals and update the button according to
+   * that, for now starting out at 1.0 and assuming only the app changes the
+   * volume will do */
+  gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
+  g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
+    G_CALLBACK (empathy_call_window_volume_changed_cb), self);
+
+  tool_item = gtk_tool_item_new ();
+  gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
+  gtk_widget_show_all (GTK_WIDGET (tool_item));
+  gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
+}
+
+static void
+dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  TpyCallChannel *call;
+  GQuark button_quark;
+  TpDTMFEvent event;
+
+  g_object_get (priv->handler, "call-channel", &call, NULL);
+
+  button_quark = g_quark_from_static_string (BUTTON_ID);
+  event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
+    button_quark));
+
+  tpy_call_channel_dtmf_start_tone (call, event);
+
+  g_object_unref (call);
+}
+
+static void
+dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  TpyCallChannel *call;
+
+  g_object_get (priv->handler, "call-channel", &call, NULL);
+
+  tpy_call_channel_dtmf_stop_tone (call);
+
+  g_object_unref (call);
+}
+
+static GtkWidget *
+empathy_call_window_create_dtmf (EmpathyCallWindow *self)
+{
+  GtkWidget *table;
+  int i;
+  GQuark button_quark;
+  struct {
+    const gchar *label;
+    TpDTMFEvent event;
+  } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
+                      { "2", TP_DTMF_EVENT_DIGIT_2 },
+                      { "3", TP_DTMF_EVENT_DIGIT_3 },
+                      { "4", TP_DTMF_EVENT_DIGIT_4 },
+                      { "5", TP_DTMF_EVENT_DIGIT_5 },
+                      { "6", TP_DTMF_EVENT_DIGIT_6 },
+                      { "7", TP_DTMF_EVENT_DIGIT_7 },
+                      { "8", TP_DTMF_EVENT_DIGIT_8 },
+                      { "9", TP_DTMF_EVENT_DIGIT_9 },
+                      { "#", TP_DTMF_EVENT_HASH },
+                      { "0", TP_DTMF_EVENT_DIGIT_0 },
+                      { "*", TP_DTMF_EVENT_ASTERISK },
+                      { NULL, } };
+
+  button_quark = g_quark_from_static_string (BUTTON_ID);
+
+  table = gtk_table_new (4, 3, TRUE);
+
+  for (i = 0; dtmfbuttons[i].label != NULL; i++)
+    {
+      GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
+      gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
+        i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
+
+      g_object_set_qdata (G_OBJECT (button), button_quark,
+        GUINT_TO_POINTER (dtmfbuttons[i].event));
+
+      g_signal_connect (G_OBJECT (button), "pressed",
+        G_CALLBACK (dtmf_button_pressed_cb), self);
+      g_signal_connect (G_OBJECT (button), "released",
+        G_CALLBACK (dtmf_button_released_cb), self);
+    }
+
+  return table;
+}
+
+static GtkWidget *
+empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
+  gchar *label_text, GtkWidget *bin)
+{
+   GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
+   GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
+   GtkWidget *label = gtk_label_new (label_text);
+
+   gtk_widget_set_sensitive (scale, FALSE);
+
+   gtk_container_add (GTK_CONTAINER (bin), vbox);
+
+   gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
+   gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
+   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+   return scale;
+}
+
+static void
+empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
+  EmpathyCallWindow *self)
+
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  empathy_video_src_set_channel (priv->video_input,
+    EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
+}
+
+static void
+empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
+  EmpathyCallWindow *self)
+
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  empathy_video_src_set_channel (priv->video_input,
+    EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
+}
+
+static void
+empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
+  EmpathyCallWindow *self)
+
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  empathy_video_src_set_channel (priv->video_input,
+    EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
+}
+
+
+static GtkWidget *
+empathy_call_window_create_video_input (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GtkWidget *hbox;
+
+  hbox = gtk_hbox_new (TRUE, 3);
+
+  priv->video_contrast = empathy_call_window_create_video_input_add_slider (
+    self,  _("Contrast"), hbox);
+
+  priv->video_brightness = empathy_call_window_create_video_input_add_slider (
+    self,  _("Brightness"), hbox);
+
+  priv->video_gamma = empathy_call_window_create_video_input_add_slider (
+    self,  _("Gamma"), hbox);
+
+  return hbox;
+}
+
+static void
+empathy_call_window_setup_video_input (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  guint supported;
+  GtkAdjustment *adj;
+
+  supported = empathy_video_src_get_supported_channels (priv->video_input);
+
+  if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
+    {
+      adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
+
+      gtk_adjustment_set_value (adj,
+        empathy_video_src_get_channel (priv->video_input,
+          EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
+
+      g_signal_connect (G_OBJECT (adj), "value-changed",
+        G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
+
+      gtk_widget_set_sensitive (priv->video_contrast, TRUE);
+    }
+
+  if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
+    {
+      adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
+
+      gtk_adjustment_set_value (adj,
+        empathy_video_src_get_channel (priv->video_input,
+          EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
+
+      g_signal_connect (G_OBJECT (adj), "value-changed",
+        G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
+      gtk_widget_set_sensitive (priv->video_brightness, TRUE);
+    }
+
+  if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
+    {
+      adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
+
+      gtk_adjustment_set_value (adj,
+        empathy_video_src_get_channel (priv->video_input,
+          EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
+
+      g_signal_connect (G_OBJECT (adj), "value-changed",
+        G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
+      gtk_widget_set_sensitive (priv->video_gamma, TRUE);
+    }
+}
+
+static void
+empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  gdouble volume;
+
+  volume = gtk_adjustment_get_value (adj)/100.0;
+
+  /* Don't store the volume because of muting */
+  if (volume > 0 || gtk_toggle_tool_button_get_active (
+        GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
+    priv->volume = volume;
+
+  /* Ensure that the toggle button is active if the volume is > 0 and inactive
+   * if it's smaller than 0 */
+  if ((volume > 0) != gtk_toggle_tool_button_get_active (
+        GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
+    gtk_toggle_tool_button_set_active (
+      GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
+
+  empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
+    volume);
+}
+
+static void
+empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
+  gdouble level, EmpathyCallWindow *window)
+{
+  gdouble value;
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  value = CLAMP (pow (10, level / 20), 0.0, 1.0);
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
+      value);
+}
+
+static GtkWidget *
+empathy_call_window_create_audio_input (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GtkWidget *hbox, *vbox, *label;
+
+  hbox = gtk_hbox_new (TRUE, 3);
+
+  vbox = gtk_vbox_new (FALSE, 3);
+  gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
+
+  priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
+  gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
+  label = gtk_label_new (_("Volume"));
+
+  priv->audio_input_adj = gtk_range_get_adjustment (
+    GTK_RANGE (priv->volume_scale));
+  priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
+    (priv->audio_input));
+  gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
+
+  g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
+    G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
+
+  gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
+  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
+
+  priv->volume_progress_bar = gtk_progress_bar_new ();
+
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar),
+      GTK_ORIENTATION_VERTICAL);
+
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
+      0);
+
+  gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
+      3);
+
+  return hbox;
+}
+
+static void
+create_video_output_widget (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstBus *bus;
+
+  g_assert (priv->video_output == NULL);
+  g_assert (priv->pipeline != NULL);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
+  priv->video_output = empathy_video_widget_new (bus);
+
+  gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
+      priv->video_output, TRUE, TRUE, 0);
+
+  gtk_widget_add_events (priv->video_output,
+      GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
+  g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
+      G_CALLBACK (empathy_call_window_video_button_press_cb), self);
+
+  g_object_unref (bus);
+}
+
+static void
+create_video_input (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  g_assert (priv->video_input == NULL);
+  priv->video_input = empathy_video_src_new ();
+  gst_object_ref (priv->video_input);
+  gst_object_sink (priv->video_input);
+}
+
+static void
+create_audio_input (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  g_assert (priv->audio_input == NULL);
+  priv->audio_input = empathy_audio_src_new ();
+  gst_object_ref (priv->audio_input);
+  gst_object_sink (priv->audio_input);
+
+  tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
+    G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
+    self, 0);
+}
+
+static void
+add_video_preview_to_pipeline (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstElement *preview;
+
+  g_assert (priv->video_preview != NULL);
+  g_assert (priv->pipeline != NULL);
+  g_assert (priv->video_input != NULL);
+  g_assert (priv->video_tee != NULL);
+
+  preview = empathy_video_widget_get_element (
+      EMPATHY_VIDEO_WIDGET (priv->video_preview));
+
+  if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
+    {
+      g_warning ("Could not add video input to pipeline");
+      return;
+    }
+
+  if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
+    {
+      g_warning ("Could not add video preview to pipeline");
+      return;
+    }
+
+  if (!gst_element_link (priv->video_input, priv->video_tee))
+    {
+      g_warning ("Could not link video input to video tee");
+      return;
+    }
+
+  if (!gst_element_link (priv->video_tee, preview))
+    {
+      g_warning ("Could not link video tee to video preview");
+      return;
+    }
+}
+
+static void
+create_video_preview (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstBus *bus;
+
+  g_assert (priv->video_preview == NULL);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
+
+  priv->video_preview = empathy_video_widget_new_with_size (bus,
+      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
+  g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
+
+  gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
+      priv->video_preview, TRUE, TRUE, 0);
+
+  g_object_unref (bus);
+}
+
+static void
+play_camera (EmpathyCallWindow *window,
+    gboolean play)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  GstElement *preview;
+  GstState state;
+
+  if (priv->video_preview == NULL)
+    {
+      create_video_preview (window);
+      add_video_preview_to_pipeline (window);
+    }
+
+  if (play)
+    state = GST_STATE_PLAYING;
+  else
+    state = GST_STATE_NULL;
+
+  preview = empathy_video_widget_get_element (
+      EMPATHY_VIDEO_WIDGET (priv->video_preview));
+
+  gst_element_set_state (preview, state);
+  gst_element_set_state (priv->video_input, state);
+  gst_element_set_state (priv->video_tee, state);
+}
+
+static void
+display_video_preview (EmpathyCallWindow *self,
+    gboolean display)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (display)
+    {
+      /* Display the preview and hide the self avatar */
+      DEBUG ("Show video preview");
+
+      play_camera (self, TRUE);
+      gtk_widget_show (priv->video_preview);
+      gtk_widget_hide (priv->self_user_avatar_widget);
+    }
+  else
+    {
+      /* Display the self avatar and hide the preview */
+      DEBUG ("Show self avatar");
+
+      if (priv->video_preview != NULL)
+        {
+          gtk_widget_hide (priv->video_preview);
+          play_camera (self, FALSE);
+        }
+      gtk_widget_show (priv->self_user_avatar_widget);
+    }
+}
+
+static void
+empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  empathy_call_window_status_message (window, _("Connecting…"));
+  priv->call_state = CONNECTING;
+
+  if (priv->outgoing)
+    empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
+        EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
+}
+
+static void
+disable_camera (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_OFF)
+    return;
+
+  DEBUG ("Disable camera");
+
+  display_video_preview (self, FALSE);
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_on), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_preview), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
+      CAMERA_STATE_OFF);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_OFF;
+}
+
+static void
+tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_OFF)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  disable_camera (self);
+}
+
+static void
+enable_preview (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_PREVIEW)
+    return;
+
+  DEBUG ("Enable preview");
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    {
+      /* preview is already displayed so we just have to stop sending */
+      empathy_call_window_set_send_video (self, CAMERA_STATE_PREVIEW);
+    }
+  else
+    {
+      display_video_preview (self, TRUE);
+    }
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_on), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_preview), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
+      CAMERA_STATE_PREVIEW);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_PREVIEW;
+}
+
+static void
+tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_PREVIEW)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  enable_preview (self);
+}
+
+static void
+enable_camera (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    return;
+
+  if (priv->video_input == NULL)
+    {
+      DEBUG ("Can't enable camera, no input");
+      return;
+    }
+
+
+  DEBUG ("Enable camera");
+
+  empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_preview), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_on), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
+      CAMERA_STATE_ON);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_ON;
+}
+
+static void
+tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_ON)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  enable_camera (self);
+}
+
+static void
+action_camera_change_cb (GtkRadioAction *action,
+    GtkRadioAction *current,
+    EmpathyCallWindow *self)
+{
+  CameraState state;
+
+  state = gtk_radio_action_get_current_value (current);
+
+  switch (state)
+    {
+      case CAMERA_STATE_OFF:
+        disable_camera (self);
+        break;
+
+      case CAMERA_STATE_PREVIEW:
+        enable_preview (self);
+        break;
+
+      case CAMERA_STATE_ON:
+        enable_camera (self);
+        break;
+
+      default:
+        g_assert_not_reached ();
+    }
+}
+
+static void
+create_pipeline (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstBus *bus;
+
+  g_assert (priv->pipeline == NULL);
+
+  priv->pipeline = gst_pipeline_new (NULL);
+  priv->pipeline_playing = FALSE;
+
+  priv->video_tee = gst_element_factory_make ("tee", NULL);
+  gst_object_ref (priv->video_tee);
+  gst_object_sink (priv->video_tee);
+
+  gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
+  priv->bus_message_source_id = gst_bus_add_watch (bus,
+      empathy_call_window_bus_message, self);
+
+  g_object_unref (bus);
+}
+
+
+static void
+empathy_call_window_init (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv;
+  GtkBuilder *gui;
+  GtkWidget *top_vbox;
+  GtkWidget *h;
+  GtkWidget *arrow;
+  GtkWidget *page;
+  gchar *filename;
+  GtkWidget *scroll;
+
+  priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+    EMPATHY_TYPE_CALL_WINDOW, EmpathyCallWindowPriv);
+
+  filename = empathy_file_lookup ("empathy-call-window.ui", "src");
+  gui = empathy_builder_get_file (filename,
+    "call_window_vbox", &top_vbox,
+    "errors_vbox", &priv->errors_vbox,
+    "pane", &priv->pane,
+    "statusbar", &priv->statusbar,
+    "redial", &priv->redial_button,
+    "microphone", &priv->mic_button,
+    "toolbar", &priv->toolbar,
+    "menuredial", &priv->redial,
+    "menudialpad", &priv->menu_dialpad,
+    "ui_manager", &priv->ui_manager,
+    "menufullscreen", &priv->menu_fullscreen,
+    "camera_off", &priv->tool_button_camera_off,
+    "camera_preview", &priv->tool_button_camera_preview,
+    "camera_on", &priv->tool_button_camera_on,
+    "action_camera_on",  &priv->action_camera_on,
+    "details_vbox",  &priv->details_vbox,
+    "vcodec_encoding_label", &priv->vcodec_encoding_label,
+    "acodec_encoding_label", &priv->acodec_encoding_label,
+    "acodec_decoding_label", &priv->acodec_decoding_label,
+    "vcodec_decoding_label", &priv->vcodec_decoding_label,
+    "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
+    "audio_local_candidate_label", &priv->audio_local_candidate_label,
+    "video_remote_candidate_label", &priv->video_remote_candidate_label,
+    "video_local_candidate_label", &priv->video_local_candidate_label,
+    "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
+    "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
+    "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
+    "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
+    NULL);
+  g_free (filename);
+
+  empathy_builder_connect (gui, self,
+    "menuhangup", "activate", empathy_call_window_hangup_cb,
+    "hangup", "clicked", empathy_call_window_hangup_cb,
+    "menuredial", "activate", empathy_call_window_redial_cb,
+    "redial", "clicked", empathy_call_window_redial_cb,
+    "menudialpad", "toggled", empathy_call_window_dialpad_cb,
+    "microphone", "toggled", empathy_call_window_mic_toggled_cb,
+    "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
+    "camera_off", "toggled", tool_button_camera_off_toggled_cb,
+    "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
+    "camera_on", "toggled", tool_button_camera_on_toggled_cb,
+    "action_camera_on", "changed", action_camera_change_cb,
+    NULL);
+
+  gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
+
+  priv->lock = g_mutex_new ();
+
+  gtk_container_add (GTK_CONTAINER (self), top_vbox);
+
+  priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
+  gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
+                                  CONTENT_HBOX_BORDER_WIDTH);
+  gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
+
+  /* remote user output frame */
+  priv->remote_user_output_frame = gtk_frame_new (NULL);
+  gtk_widget_set_size_request (priv->remote_user_output_frame,
+      EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
+  gtk_box_pack_start (GTK_BOX (priv->content_hbox),
+      priv->remote_user_output_frame, TRUE, TRUE,
+      CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+
+  priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
+
+  priv->remote_user_avatar_widget = gtk_image_new ();
+
+  gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
+      priv->remote_user_avatar_widget, TRUE, TRUE, 0);
+
+  gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
+      priv->remote_user_output_hbox);
+
+  /* self user output frame */
+  priv->self_user_output_frame = gtk_frame_new (NULL);
+  gtk_widget_set_size_request (priv->self_user_output_frame,
+      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
+
+  priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
+
+  priv->self_user_avatar_widget = gtk_image_new ();
+  gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
+      priv->self_user_avatar_widget, TRUE, TRUE, 0);
+
+  gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
+      priv->self_user_output_hbox);
+
+  create_pipeline (self);
+  create_video_output_widget (self);
+  create_audio_input (self);
+  create_video_input (self);
+
+  /* The call will be started as soon the pipeline is playing */
+  priv->start_call_when_playing = TRUE;
+
+  priv->vbox = gtk_vbox_new (FALSE, 3);
+  gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
+      FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+  gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
+      FALSE, FALSE, 0);
+
+  empathy_call_window_setup_toolbar (self);
+
+  priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
+  arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
+  g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
+    G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
+
+  gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
+
+  h = gtk_hbox_new (FALSE, 3);
+  gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
+  gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
+
+  priv->sidebar = ev_sidebar_new ();
+  g_signal_connect (G_OBJECT (priv->sidebar),
+    "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
+  g_signal_connect (G_OBJECT (priv->sidebar),
+    "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
+  g_signal_connect (G_OBJECT (priv->sidebar), "changed",
+    G_CALLBACK (empathy_call_window_sidebar_changed_cb), self);
+  gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
+
+  page = empathy_call_window_create_audio_input (self);
+  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input",
+      _("Audio input"), page);
+
+  page = empathy_call_window_create_video_input (self);
+  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input",
+      _("Video input"), page);
+
+  priv->dtmf_panel = empathy_call_window_create_dtmf (self);
+  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
+      _("Dialpad"), priv->dtmf_panel);
+
+  gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
+
+  /* Put the details vbox in a scroll window as it can require a lot of
+   * horizontal space. */
+  scroll = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll),
+      priv->details_vbox);
+
+  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details", _("Details"),
+    scroll);
+
+  gtk_widget_show_all (top_vbox);
+
+  gtk_widget_hide (priv->sidebar);
+
+  priv->fullscreen = empathy_call_window_fullscreen_new (self);
+  empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
+      priv->video_output);
+  g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
+      "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
+
+  g_signal_connect (G_OBJECT (self), "realize",
+    G_CALLBACK (empathy_call_window_realized_cb), self);
+
+  g_signal_connect (G_OBJECT (self), "delete-event",
+    G_CALLBACK (empathy_call_window_delete_cb), self);
+
+  g_signal_connect (G_OBJECT (self), "window-state-event",
+    G_CALLBACK (empathy_call_window_state_event_cb), self);
+
+  g_signal_connect (G_OBJECT (self), "key-press-event",
+      G_CALLBACK (empathy_call_window_key_press_cb), self);
+
+  priv->timer = g_timer_new ();
+
+  g_object_ref (priv->ui_manager);
+  g_object_unref (gui);
+
+  priv->sound_mgr = empathy_sound_manager_dup_singleton ();
+
+  empathy_geometry_bind (GTK_WINDOW (self), "call-window");
+}
+
+/* Instead of specifying a width and a height, we specify only one size. That's
+   because we want a square avatar icon.  */
+static void
+init_contact_avatar_with_size (EmpathyContact *contact,
+    GtkWidget *image_widget,
+    gint size)
+{
+  GdkPixbuf *pixbuf_avatar = NULL;
+
+  if (contact != NULL)
+    {
+      pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
+        size, size);
+    }
+
+  if (pixbuf_avatar == NULL)
+    {
+      pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
+          EMPATHY_IMAGE_AVATAR_DEFAULT, size);
+    }
+
+  gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
+
+  if (pixbuf_avatar != NULL)
+    g_object_unref (pixbuf_avatar);
+}
+
+static void
+set_window_title (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  gchar *tmp;
+
+  if (priv->contact != NULL)
+    {
+      /* translators: Call is a noun and %s is the contact name. This string
+       * is used in the window title */
+      tmp = g_strdup_printf (_("Call with %s"),
+          empathy_contact_get_alias (priv->contact));
+      gtk_window_set_title (GTK_WINDOW (self), tmp);
+      g_free (tmp);
+    }
+  else
+    {
+      gtk_window_set_title (GTK_WINDOW (self), _("Call with %d participants"));
+    }
+}
+
+static void
+contact_name_changed_cb (EmpathyContact *contact,
+    GParamSpec *pspec, EmpathyCallWindow *self)
+{
+  set_window_title (self);
+}
+
+static void
+contact_avatar_changed_cb (EmpathyContact *contact,
+    GParamSpec *pspec, GtkWidget *avatar_widget)
+{
+  int size;
+  GtkAllocation allocation;
+
+  gtk_widget_get_allocation (avatar_widget, &allocation);
+  size = allocation.height;
+
+  if (size == 0)
+    {
+      /* the widget is not allocated yet, set a default size */
+      size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
+          REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
+    }
+
+  init_contact_avatar_with_size (contact, avatar_widget, size);
+}
+
+static void
+empathy_call_window_got_self_contact_cb (TpConnection *connection,
+    EmpathyContact *contact, const GError *error, gpointer user_data,
+    GObject *weak_object)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
+      MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
+
+  g_signal_connect (contact, "notify::avatar",
+      G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
+}
+
+static void
+empathy_call_window_setup_avatars (EmpathyCallWindow *self,
+    EmpathyCallHandler *handler)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  TpConnection *connection;
+
+  g_signal_connect (priv->contact, "notify::name",
+      G_CALLBACK (contact_name_changed_cb), self);
+  g_signal_connect (priv->contact, "notify::avatar",
+    G_CALLBACK (contact_avatar_changed_cb),
+    priv->remote_user_avatar_widget);
+
+  /* Retrieving the self avatar */
+  connection = empathy_contact_get_connection (priv->contact);
+  empathy_tp_contact_factory_get_from_handle (connection,
+      tp_connection_get_self_handle (connection),
+      empathy_call_window_got_self_contact_cb, self, NULL,
+      G_OBJECT (self));
+
+  set_window_title (self);
+
+  init_contact_avatar_with_size (priv->contact,
+      priv->remote_user_avatar_widget,
+      MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
+          REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
+
+  /* The remote avatar is shown by default and will be hidden when we receive
+     video from the remote side. */
+  gtk_widget_hide (priv->video_output);
+  gtk_widget_show (priv->remote_user_avatar_widget);
+}
+
+static void
+update_send_codec (EmpathyCallWindow *self,
+    gboolean audio)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  FsCodec *codec;
+  GtkWidget *widget;
+  gchar *tmp;
+
+  if (audio)
+    {
+      codec = empathy_call_handler_get_send_audio_codec (priv->handler);
+      widget = priv->acodec_encoding_label;
+    }
+  else
+    {
+      codec = empathy_call_handler_get_send_video_codec (priv->handler);
+      widget = priv->vcodec_encoding_label;
+    }
+
+  if (codec == NULL)
+    return;
+
+  tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
+  gtk_label_set_text (GTK_LABEL (widget), tmp);
+  g_free (tmp);
+}
+
+static void
+send_audio_codec_notify_cb (GObject *object,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyCallWindow *self = user_data;
+
+  update_send_codec (self, TRUE);
+}
+
+static void
+send_video_codec_notify_cb (GObject *object,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyCallWindow *self = user_data;
+
+  update_send_codec (self, FALSE);
+}
+
+static void
+update_recv_codec (EmpathyCallWindow *self,
+    gboolean audio)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GList *codecs, *l;
+  GtkWidget *widget;
+  GString *str = NULL;
+
+  if (audio)
+    {
+      codecs = empathy_call_handler_get_recv_audio_codecs (priv->handler);
+      widget = priv->acodec_decoding_label;
+    }
+  else
+    {
+      codecs = empathy_call_handler_get_recv_video_codecs (priv->handler);
+      widget = priv->vcodec_decoding_label;
+    }
+
+  if (codecs == NULL)
+    return;
+
+  for (l = codecs; l != NULL; l = g_list_next (l))
+    {
+      FsCodec *codec = l->data;
+
+      if (str == NULL)
+        str = g_string_new (NULL);
+      else
+        g_string_append (str, ", ");
+
+      g_string_append_printf (str, "%s/%u", codec->encoding_name,
+          codec->clock_rate);
+    }
+
+  gtk_label_set_text (GTK_LABEL (widget), str->str);
+  g_string_free (str, TRUE);
+}
+
+static void
+recv_audio_codecs_notify_cb (GObject *object,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyCallWindow *self = user_data;
+
+  update_recv_codec (self, TRUE);
+}
+
+static void
+recv_video_codecs_notify_cb (GObject *object,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyCallWindow *self = user_data;
+
+  update_recv_codec (self, FALSE);
+}
+
+static const gchar *
+candidate_type_to_str (FsCandidate *candidate)
+{
+  switch (candidate->type)
+    {
+      case FS_CANDIDATE_TYPE_HOST:
+        return "host";
+      case FS_CANDIDATE_TYPE_SRFLX:
+        return "server reflexive";
+      case FS_CANDIDATE_TYPE_PRFLX:
+        return "peer reflexive";
+      case FS_CANDIDATE_TYPE_RELAY:
+        return "relay";
+      case FS_CANDIDATE_TYPE_MULTICAST:
+        return "multicast";
+    }
+
+  return NULL;
+}
+
+static const gchar *
+candidate_type_to_desc (FsCandidate *candidate)
+{
+  switch (candidate->type)
+    {
+      case FS_CANDIDATE_TYPE_HOST:
+        return _("The IP address as seen by the machine");
+      case FS_CANDIDATE_TYPE_SRFLX:
+        return _("The IP address as seen by a server on the Internet");
+      case FS_CANDIDATE_TYPE_PRFLX:
+        return _("The IP address of the peer as seen by the other side");
+      case FS_CANDIDATE_TYPE_RELAY:
+        return _("The IP address of a relay server");
+      case FS_CANDIDATE_TYPE_MULTICAST:
+        return _("The IP address of the multicast group");
+    }
+
+  return NULL;
+}
+
+static void
+update_candidat_widget (EmpathyCallWindow *self,
+    GtkWidget *label,
+    GtkWidget *img,
+    FsCandidate *candidate)
+{
+  gchar *str;
+
+  g_assert (candidate != NULL);
+  str = g_strdup_printf ("%s %u (%s)", candidate->ip,
+      candidate->port, candidate_type_to_str (candidate));
+
+  gtk_label_set_text (GTK_LABEL (label), str);
+  gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
+
+  g_free (str);
+}
+
+static void
+candidates_changed_cb (GObject *object,
+    FsMediaType type,
+    EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  FsCandidate *candidate = NULL;
+
+  if (type == FS_MEDIA_TYPE_VIDEO)
+    {
+      /* Update remote candidate */
+      candidate = empathy_call_handler_get_video_remote_candidate (
+          priv->handler);
+
+      update_candidat_widget (self, priv->video_remote_candidate_label,
+          priv->video_remote_candidate_info_img, candidate);
+
+      /* Update local candidate */
+      candidate = empathy_call_handler_get_video_local_candidate (
+          priv->handler);
+
+      update_candidat_widget (self, priv->video_local_candidate_label,
+          priv->video_local_candidate_info_img, candidate);
+    }
+  else
+    {
+      /* Update remote candidate */
+      candidate = empathy_call_handler_get_audio_remote_candidate (
+          priv->handler);
+
+      update_candidat_widget (self, priv->audio_remote_candidate_label,
+          priv->audio_remote_candidate_info_img, candidate);
+
+      /* Update local candidate */
+      candidate = empathy_call_handler_get_audio_local_candidate (
+          priv->handler);
+
+      update_candidat_widget (self, priv->audio_local_candidate_label,
+          priv->audio_local_candidate_info_img, candidate);
+    }
+}
+
+static void
+empathy_call_window_constructed (GObject *object)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  TpyCallChannel *call;
+
+  g_assert (priv->handler != NULL);
+
+  g_object_get (priv->handler, "call-channel", &call, NULL);
+  priv->outgoing = (call == NULL);
+  if (call != NULL)
+    g_object_unref (call);
+
+  g_object_get (priv->handler, "target-contact", &priv->contact, NULL);
+  g_assert (priv->contact != NULL);
+
+  empathy_call_window_setup_avatars (self, priv->handler);
+  empathy_call_window_set_state_connecting (self);
+
+  if (!empathy_call_handler_has_initial_video (priv->handler))
+    {
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
+    }
+  /* If call has InitialVideo, the preview will be started once the call has
+   * been started (start_call()). */
+
+  update_send_codec (self, TRUE);
+  update_send_codec (self, FALSE);
+  update_recv_codec (self, TRUE);
+  update_recv_codec (self, FALSE);
+
+  tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
+      G_CALLBACK (send_audio_codec_notify_cb), self, 0);
+  tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
+      G_CALLBACK (send_video_codec_notify_cb), self, 0);
+  tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
+      G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
+  tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
+      G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
+
+  tp_g_signal_connect_object (priv->handler, "candidates-changed",
+      G_CALLBACK (candidates_changed_cb), self, 0);
+}
+
+static void empathy_call_window_dispose (GObject *object);
+static void empathy_call_window_finalize (GObject *object);
+
+static void
+empathy_call_window_set_property (GObject *object,
+  guint property_id, const GValue *value, GParamSpec *pspec)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (object);
+
+  switch (property_id)
+    {
+      case PROP_CALL_HANDLER:
+        priv->handler = g_value_dup_object (value);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+empathy_call_window_get_property (GObject *object,
+  guint property_id, GValue *value, GParamSpec *pspec)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (object);
+
+  switch (property_id)
+    {
+      case PROP_CALL_HANDLER:
+        g_value_set_object (value, priv->handler);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+empathy_call_window_class_init (
+  EmpathyCallWindowClass *empathy_call_window_class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
+  GParamSpec *param_spec;
+
+  g_type_class_add_private (empathy_call_window_class,
+    sizeof (EmpathyCallWindowPriv));
+
+  object_class->constructed = empathy_call_window_constructed;
+  object_class->set_property = empathy_call_window_set_property;
+  object_class->get_property = empathy_call_window_get_property;
+
+  object_class->dispose = empathy_call_window_dispose;
+  object_class->finalize = empathy_call_window_finalize;
+
+  param_spec = g_param_spec_object ("handler",
+    "handler", "The call handler",
+    EMPATHY_TYPE_CALL_HANDLER,
+    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+    PROP_CALL_HANDLER, param_spec);
+}
+
+void
+empathy_call_window_dispose (GObject *object)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->dispose_has_run)
+    return;
+
+  priv->dispose_has_run = TRUE;
+
+  if (priv->handler != NULL)
+    {
+      empathy_call_handler_stop_call (priv->handler);
+      tp_clear_object (&priv->handler);
+    }
+
+  if (priv->bus_message_source_id != 0)
+    {
+      g_source_remove (priv->bus_message_source_id);
+      priv->bus_message_source_id = 0;
+    }
+
+  tp_clear_object (&priv->pipeline);
+  tp_clear_object (&priv->video_input);
+  tp_clear_object (&priv->audio_input);
+  tp_clear_object (&priv->video_tee);
+  tp_clear_object (&priv->ui_manager);
+  tp_clear_object (&priv->fullscreen);
+
+  g_list_free_full (priv->notifiers, g_object_unref);
+
+  if (priv->timer_id != 0)
+    g_source_remove (priv->timer_id);
+  priv->timer_id = 0;
+
+  if (priv->contact != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (priv->contact,
+          contact_name_changed_cb, self);
+      priv->contact = NULL;
+    }
+
+
+  tp_clear_object (&priv->sound_mgr);
+
+  G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
+}
+
+static void
+disconnect_video_output_motion_handler (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->video_output_motion_handler_id != 0)
+    {
+      g_signal_handler_disconnect (G_OBJECT (priv->video_output),
+          priv->video_output_motion_handler_id);
+      priv->video_output_motion_handler_id = 0;
+    }
+}
+
+void
+empathy_call_window_finalize (GObject *object)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  disconnect_video_output_motion_handler (self);
+
+  /* free any data held directly by the object here */
+  g_mutex_free (priv->lock);
+
+  g_timer_destroy (priv->timer);
+
+  G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
+}
+
+
+EmpathyCallWindow *
+empathy_call_window_new (EmpathyCallHandler *handler)
+{
+  return EMPATHY_CALL_WINDOW (
+    g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
+}
+
+static void
+empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
+  GstElement *conference, gpointer user_data)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  FsElementAddedNotifier *notifier;
+  GKeyFile *keyfile;
+
+  DEBUG ("Conference added");
+
+  /* Add notifier to set the various element properties as needed */
+  notifier = fs_element_added_notifier_new ();
+  keyfile = fs_utils_get_default_element_properties (conference);
+
+  if (keyfile != NULL)
+    fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
+
+  fs_element_added_notifier_add (notifier, GST_BIN (priv->pipeline));
+
+  priv->notifiers = g_list_prepend (priv->notifiers, notifier);
+
+  gst_bin_add (GST_BIN (priv->pipeline), conference);
+  gst_element_set_state (conference, GST_STATE_PLAYING);
+}
+
+static void
+empathy_call_window_conference_removed_cb (EmpathyCallHandler *handler,
+  GstElement *conference, gpointer user_data)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  gst_bin_remove (GST_BIN (priv->pipeline), conference);
+  gst_element_set_state (conference, GST_STATE_NULL);
+}
+
+static gboolean
+empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
+{
+  GstStateChangeReturn state_change_return;
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->pipeline == NULL)
+    return TRUE;
+
+  if (priv->bus_message_source_id != 0)
+    {
+      g_source_remove (priv->bus_message_source_id);
+      priv->bus_message_source_id = 0;
+    }
+
+  state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+
+  if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
+        state_change_return == GST_STATE_CHANGE_NO_PREROLL)
+    {
+      if (priv->pipeline != NULL)
+        g_object_unref (priv->pipeline);
+      priv->pipeline = NULL;
+
+      g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
+          empathy_call_window_mic_volume_changed_cb, self);
+
+      if (priv->audio_output != NULL)
+        g_object_unref (priv->audio_output);
+      priv->audio_output = NULL;
+
+      if (priv->video_tee != NULL)
+        g_object_unref (priv->video_tee);
+      priv->video_tee = NULL;
+
+      if (priv->video_preview != NULL)
+        gtk_widget_destroy (priv->video_preview);
+      priv->video_preview = NULL;
+
+      priv->funnel = NULL;
+
+      create_pipeline (self);
+      /* Call will be started when user will hit the 'redial' button */
+      priv->start_call_when_playing = FALSE;
+      gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+
+      return TRUE;
+    }
+  else
+    {
+      g_message ("Error: could not destroy pipeline. Closing call window");
+      gtk_widget_destroy (GTK_WIDGET (self));
+
+      return FALSE;
+    }
+}
+
+static void
+reset_details_pane (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
+  gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
+  gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
+  gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
+}
+
+static gboolean
+empathy_call_window_disconnected (EmpathyCallWindow *self,
+    gboolean restart)
+{
+  gboolean could_disconnect = FALSE;
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  gboolean could_reset_pipeline;
+
+  /* Leave full screen mode if needed */
+  gtk_window_unfullscreen (GTK_WINDOW (self));
+
+  gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
+  gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
+
+  could_reset_pipeline = empathy_call_window_reset_pipeline (self);
+
+  if (priv->call_state == CONNECTING)
+      empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
+
+  if (priv->call_state != REDIALING)
+    priv->call_state = DISCONNECTED;
+
+  if (could_reset_pipeline)
+    {
+      g_mutex_lock (priv->lock);
+
+      g_timer_stop (priv->timer);
+
+      if (priv->timer_id != 0)
+        g_source_remove (priv->timer_id);
+      priv->timer_id = 0;
+
+      g_mutex_unlock (priv->lock);
+
+      if (!restart)
+        /* We are about to destroy the window, no need to update it or create
+         * a video preview */
+        return TRUE;
+
+      empathy_call_window_status_message (self, _("Disconnected"));
+
+      gtk_action_set_sensitive (priv->redial, TRUE);
+      gtk_widget_set_sensitive (priv->redial_button, TRUE);
+
+      /* Unsensitive the camera and mic button */
+      gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
+      gtk_action_set_sensitive (priv->action_camera_on, FALSE);
+      gtk_widget_set_sensitive (priv->mic_button, FALSE);
+
+      /* Be sure that the mic button is enabled */
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
+
+      if (priv->camera_state == CAMERA_STATE_ON)
+        {
+          /* Enable the 'preview' button as we are not sending atm. */
+          gtk_toggle_tool_button_set_active (
+              GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_preview), TRUE);
+        }
+      else if (priv->camera_state == CAMERA_STATE_PREVIEW)
+        {
+          /* Restart the preview with the new pipeline. */
+          display_video_preview (self, TRUE);
+        }
+
+      gtk_progress_bar_set_fraction (
+          GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
+
+      /* destroy the video output; it will be recreated when we'll redial */
+      disconnect_video_output_motion_handler (self);
+      if (priv->video_output != NULL)
+        gtk_widget_destroy (priv->video_output);
+      priv->video_output = NULL;
+
+      gtk_widget_show (priv->remote_user_avatar_widget);
+
+      reset_details_pane (self);
+
+      priv->sending_video = FALSE;
+      priv->call_started = FALSE;
+
+      could_disconnect = TRUE;
+
+      /* TODO: display the self avatar of the preview (depends if the "Always
+       * Show Video Preview" is enabled or not) */
+    }
+
+  return could_disconnect;
+}
+
+
+static void
+empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
+    gpointer user_data)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (empathy_call_window_disconnected (self, TRUE) &&
+      priv->call_state == REDIALING)
+      empathy_call_window_restart_call (self);
+}
+
+static gboolean
+empathy_call_window_sink_removed_cb (EmpathyCallHandler *handler,
+    GstPad *sink,
+    FsMediaType media_type,
+    EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  DEBUG ("removing content");
+
+  /*
+   * This assumes that there is only one video stream per channel...
+   */
+
+  if ((guint) media_type == FS_MEDIA_TYPE_VIDEO)
+    {
+      if (priv->funnel != NULL)
+        {
+          GstElement *output;
+
+          output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
+              (priv->video_output));
+
+          gst_element_set_state (output, GST_STATE_NULL);
+          gst_element_set_state (priv->funnel, GST_STATE_NULL);
+
+          gst_bin_remove (GST_BIN (priv->pipeline), output);
+          gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
+          priv->funnel = NULL;
+          return TRUE;
+        }
+    }
+  else if (media_type == FS_MEDIA_TYPE_AUDIO)
+    {
+      if (priv->audio_output != NULL)
+        {
+          gst_element_set_state (priv->audio_output, GST_STATE_NULL);
+
+          gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
+          priv->audio_output = NULL;
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+/* Called with global lock held */
+static GstPad *
+empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstPad *pad;
+  GstElement *output;
+
+  if (priv->funnel == NULL)
+    {
+      output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
+        (priv->video_output));
+
+      priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
+
+      if (!priv->funnel)
+        {
+          g_warning ("Could not create fsfunnel");
+          return NULL;
+        }
+
+      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
+        {
+          gst_object_unref (priv->funnel);
+          priv->funnel = NULL;
+          g_warning ("Could  not add funnel to pipeline");
+          return NULL;
+        }
+
+      if (!gst_bin_add (GST_BIN (priv->pipeline), output))
+        {
+          g_warning ("Could not add the video output widget to the pipeline");
+          goto error;
+        }
+
+      if (!gst_element_link (priv->funnel, output))
+        {
+          g_warning ("Could not link output sink to funnel");
+          goto error_output_added;
+        }
+
+      if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start video sink");
+          goto error_output_added;
+        }
+
+      if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start funnel");
+          goto error_output_added;
+        }
+    }
+
+  pad = gst_element_get_request_pad (priv->funnel, "sink%d");
+
+  if (!pad)
+    g_warning ("Could not get request pad from funnel");
+
+  return pad;
+
+
+ error_output_added:
+
+  gst_element_set_locked_state (priv->funnel, TRUE);
+  gst_element_set_locked_state (output, TRUE);
+
+  gst_element_set_state (priv->funnel, GST_STATE_NULL);
+  gst_element_set_state (output, GST_STATE_NULL);
+
+  gst_bin_remove (GST_BIN (priv->pipeline), output);
+  gst_element_set_locked_state (output, FALSE);
+
+ error:
+
+  gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
+  priv->funnel = NULL;
+
+  return NULL;
+}
+
+/* Called with global lock held */
+static GstPad *
+empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstPad *pad;
+  GstPadTemplate *template;
+
+  if (priv->audio_output == NULL)
+    {
+      priv->audio_output = empathy_audio_sink_new ();
+      g_object_ref_sink (priv->audio_output);
+
+      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
+        {
+          g_warning ("Could not add audio sink to pipeline");
+          g_object_unref (priv->audio_output);
+          goto error_add_output;
+        }
+
+      if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start audio sink");
+          goto error;
+        }
+    }
+
+  template = gst_element_class_get_pad_template (
+    GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
+
+  pad = gst_element_request_pad (priv->audio_output,
+    template, NULL, NULL);
+
+  if (pad == NULL)
+    {
+      g_warning ("Could not get sink pad from sink");
+      return NULL;
+    }
+
+  return pad;
+
+error:
+  gst_element_set_locked_state (priv->audio_output, TRUE);
+  gst_element_set_state (priv->audio_output, GST_STATE_NULL);
+  gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
+  priv->audio_output = NULL;
+
+error_add_output:
+
+  return NULL;
+}
+
+static gboolean
+empathy_call_window_update_timer (gpointer user_data)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  gchar *str;
+  gdouble time_;
+
+  time_ = g_timer_elapsed (priv->timer, NULL);
+
+  /* Translators: 'status - minutes:seconds' the caller has been connected */
+  str = g_strdup_printf (_("%s â€” %d:%02dm"),
+      priv->call_state == HELD ? _("On hold") : _("Connected"),
+      (int) time_ / 60, (int) time_ % 60);
+  empathy_call_window_status_message (self, str);
+  g_free (str);
+
+  return TRUE;
+}
+
+#if 0
+static void
+display_error (EmpathyCallWindow *self,
+    TpyCallChannel *call,
+    const gchar *img,
+    const gchar *title,
+    const gchar *desc,
+    const gchar *details)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GtkWidget *info_bar;
+  GtkWidget *content_area;
+  GtkWidget *hbox;
+  GtkWidget *vbox;
+  GtkWidget *image;
+  GtkWidget *label;
+  gchar *txt;
+
+  /* Create info bar */
+  info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+      NULL);
+
+  gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
+
+  content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
+
+  /* hbox containing the image and the messages vbox */
+  hbox = gtk_hbox_new (FALSE, 3);
+  gtk_container_add (GTK_CONTAINER (content_area), hbox);
+
+  /* Add image */
+  image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
+  gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+
+  /* vbox containing the main message and the details expander */
+  vbox = gtk_vbox_new (FALSE, 3);
+  gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
+
+  /* Add text */
+  txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
+
+  label = gtk_label_new (NULL);
+  gtk_label_set_markup (GTK_LABEL (label), txt);
+  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+  g_free (txt);
+
+  gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
+
+  /* Add details */
+  if (details != NULL)
+    {
+      GtkWidget *expander;
+
+      expander = gtk_expander_new (_("Technical Details"));
+
+      txt = g_strdup_printf ("<i>%s</i>", details);
+
+      label = gtk_label_new (NULL);
+      gtk_label_set_markup (GTK_LABEL (label), txt);
+      gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+      gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+      g_free (txt);
+
+      gtk_container_add (GTK_CONTAINER (expander), label);
+      gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
+    }
+
+  g_signal_connect (info_bar, "response",
+      G_CALLBACK (gtk_widget_destroy), NULL);
+
+  gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
+      FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+  gtk_widget_show_all (info_bar);
+}
+
+static gchar *
+media_stream_error_to_txt (EmpathyCallWindow *self,
+    TpyCallChannel *call,
+    gboolean audio,
+    TpMediaStreamError error)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  const gchar *cm = NULL;
+  gchar *url;
+  gchar *result;
+
+  switch (error)
+    {
+      case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
+        if (audio)
+          return g_strdup_printf (
+              _("%s's software does not understand any of the audio formats "
+                "supported by your computer"),
+            empathy_contact_get_alias (priv->contact));
+        else
+          return g_strdup_printf (
+              _("%s's software does not understand any of the video formats "
+                "supported by your computer"),
+            empathy_contact_get_alias (priv->contact));
+
+      case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
+        return g_strdup_printf (
+            _("Can't establish a connection to %s. "
+              "One of you might be on a network that does not allow "
+              "direct connections."),
+          empathy_contact_get_alias (priv->contact));
+
+      case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
+          return g_strdup (_("There was a failure on the network"));
+
+      case TP_MEDIA_STREAM_ERROR_NO_CODECS:
+        if (audio)
+          return g_strdup (_("The audio formats necessary for this call "
+                "are not installed on your computer"));
+        else
+          return g_strdup (_("The video formats necessary for this call "
+                "are not installed on your computer"));
+
+      case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
+        tp_connection_parse_object_path (
+            tp_channel_borrow_connection (TP_CHANNEL (call)),
+            NULL, &cm);
+
+        url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
+            "product=Telepathy&amp;component=%s", cm);
+
+        result = g_strdup_printf (
+            _("Something unexpected happened in a Telepathy component. "
+              "Please <a href=\"%s\">report this bug</a> and attach "
+              "logs gathered from the 'Debug' window in the Help menu."), url);
+
+        g_free (url);
+        g_free (cm);
+        return result;
+
+      case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
+        return g_strdup (_("There was a failure in the call engine"));
+
+      case TP_MEDIA_STREAM_ERROR_EOS:
+        return g_strdup (_("The end of the stream was reached"));
+
+      case TP_MEDIA_STREAM_ERROR_UNKNOWN:
+      default:
+        return NULL;
+    }
+}
+
+static void
+empathy_call_window_stream_error (EmpathyCallWindow *self,
+    TpyCallChannel *call,
+    gboolean audio,
+    guint code,
+    const gchar *msg,
+    const gchar *icon,
+    const gchar *title)
+{
+  gchar *desc;
+
+  desc = media_stream_error_to_txt (self, call, audio, code);
+  if (desc == NULL)
+    {
+      /* No description, use the error message. That's not great as it's not
+       * localized but it's better than nothing. */
+      display_error (self, call, icon, title, msg, NULL);
+    }
+  else
+    {
+      display_error (self, call, icon, title, desc, msg);
+      g_free (desc);
+    }
+}
+
+static void
+empathy_call_window_audio_stream_error (TpyCallChannel *call,
+    guint code,
+    const gchar *msg,
+    EmpathyCallWindow *self)
+{
+  empathy_call_window_stream_error (self, call, TRUE, code, msg,
+      "gnome-stock-mic", _("Can't establish audio stream"));
+}
+
+static void
+empathy_call_window_video_stream_error (TpyCallChannel *call,
+    guint code,
+    const gchar *msg,
+    EmpathyCallWindow *self)
+{
+  empathy_call_window_stream_error (self, call, FALSE, code, msg,
+      "camera-web", _("Can't establish video stream"));
+}
+#endif
+
+static void
+empathy_call_window_state_changed_cb (EmpathyCallHandler *handler,
+    TpyCallState state,
+    EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  TpyCallChannel *call;
+  gboolean can_send_video;
+
+  if (state != TPY_CALL_STATE_ACCEPTED)
+    return;
+
+  if (priv->call_state == CONNECTED)
+    return;
+
+  g_timer_start (priv->timer);
+  priv->call_state = CONNECTED;
+
+  empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
+
+  can_send_video = priv->video_input != NULL &&
+    empathy_contact_can_voip_video (priv->contact);
+
+  g_object_get (priv->handler, "call-channel", &call, NULL);
+
+  if (tpy_call_channel_has_dtmf (call))
+    gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
+
+  if (priv->video_input == NULL)
+    empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
+
+  gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
+  gtk_action_set_sensitive (priv->action_camera_on, can_send_video);
+
+  gtk_action_set_sensitive (priv->redial, FALSE);
+  gtk_widget_set_sensitive (priv->redial_button, FALSE);
+
+  gtk_widget_set_sensitive (priv->mic_button, TRUE);
+
+  gtk_widget_hide (priv->video_output);
+  gtk_widget_show (priv->remote_user_avatar_widget);
+
+  g_object_unref (call);
+
+  g_mutex_lock (priv->lock);
+
+  priv->timer_id = g_timeout_add_seconds (1,
+    empathy_call_window_update_timer, self);
+
+  g_mutex_unlock (priv->lock);
+
+  empathy_call_window_update_timer (self);
+
+  gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
+}
+
+static gboolean
+emapthy_call_window_show_video_output_cb (gpointer user_data)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+
+  gtk_widget_hide (self->priv->remote_user_avatar_widget);
+  gtk_widget_show (self->priv->video_output);
+
+  return FALSE;
+}
+
+/* Called from the streaming thread */
+static gboolean
+empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
+  GstPad *src, guint media_type, gpointer user_data)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  gboolean retval = FALSE;
+
+  GstPad *pad;
+
+  g_mutex_lock (priv->lock);
+
+  switch (media_type)
+    {
+      case TP_MEDIA_STREAM_TYPE_AUDIO:
+        pad = empathy_call_window_get_audio_sink_pad (self);
+        break;
+      case TP_MEDIA_STREAM_TYPE_VIDEO:
+        g_idle_add (emapthy_call_window_show_video_output_cb, self);
+        pad = empathy_call_window_get_video_sink_pad (self);
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  if (pad == NULL)
+    goto out;
+
+  if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
+      g_warning ("Could not link %s sink pad",
+          media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
+  else
+      retval = TRUE;
+
+  gst_object_unref (pad);
+
+ out:
+
+  /* If no sink could be linked, try to add fakesink to prevent the whole call
+   * aborting */
+
+  if (!retval)
+    {
+      GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
+
+      if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
+        {
+          GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
+          if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
+              GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
+            {
+              gst_element_set_locked_state (fakesink, TRUE);
+              gst_element_set_state (fakesink, GST_STATE_NULL);
+              gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
+            }
+          else
+            {
+              DEBUG ("Could not link real sink, linked fakesink instead");
+            }
+          gst_object_unref (sinkpad);
+        }
+      else
+        {
+          gst_object_unref (fakesink);
+        }
+    }
+
+
+  g_mutex_unlock (priv->lock);
+
+  return TRUE;
+}
+
+static gboolean
+empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
+  GstPad *sink, FsMediaType media_type, gpointer user_data)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstPad *pad;
+  gboolean retval = FALSE;
+
+  switch (media_type)
+    {
+      case FS_MEDIA_TYPE_AUDIO:
+        if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
+          {
+            g_warning ("Could not add audio source to pipeline");
+            break;
+          }
+
+        pad = gst_element_get_static_pad (priv->audio_input, "src");
+        if (!pad)
+          {
+            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
+            g_warning ("Could not get source pad from audio source");
+            break;
+          }
+
+        if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
+          {
+            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
+            g_warning ("Could not link audio source to farsight");
+            break;
+          }
+
+        if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+          {
+            g_warning ("Could not start audio source");
+            gst_element_set_state (priv->audio_input, GST_STATE_NULL);
+            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
+            break;
+          }
+
+        retval = TRUE;
+        break;
+      case FS_MEDIA_TYPE_VIDEO:
+        if (priv->video_tee != NULL)
+          {
+            pad = gst_element_get_request_pad (priv->video_tee, "src%d");
+            if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
+              {
+                g_warning ("Could not link video source input pipeline");
+                break;
+              }
+            gst_object_unref (pad);
+          }
+
+        retval = TRUE;
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  return retval;
+}
+
+static void
+empathy_call_window_remove_video_input (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstElement *preview;
+
+  disable_camera (self);
+
+  DEBUG ("remove video input");
+  preview = empathy_video_widget_get_element (
+    EMPATHY_VIDEO_WIDGET (priv->video_preview));
+
+  gst_element_set_state (priv->video_input, GST_STATE_NULL);
+  gst_element_set_state (priv->video_tee, GST_STATE_NULL);
+  gst_element_set_state (preview, GST_STATE_NULL);
+
+  gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
+    preview, NULL);
+
+  g_object_unref (priv->video_input);
+  priv->video_input = NULL;
+  g_object_unref (priv->video_tee);
+  priv->video_tee = NULL;
+  gtk_widget_destroy (priv->video_preview);
+  priv->video_preview = NULL;
+
+  gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
+  gtk_action_set_sensitive (priv->action_camera_on, FALSE);
+  gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
+}
+
+static void
+start_call (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  priv->call_started = TRUE;
+  empathy_call_handler_start_call (priv->handler,
+      gtk_get_current_event_time ());
+
+  if (empathy_call_handler_has_initial_video (priv->handler))
+    {
+      TpyCallChannel *call;
+      TpySendingState s;
+
+      g_object_get (priv->handler, "call-channel", &call, NULL);
+      s = tpy_call_channel_get_video_state (call);
+
+      if (s == TPY_SENDING_STATE_PENDING_SEND ||
+          s == TPY_SENDING_STATE_SENDING)
+        {
+          /* Enable 'send video' buttons and display the preview */
+          gtk_toggle_tool_button_set_active (
+            GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
+            TRUE);
+        }
+      else
+        {
+          gtk_toggle_tool_button_set_active (
+            GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off),
+            TRUE);
+
+          if (priv->video_preview == NULL)
+            {
+              create_video_preview (self);
+              add_video_preview_to_pipeline (self);
+            }
+        }
+
+      g_object_unref (call);
+    }
+}
+
+static gboolean
+empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
+  gpointer user_data)
+{
+  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GstState newstate;
+
+  empathy_call_handler_bus_message (priv->handler, bus, message);
+
+  switch (GST_MESSAGE_TYPE (message))
+    {
+      case GST_MESSAGE_STATE_CHANGED:
+        if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
+          {
+            gst_message_parse_state_changed (message, NULL, &newstate, NULL);
+            if (newstate == GST_STATE_PAUSED)
+                empathy_call_window_setup_video_input (self);
+          }
+        if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
+            !priv->call_started)
+          {
+            gst_message_parse_state_changed (message, NULL, &newstate, NULL);
+            if (newstate == GST_STATE_PAUSED)
+              {
+                gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+                priv->pipeline_playing = TRUE;
+
+                if (priv->start_call_when_playing)
+                  start_call (self);
+              }
+          }
+        break;
+      case GST_MESSAGE_ERROR:
+        {
+          GError *error = NULL;
+          GstElement *gst_error;
+          gchar *debug;
+
+          gst_message_parse_error (message, &error, &debug);
+          gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
+
+          g_message ("Element error: %s -- %s\n", error->message, debug);
+
+          if (g_str_has_prefix (gst_element_get_name (gst_error),
+                VIDEO_INPUT_ERROR_PREFIX))
+            {
+              /* Remove the video input and continue */
+              if (priv->video_input != NULL)
+                empathy_call_window_remove_video_input (self);
+              gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+            }
+          else
+            {
+              empathy_call_window_disconnected (self, TRUE);
+            }
+          g_error_free (error);
+          g_free (debug);
+        }
+      case GST_MESSAGE_UNKNOWN:
+      case GST_MESSAGE_EOS:
+      case GST_MESSAGE_WARNING:
+      case GST_MESSAGE_INFO:
+      case GST_MESSAGE_TAG:
+      case GST_MESSAGE_BUFFERING:
+      case GST_MESSAGE_STATE_DIRTY:
+      case GST_MESSAGE_STEP_DONE:
+      case GST_MESSAGE_CLOCK_PROVIDE:
+      case GST_MESSAGE_CLOCK_LOST:
+      case GST_MESSAGE_NEW_CLOCK:
+      case GST_MESSAGE_STRUCTURE_CHANGE:
+      case GST_MESSAGE_STREAM_STATUS:
+      case GST_MESSAGE_APPLICATION:
+      case GST_MESSAGE_ELEMENT:
+      case GST_MESSAGE_SEGMENT_START:
+      case GST_MESSAGE_SEGMENT_DONE:
+      case GST_MESSAGE_DURATION:
+      case GST_MESSAGE_ANY:
+      default:
+        break;
+    }
+
+  return TRUE;
+}
+
+static void
+empathy_call_window_members_changed_cb (TpyCallChannel *call,
+    GHashTable *members,
+    EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  GHashTableIter iter;
+  gpointer key, value;
+  gboolean held = FALSE;
+
+  g_hash_table_iter_init (&iter, members);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      if (GPOINTER_TO_INT (value) & TPY_CALL_MEMBER_FLAG_HELD)
+        {
+          /* This assumes this is a 1-1 call, otherwise one participant
+           * putting the call on hold wouldn't mean the call is on hold
+           * for everyone. */
+          held = TRUE;
+          break;
+        }
+    }
+
+  if (held)
+    priv->call_state = HELD;
+  else if (priv->call_state == HELD)
+    priv->call_state = CONNECTED;
+}
+
+static void
+call_handler_notify_call_cb (EmpathyCallHandler *handler,
+    GParamSpec *spec,
+    EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  TpyCallChannel *call;
+
+  g_object_get (priv->handler, "call-channel", &call, NULL);
+  if (call == NULL)
+    return;
+
+/* FIXME
+  tp_g_signal_connect_object (call, "audio-stream-error",
+      G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
+  tp_g_signal_connect_object (call, "video-stream-error",
+      G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
+*/
+
+  tp_g_signal_connect_object (call, "members-changed",
+      G_CALLBACK (empathy_call_window_members_changed_cb), self, 0);
+
+  g_object_unref (call);
+}
+
+static void
+empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  TpyCallChannel *call;
+
+  g_signal_connect (priv->handler, "state-changed",
+    G_CALLBACK (empathy_call_window_state_changed_cb), window);
+  g_signal_connect (priv->handler, "conference-added",
+    G_CALLBACK (empathy_call_window_conference_added_cb), window);
+  g_signal_connect (priv->handler, "conference-removed",
+    G_CALLBACK (empathy_call_window_conference_removed_cb), window);
+  g_signal_connect (priv->handler, "closed",
+    G_CALLBACK (empathy_call_window_channel_closed_cb), window);
+  g_signal_connect (priv->handler, "src-pad-added",
+    G_CALLBACK (empathy_call_window_src_added_cb), window);
+  g_signal_connect (priv->handler, "sink-pad-added",
+    G_CALLBACK (empathy_call_window_sink_added_cb), window);
+  g_signal_connect (priv->handler, "sink-pad-removed",
+    G_CALLBACK (empathy_call_window_sink_removed_cb), window);
+
+  g_object_get (priv->handler, "call-channel", &call, NULL);
+  if (call != NULL)
+    {
+      call_handler_notify_call_cb (priv->handler, NULL, window);
+      g_object_unref (call);
+    }
+  else
+    {
+      /* call-channel doesn't exist yet, we'll connect signals once it has been
+       * set */
+      g_signal_connect (priv->handler, "notify::call-channel",
+        G_CALLBACK (call_handler_notify_call_cb), window);
+    }
+
+  gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+}
+
+static gboolean
+empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
+  EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->pipeline != NULL)
+    {
+      if (priv->bus_message_source_id != 0)
+        {
+          g_source_remove (priv->bus_message_source_id);
+          priv->bus_message_source_id = 0;
+        }
+
+      gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+    }
+
+  if (priv->call_state == CONNECTING)
+    empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
+
+  return FALSE;
+}
+
+static void
+show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
+{
+  GtkWidget *menu;
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  menu = gtk_ui_manager_get_widget (priv->ui_manager,
+            "/menubar1");
+
+  if (set_fullscreen)
+    {
+      gtk_widget_hide (priv->sidebar);
+      gtk_widget_hide (menu);
+      gtk_widget_hide (priv->vbox);
+      gtk_widget_hide (priv->statusbar);
+      gtk_widget_hide (priv->toolbar);
+    }
+  else
+    {
+      if (priv->sidebar_was_visible_before_fs)
+        gtk_widget_show (priv->sidebar);
+
+      gtk_widget_show (menu);
+      gtk_widget_show (priv->vbox);
+      gtk_widget_show (priv->statusbar);
+      gtk_widget_show (priv->toolbar);
+
+      gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
+          priv->original_height_before_fs);
+    }
+}
+
+static void
+show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
+      set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
+  gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
+      set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
+
+  if (priv->video_output != NULL)
+    {
+      gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
+          priv->video_output, TRUE, TRUE,
+          set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
+          GTK_PACK_START);
+    }
+
+  gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
+      priv->vbox, TRUE, TRUE,
+      set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
+      GTK_PACK_START);
+}
+
+static gboolean
+empathy_call_window_state_event_cb (GtkWidget *widget,
+  GdkEventWindowState *event, EmpathyCallWindow *window)
+{
+  if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
+    {
+      EmpathyCallWindowPriv *priv = GET_PRIV (window);
+      gboolean set_fullscreen = event->new_window_state &
+        GDK_WINDOW_STATE_FULLSCREEN;
+
+      if (set_fullscreen)
+        {
+          gboolean sidebar_was_visible;
+          GtkAllocation allocation;
+          gint original_width, original_height;
+
+          gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
+          original_width = allocation.width;
+          original_height = allocation.height;
+
+          g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
+
+          priv->sidebar_was_visible_before_fs = sidebar_was_visible;
+          priv->original_width_before_fs = original_width;
+          priv->original_height_before_fs = original_height;
+
+          if (priv->video_output_motion_handler_id == 0 &&
+                priv->video_output != NULL)
+            {
+              priv->video_output_motion_handler_id = g_signal_connect (
+                  G_OBJECT (priv->video_output), "motion-notify-event",
+                  G_CALLBACK (empathy_call_window_video_output_motion_notify),
+                  window);
+            }
+        }
+      else
+        {
+          disconnect_video_output_motion_handler (window);
+        }
+
+      empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
+          set_fullscreen);
+      show_controls (window, set_fullscreen);
+      show_borders (window, set_fullscreen);
+      gtk_action_set_stock_id (priv->menu_fullscreen,
+          (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
+      priv->is_fullscreen = set_fullscreen;
+  }
+
+  return FALSE;
+}
+
+static void
+empathy_call_window_update_dialpad_menu (EmpathyCallWindow *window,
+    gboolean toggled)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  g_signal_handlers_block_by_func (priv->menu_dialpad,
+      empathy_call_window_dialpad_cb, window);
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->menu_dialpad),
+      toggled);
+  g_signal_handlers_unblock_by_func (priv->menu_dialpad,
+      empathy_call_window_dialpad_cb, window);
+}
+
+static void
+empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
+  EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  GtkWidget *arrow;
+  int w, h, handle_size;
+  GtkAllocation allocation, sidebar_allocation;
+  gchar *page;
+  gboolean active, dialpad_shown;
+
+  gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
+  w = allocation.width;
+  h = allocation.height;
+
+  gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
+
+  gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
+  active = gtk_toggle_button_get_active (toggle);
+
+  if (active)
+    {
+      arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
+      gtk_widget_show (priv->sidebar);
+      w += sidebar_allocation.width + handle_size;
+    }
+  else
+    {
+      arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
+      w -= sidebar_allocation.width + handle_size;
+      gtk_widget_hide (priv->sidebar);
+    }
+
+  gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
+
+  if (w > 0 && h > 0)
+    gtk_window_resize (GTK_WINDOW (window), w, h);
+
+  /* Update the 'Dialpad' menu */
+  page = ev_sidebar_get_current_page (EV_SIDEBAR (priv->sidebar));
+  dialpad_shown = active && !tp_strdiff (page, "dialpad");
+  g_free (page);
+
+  empathy_call_window_update_dialpad_menu (window, dialpad_shown);
+}
+
+static void
+empathy_call_window_set_send_video (EmpathyCallWindow *window,
+  CameraState state)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  TpyCallChannel *call;
+
+  priv->sending_video = (state == CAMERA_STATE_ON);
+
+  if (state == CAMERA_STATE_PREVIEW ||
+      state == CAMERA_STATE_ON)
+    {
+      /* When we start sending video, we want to show the video preview by
+         default. */
+      display_video_preview (window, TRUE);
+    }
+  else
+    {
+      display_video_preview (window, FALSE);
+    }
+
+  if (priv->call_state != CONNECTED)
+    return;
+
+  g_object_get (priv->handler, "call-channel", &call, NULL);
+  DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
+  tpy_call_channel_send_video (call, priv->sending_video);
+  g_object_unref (call);
+}
+
+static void
+empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  gboolean active;
+
+  active = (gtk_toggle_tool_button_get_active (toggle));
+
+  if (active)
+    {
+      empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
+        priv->volume);
+      gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
+    }
+  else
+    {
+      /* TODO, Instead of setting the input volume to 0 we should probably
+       * stop sending but this would cause the audio call to drop if both
+       * sides mute at the same time on certain CMs AFAIK. Need to revisit this
+       * in the future. GNOME #574574
+       */
+      empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
+        0);
+      gtk_adjustment_set_value (priv->audio_input_adj, 0);
+    }
+}
+
+static void
+empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
+  EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
+    FALSE);
+}
+
+static void
+empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
+  EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
+    TRUE);
+}
+
+static void
+empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
+  const gchar *page,
+  EmpathyCallWindow *window)
+{
+  empathy_call_window_update_dialpad_menu (window,
+      !tp_strdiff (page, "dialpad"));
+}
+
+static void
+empathy_call_window_hangup_cb (gpointer object,
+                               EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  empathy_call_handler_stop_call (priv->handler);
+
+  if (empathy_call_window_disconnected (window, FALSE))
+    gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+static void
+empathy_call_window_restart_call (EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  /* Remove error info bars */
+  gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
+      (GtkCallback) gtk_widget_destroy, NULL);
+
+  create_video_output_widget (window);
+
+  g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
+      G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
+
+  /* While the call was disconnected, the input volume might have changed.
+   * However, since the audio_input source was destroyed, its volume has not
+   * been updated during that time. That's why we manually update it here */
+  empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
+
+  priv->outgoing = TRUE;
+  empathy_call_window_set_state_connecting (window);
+
+  if (priv->pipeline_playing)
+    start_call (window);
+  else
+    /* call will be started when the pipeline is ready */
+    priv->start_call_when_playing = TRUE;
+
+
+  empathy_call_window_setup_avatars (window, priv->handler);
+
+  gtk_action_set_sensitive (priv->redial, FALSE);
+  gtk_widget_set_sensitive (priv->redial_button, FALSE);
+}
+
+static void
+empathy_call_window_redial_cb (gpointer object,
+    EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->call_state == CONNECTED)
+    priv->call_state = REDIALING;
+
+  empathy_call_handler_stop_call (priv->handler);
+
+  if (priv->call_state != CONNECTED)
+    empathy_call_window_restart_call (window);
+}
+
+static void
+empathy_call_window_dialpad_cb (GtkToggleAction *menu,
+    EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  gboolean active;
+
+  active = gtk_toggle_action_get_active (menu);
+
+  if (active)
+    ev_sidebar_set_current_page (EV_SIDEBAR (priv->sidebar), "dialpad");
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
+      active);
+}
+
+static void
+empathy_call_window_fullscreen_cb (gpointer object,
+                                   EmpathyCallWindow *window)
+{
+  empathy_call_window_fullscreen_toggle (window);
+}
+
+static void
+empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->is_fullscreen)
+    gtk_window_unfullscreen (GTK_WINDOW (window));
+  else
+    gtk_window_fullscreen (GTK_WINDOW (window));
+}
+
+static gboolean
+empathy_call_window_video_button_press_cb (GtkWidget *video_output,
+  GdkEventButton *event, EmpathyCallWindow *window)
+{
+  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
+    {
+      empathy_call_window_video_menu_popup (window, event->button);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+empathy_call_window_key_press_cb (GtkWidget *video_output,
+  GdkEventKey *event, EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
+    {
+      /* Since we are in fullscreen mode, toggling will bring us back to
+         normal mode. */
+      empathy_call_window_fullscreen_toggle (window);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+empathy_call_window_video_output_motion_notify (GtkWidget *widget,
+    GdkEventMotion *event, EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->is_fullscreen)
+    {
+      empathy_call_window_fullscreen_show_popup (priv->fullscreen);
+      return TRUE;
+    }
+  return FALSE;
+}
+
+static void
+empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
+  guint button)
+{
+  GtkWidget *menu;
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  menu = gtk_ui_manager_get_widget (priv->ui_manager,
+            "/video-popup");
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
+      button, gtk_get_current_event_time ());
+  gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+}
+
+static void
+empathy_call_window_status_message (EmpathyCallWindow *window,
+  gchar *message)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->context_id == 0)
+    {
+      priv->context_id = gtk_statusbar_get_context_id (
+        GTK_STATUSBAR (priv->statusbar), "voip call status messages");
+    }
+  else
+    {
+      gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
+    }
+
+  gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
+    message);
+}
+
+static void
+empathy_call_window_volume_changed_cb (GtkScaleButton *button,
+  gdouble value, EmpathyCallWindow *window)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->audio_output == NULL)
+    return;
+
+  empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
+    value);
+}
+
+/* block all the signals related to camera control widgets. This is useful
+ * when we are manually updating the UI and so don't want to fire the
+ * callbacks */
+static void
+block_camera_control_signals (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  g_signal_handlers_block_by_func (priv->tool_button_camera_off,
+      tool_button_camera_off_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
+      tool_button_camera_preview_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->tool_button_camera_on,
+      tool_button_camera_on_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->action_camera_on,
+      action_camera_change_cb, self);
+}
+
+static void
+unblock_camera_control_signals (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
+      tool_button_camera_off_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
+      tool_button_camera_preview_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
+      tool_button_camera_on_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->action_camera_on,
+      action_camera_change_cb, self);
+}
diff --git a/src/empathy-call-window.h b/src/empathy-call-window.h
new file mode 100644 (file)
index 0000000..11237ff
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * empathy-call-window.h - Header for EmpathyCallWindow
+ * Copyright (C) 2008-2011 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __EMPATHY_CALL_WINDOW_H__
+#define __EMPATHY_CALL_WINDOW_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "empathy-call-handler.h"
+
+G_BEGIN_DECLS
+
+typedef struct _EmpathyCallWindow EmpathyCallWindow;
+typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
+typedef struct _EmpathyCallWindowClass EmpathyCallWindowClass;
+
+struct _EmpathyCallWindowClass {
+    GtkWindowClass parent_class;
+};
+
+struct _EmpathyCallWindow {
+    GtkWindow parent;
+    EmpathyCallWindowPriv *priv;
+};
+
+GType empathy_call_window_get_type (void);
+
+/* TYPE MACROS */
+#define EMPATHY_TYPE_CALL_WINDOW \
+  (empathy_call_window_get_type ())
+#define EMPATHY_CALL_WINDOW(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), EMPATHY_TYPE_CALL_WINDOW, \
+    EmpathyCallWindow))
+#define EMPATHY_CALL_WINDOW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), EMPATHY_TYPE_CALL_WINDOW, \
+    EmpathyCallWindowClass))
+#define EMPATHY_IS_CALL_WINDOW(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EMPATHY_TYPE_CALL_WINDOW))
+#define EMPATHY_IS_CALL_WINDOW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), EMPATHY_TYPE_CALL_WINDOW))
+#define EMPATHY_CALL_WINDOW_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_CALL_WINDOW, \
+    EmpathyCallWindowClass))
+
+EmpathyCallWindow *empathy_call_window_new (EmpathyCallHandler *handler);
+
+G_END_DECLS
+
+#endif /* #ifndef __EMPATHY_CALL_WINDOW_H__*/
index ecd00dfc1bdf9e19a01780daf3d3b36faecd53d4..0d32677f90e3c67863741fca4a4099481ab3004d 100644 (file)
             <property name="sensitive">False</property>
           </object>
         </child>
+        <child>
+          <object class="GtkToggleAction" id="menudialpad">
+            <property name="name">menudialpad</property>
+            <property name="label" translatable="yes">_Dialpad</property>
+          </object>
+        </child>
         <child>
           <object class="GtkAction" id="camera">
             <property name="name">camera</property>
@@ -82,6 +88,7 @@
         <menu action="call">
           <menuitem action="menuhangup"/>
           <menuitem action="menuredial"/>
+          <menuitem action="menudialpad"/>
         </menu>
         <menu action="camera">
           <menuitem action="action_camera_off"/>
diff --git a/src/empathy-call.c b/src/empathy-call.c
new file mode 100644 (file)
index 0000000..d5fa2d1
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2007-2011 Collabora Ltd.
+ *
+ * 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 St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ *          Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <telepathy-glib/debug-sender.h>
+
+#include <telepathy-yell/telepathy-yell.h>
+
+#include <libempathy-gtk/empathy-ui-utils.h>
+
+#include "empathy-call-window.h"
+#include "empathy-call-factory.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
+#include <libempathy/empathy-debug.h>
+
+#include <gst/gst.h>
+
+/* Exit after $TIMEOUT seconds if not displaying any call window */
+#define TIMEOUT 60
+
+#define EMPATHY_CALL_DBUS_NAME "org.gnome.Empathy.Call"
+
+static GtkApplication *app = NULL;
+static gboolean activated = FALSE;
+static gboolean use_timer = TRUE;
+
+static EmpathyCallFactory *call_factory = NULL;
+
+static void
+new_call_handler_cb (EmpathyCallFactory *factory,
+    EmpathyCallHandler *handler,
+    gboolean outgoing,
+    gpointer user_data)
+{
+  EmpathyCallWindow *window;
+
+  DEBUG ("Create a new call window");
+
+  window = empathy_call_window_new (handler);
+
+  g_application_hold (G_APPLICATION (app));
+
+  g_signal_connect_swapped (window, "destroy",
+      G_CALLBACK (g_application_release), app);
+
+  gtk_widget_show (GTK_WIDGET (window));
+}
+
+static void
+activate_cb (GApplication *application)
+{
+  GError *error = NULL;
+
+  if (activated)
+    return;
+
+  activated = TRUE;
+
+  if (!use_timer)
+    {
+      /* keep a 'ref' to the application */
+      g_application_hold (G_APPLICATION (app));
+    }
+
+  g_assert (call_factory == NULL);
+  call_factory = empathy_call_factory_initialise ();
+
+  g_signal_connect (G_OBJECT (call_factory), "new-call-handler",
+      G_CALLBACK (new_call_handler_cb), NULL);
+
+  if (!empathy_call_factory_register (call_factory, &error))
+    {
+      g_critical ("Failed to register Handler: %s", error->message);
+      g_error_free (error);
+    }
+}
+
+int
+main (int argc,
+    char *argv[])
+{
+  GOptionContext *optcontext;
+  GOptionEntry options[] = {
+      { NULL }
+  };
+#ifdef ENABLE_DEBUG
+  TpDebugSender *debug_sender;
+#endif
+  GError *error = NULL;
+  gint retval;
+
+  /* Init */
+  g_thread_init (NULL);
+
+  optcontext = g_option_context_new (N_("- Empathy Audio/Video Client"));
+  g_option_context_add_group (optcontext, gst_init_get_option_group ());
+  g_option_context_add_group (optcontext, gtk_get_option_group (TRUE));
+  g_option_context_add_main_entries (optcontext, options, GETTEXT_PACKAGE);
+
+  if (!g_option_context_parse (optcontext, &argc, &argv, &error)) {
+    g_print ("%s\nRun '%s --help' to see a full list of available command "
+        "line options.\n",
+        error->message, argv[0]);
+    g_warning ("Error in empathy-call init: %s", error->message);
+    return EXIT_FAILURE;
+  }
+
+  g_option_context_free (optcontext);
+
+  tpy_cli_init ();
+
+  empathy_gtk_init ();
+  g_set_application_name (_("Empathy Audio/Video Client"));
+  g_setenv ("PULSE_PROP_media.role", "phone", TRUE);
+
+  /* Make empathy and empathy-call appear as the same app in gnome-shell */
+  gdk_set_program_class ("Empathy");
+  gtk_window_set_default_icon_name ("empathy");
+  textdomain (GETTEXT_PACKAGE);
+
+  app = gtk_application_new (EMPATHY_CALL_DBUS_NAME, G_APPLICATION_FLAGS_NONE);
+  g_signal_connect (app, "activate", G_CALLBACK (activate_cb), NULL);
+
+#ifdef ENABLE_DEBUG
+  /* Set up debug sender */
+  debug_sender = tp_debug_sender_dup ();
+  g_log_set_default_handler (tp_debug_sender_log_handler, G_LOG_DOMAIN);
+#endif
+
+  if (g_getenv ("EMPATHY_PERSIST") != NULL)
+    {
+      DEBUG ("Disable timer");
+
+      use_timer = FALSE;
+    }
+
+  /* the inactivity timeout can only be set while the application is held */
+  g_application_hold (G_APPLICATION (app));
+  g_application_set_inactivity_timeout (G_APPLICATION (app), TIMEOUT * 1000);
+  g_application_release (G_APPLICATION (app));
+
+  retval = g_application_run (G_APPLICATION (app), argc, argv);
+
+  g_object_unref (app);
+  tp_clear_object (&call_factory);
+
+#ifdef ENABLE_DEBUG
+  g_object_unref (debug_sender);
+#endif
+
+  return retval;
+}
index 0dcc2be8a99e910b66e9373357ec92b796f30ff1..c413be020c666160db3c6e43f2f9ef5482a8dac2 100644 (file)
@@ -29,6 +29,8 @@
 #include <telepathy-glib/interfaces.h>
 #include <telepathy-glib/simple-approver.h>
 
+#include <telepathy-yell/telepathy-yell.h>
+
 #include <libempathy/empathy-channel-factory.h>
 #include <libempathy/empathy-presence-manager.h>
 #include <libempathy/empathy-tp-contact-factory.h>
@@ -56,6 +58,8 @@
 
 #define NOTIFICATION_TIMEOUT 2 /* seconds */
 
+#define ACCEPT_WITHOUT_VIDEO 1
+
 /* The time interval in milliseconds between 2 incoming rings */
 #define MS_BETWEEN_RING 500
 
@@ -233,6 +237,7 @@ event_manager_add (EmpathyEventManager *manager,
   event->public.header = g_strdup (header);
   event->public.message = g_strdup (message);
   event->public.must_ack = (func != NULL);
+  event->public.handler_instance = approval->handler_instance;
   event->inhibit = FALSE;
   event->func = func;
   event->user_data = user_data;
@@ -406,6 +411,13 @@ reject_channel_claim_cb (GObject *source,
     {
       empathy_tp_streamed_media_close (user_data);
     }
+  else if (TPY_IS_CALL_CHANNEL (user_data))
+    {
+      tpy_call_channel_hangup_async (user_data,
+          TPY_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+          "", "", NULL, NULL);
+      tp_channel_close_async (user_data, NULL, NULL);
+    }
   else if (EMPATHY_IS_TP_CHAT (user_data))
     {
       empathy_tp_chat_leave (user_data, "");
@@ -475,14 +487,20 @@ event_manager_call_window_confirmation_dialog_response_cb (GtkDialog *dialog,
   gtk_widget_destroy (approval->dialog);
   approval->dialog = NULL;
 
-  if (response != GTK_RESPONSE_ACCEPT)
+  if (response == GTK_RESPONSE_ACCEPT)
     {
-      reject_approval (approval);
+      event_manager_approval_approve (approval);
     }
-  else
+  else if (response == ACCEPT_WITHOUT_VIDEO)
     {
+      tpy_call_channel_send_video (TPY_CALL_CHANNEL (approval->main_channel),
+        FALSE);
       event_manager_approval_approve (approval);
     }
+  else
+    {
+      reject_approval (approval);
+    }
 }
 
 static void
@@ -491,9 +509,9 @@ event_channel_process_voip_func (EventPriv *event)
   GtkWidget *dialog;
   GtkWidget *button;
   GtkWidget *image;
-  EmpathyTpStreamedMedia *call;
   gboolean video;
   gchar *title;
+  EmpathyEventType etype = event->public.type;
 
   if (event->approval->dialog != NULL)
     {
@@ -501,9 +519,23 @@ event_channel_process_voip_func (EventPriv *event)
       return;
     }
 
-  call = EMPATHY_TP_STREAMED_MEDIA (event->approval->handler_instance);
-
-  video = empathy_tp_streamed_media_has_initial_video (call);
+  if (etype == EMPATHY_EVENT_TYPE_VOIP)
+    {
+      EmpathyTpStreamedMedia *call;
+      call = EMPATHY_TP_STREAMED_MEDIA (event->approval->handler_instance);
+      video = empathy_tp_streamed_media_has_initial_video (call);
+    }
+  else if (etype == EMPATHY_EVENT_TYPE_CALL)
+    {
+      TpyCallChannel *call;
+      call = TPY_CALL_CHANNEL (event->approval->handler_instance);
+      g_object_get (G_OBJECT (call), "initial-video", &video, NULL);
+    }
+  else
+    {
+      g_warning ("Unknown event type: %d", event->public.type);
+      return;
+    }
 
   dialog = gtk_message_dialog_new (NULL, 0,
       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
@@ -545,15 +577,26 @@ event_channel_process_voip_func (EventPriv *event)
     GTK_ICON_SIZE_BUTTON);
   gtk_button_set_image (GTK_BUTTON (button), image);
 
+  if (video && etype == EMPATHY_EVENT_TYPE_CALL)
+    {
+      button = gtk_dialog_add_button (GTK_DIALOG (dialog),
+        _("_Answer"), ACCEPT_WITHOUT_VIDEO);
+
+      image = gtk_image_new_from_icon_name ("call-start",
+        GTK_ICON_SIZE_BUTTON);
+      gtk_button_set_image (GTK_BUTTON (button), image);
+    }
+
   button = gtk_dialog_add_button (GTK_DIALOG (dialog),
-      _("_Answer"), GTK_RESPONSE_ACCEPT);
+    video ? _("_Answer with video") : _("_Answer"), GTK_RESPONSE_ACCEPT);
 
-  image = gtk_image_new_from_icon_name ("call-start", GTK_ICON_SIZE_BUTTON);
+  image = gtk_image_new_from_icon_name ("call-start",
+        GTK_ICON_SIZE_BUTTON);
   gtk_button_set_image (GTK_BUTTON (button), image);
 
   g_signal_connect (dialog, "response",
-      G_CALLBACK (event_manager_call_window_confirmation_dialog_response_cb),
-      event->approval);
+    G_CALLBACK (event_manager_call_window_confirmation_dialog_response_cb),
+    event->approval);
 
   gtk_widget_show (dialog);
 
@@ -613,7 +656,8 @@ event_manager_approval_done (EventManagerApproval *approval)
 
       channel_type = tp_channel_get_channel_type_id (approval->main_channel);
 
-      if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA)
+      if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA ||
+          channel_type == TPY_IFACE_QUARK_CHANNEL_TYPE_CALL)
         {
           priv->ringing--;
           if (priv->ringing == 0)
@@ -650,6 +694,79 @@ cdo_invalidated_cb (TpProxy *cdo,
   event_manager_approval_done (approval);
 }
 
+static void
+event_manager_call_state_changed_cb (TpyCallChannel *call,
+  TpyCallState state,
+  TpyCallFlags flags,
+   const GValueArray *call_state_reason,
+  GHashTable *call_state_details,
+  EventManagerApproval *approval)
+{
+  if (state == TPY_CALL_STATE_ENDED)
+    {
+      DEBUG ("Call ended, seems we missed it :/");
+      reject_approval (approval);
+    }
+}
+
+static void
+event_manager_call_channel_got_contact_cb (TpConnection *connection,
+                                 EmpathyContact *contact,
+                                 const GError *error,
+                                 gpointer user_data,
+                                 GObject *object)
+{
+  EventManagerApproval *approval = (EventManagerApproval *) user_data;
+  EmpathyEventManagerPriv *priv = GET_PRIV (approval->manager);
+  GtkWidget *window;
+  TpyCallChannel *call;
+  gchar *header;
+  gboolean video;
+
+  call = TPY_CALL_CHANNEL (approval->handler_instance);
+
+  if (error != NULL)
+    {
+      DEBUG ("Can't get the contact for the call.. Rejecting?");
+      reject_approval (approval);
+      return;
+    }
+
+  if (tpy_call_channel_get_state (call, NULL, NULL) == TPY_CALL_STATE_ENDED)
+    {
+      DEBUG ("Call already ended, seems we missed it :/");
+      reject_approval (approval);
+      return;
+    }
+
+  approval->handler = g_signal_connect (call, "state-changed",
+    G_CALLBACK (event_manager_call_state_changed_cb), approval);
+
+  window = empathy_main_window_dup ();
+  approval->contact = g_object_ref (contact);
+
+  g_object_get (G_OBJECT (call), "initial-video", &video, NULL);
+
+  header = g_strdup_printf (
+    video ? _("Incoming video call from %s") :_("Incoming call from %s"),
+    empathy_contact_get_alias (approval->contact));
+
+  event_manager_add (approval->manager, NULL,
+      approval->contact, EMPATHY_EVENT_TYPE_CALL,
+      video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
+      header, NULL, approval,
+      event_channel_process_voip_func, NULL);
+
+  g_free (header);
+
+  priv->ringing++;
+  if (priv->ringing == 1)
+    empathy_sound_manager_start_playing (priv->sound_mgr, window,
+        EMPATHY_SOUND_PHONE_INCOMING, MS_BETWEEN_RING);
+
+  g_object_unref (window);
+}
+
 static void
 event_manager_media_channel_got_contact (EventManagerApproval *approval)
 {
@@ -884,6 +1001,7 @@ find_main_channel (GList *channels)
       channel_type = tp_channel_get_channel_type_id (channel);
 
       if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA ||
+          channel_type == TPY_IFACE_QUARK_CHANNEL_TYPE_CALL ||
           channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_FILE_TRANSFER ||
           channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION)
         return channel;
@@ -985,7 +1103,8 @@ approve_channels (TpSimpleApprover *approver,
   else if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA)
     {
       EmpathyContact *contact;
-      EmpathyTpStreamedMedia *call = empathy_tp_streamed_media_new (account, channel);
+      EmpathyTpStreamedMedia *call = empathy_tp_streamed_media_new (account,
+        channel);
 
       approval->handler_instance = G_OBJECT (call);
 
@@ -1004,6 +1123,19 @@ approve_channels (TpSimpleApprover *approver,
         }
 
     }
+  else if (channel_type == TPY_IFACE_QUARK_CHANNEL_TYPE_CALL)
+    {
+      TpyCallChannel *call = TPY_CALL_CHANNEL (channel);
+      const gchar *id;
+
+      approval->handler_instance = g_object_ref (call);
+
+      id = tp_channel_get_identifier (channel);
+
+      empathy_tp_contact_factory_get_from_id (connection, id,
+        event_manager_call_channel_got_contact_cb,
+        approval, NULL, G_OBJECT (self));
+    }
   else if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_FILE_TRANSFER)
     {
       TpHandle handle;
@@ -1013,7 +1145,6 @@ approve_channels (TpSimpleApprover *approver,
 
       handle = tp_channel_get_handle (channel, NULL);
 
-      connection = tp_channel_borrow_connection (channel);
       empathy_tp_contact_factory_get_from_handle (connection, handle,
         event_manager_ft_got_contact_cb, approval, NULL, G_OBJECT (self));
     }
@@ -1322,6 +1453,12 @@ empathy_event_manager_init (EmpathyEventManager *manager)
           TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
         NULL));
+  tp_base_client_take_approver_filter (priv->approver,
+      tp_asv_new (
+        TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+          TPY_IFACE_CHANNEL_TYPE_CALL,
+        TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+        NULL));
 
   /* I don't feel good about doing this, and I'm sorry, but the
    * capabilities connection feature is added earlier because it's
index c8b032ae072d574550f8768a846b3289892040e6..91c6e1f9903cc5cf3176d39ced329779b6eb1fa6 100644 (file)
@@ -51,6 +51,7 @@ struct _EmpathyEventManagerClass {
 typedef enum {
     EMPATHY_EVENT_TYPE_CHAT,
     EMPATHY_EVENT_TYPE_VOIP,
+    EMPATHY_EVENT_TYPE_CALL,
     EMPATHY_EVENT_TYPE_TRANSFER,
     EMPATHY_EVENT_TYPE_SUBSCRIPTION,
     EMPATHY_EVENT_TYPE_PRESENCE_ONLINE,
@@ -67,6 +68,7 @@ typedef struct {
     gchar *header;
     gchar *message;
     gboolean must_ack;
+    GObject *handler_instance;
 } EmpathyEvent;
 
 GType                empathy_event_manager_get_type      (void) G_GNUC_CONST;
index af6c139d2ea3130958885805e063e1ee345b9071..6087a66201764184d50062646dd2255209f67d49 100644 (file)
 #include <libnotify/notify.h>
 #include <telepathy-glib/telepathy-glib.h>
 
+#include <telepathy-yell/telepathy-yell.h>
+
 #include <libempathy/empathy-contact-manager.h>
+#include <libempathy/empathy-tp-streamed-media.h>
 
 #include <libempathy-gtk/empathy-notify-manager.h>
 
@@ -118,6 +121,20 @@ notification_close_helper (EmpathyNotificationsApprover *self)
     }
 }
 
+static void
+notification_approve_no_video_cb (NotifyNotification *notification,
+    gchar *action,
+    EmpathyNotificationsApprover *self)
+{
+  if (self->priv->event)
+    {
+      tpy_call_channel_send_video (
+          TPY_CALL_CHANNEL (self->priv->event->handler_instance),
+          FALSE);
+      empathy_event_approve (self->priv->event);
+    }
+}
+
 static void
 notification_approve_cb (NotifyNotification *notification,
     gchar *action,
@@ -178,6 +195,8 @@ static void
 add_notification_actions (EmpathyNotificationsApprover *self,
     NotifyNotification *notification)
 {
+  gboolean video;
+
   switch (self->priv->event->type) {
     case EMPATHY_EVENT_TYPE_CHAT:
       notify_notification_add_action (notification,
@@ -186,12 +205,27 @@ add_notification_actions (EmpathyNotificationsApprover *self,
       break;
 
     case EMPATHY_EVENT_TYPE_VOIP:
+    case EMPATHY_EVENT_TYPE_CALL:
+      if (self->priv->event->type == EMPATHY_EVENT_TYPE_VOIP)
+        video = empathy_tp_streamed_media_has_initial_video (
+            EMPATHY_TP_STREAMED_MEDIA (self->priv->event->handler_instance));
+      else
+        video = tpy_call_channel_has_initial_video (
+            TPY_CALL_CHANNEL (self->priv->event->handler_instance));
+
       notify_notification_add_action (notification,
         "reject", _("Reject"), (NotifyActionCallback) notification_decline_cb,
           self, NULL);
 
+      if (video && self->priv->event->type == EMPATHY_EVENT_TYPE_CALL)
+          notify_notification_add_action (notification,
+          "answer-no-video", _("Answer"),
+          (NotifyActionCallback) notification_approve_no_video_cb,
+          self, NULL);
+
       notify_notification_add_action (notification,
-        "answer", _("Answer"), (NotifyActionCallback) notification_approve_cb,
+          "answer", video ? _("Answer with video") : _("Answer"),
+          (NotifyActionCallback) notification_approve_cb,
           self, NULL);
       break;
 
@@ -241,6 +275,7 @@ notification_is_urgent (EmpathyNotificationsApprover *self,
   switch (self->priv->event->type) {
     case EMPATHY_EVENT_TYPE_CHAT:
     case EMPATHY_EVENT_TYPE_VOIP:
+    case EMPATHY_EVENT_TYPE_CALL:
     case EMPATHY_EVENT_TYPE_TRANSFER:
     case EMPATHY_EVENT_TYPE_INVITATION:
     case EMPATHY_EVENT_TYPE_AUTH:
@@ -266,6 +301,7 @@ get_category_for_event_type (EmpathyEventType type)
     case EMPATHY_EVENT_TYPE_PRESENCE_OFFLINE:
       return "presence.offline";
     case EMPATHY_EVENT_TYPE_VOIP:
+    case EMPATHY_EVENT_TYPE_CALL:
     case EMPATHY_EVENT_TYPE_TRANSFER:
     case EMPATHY_EVENT_TYPE_INVITATION:
     case EMPATHY_EVENT_TYPE_AUTH:
index 75007f1abc3f98d2c0b6116cb6ef3d8c410be5e7..39e3e560d2f3f70931cd78c9a71d722960b2b1c3 100644 (file)
@@ -31,6 +31,8 @@
 
 #include <libempathy/empathy-utils.h>
 
+#include <libempathy-gtk/empathy-call-utils.h>
+
 #include "empathy-streamed-media-handler.h"
 #include "src-marshal.h"
 
@@ -806,7 +808,8 @@ empathy_streamed_media_handler_start_call (EmpathyStreamedMediaHandler *handler,
   g_assert (priv->contact != NULL);
 
   account = empathy_contact_get_account (priv->contact);
-  request = empathy_call_create_streamed_media_request (priv->contact,
+  request = empathy_call_create_streamed_media_request (
+      empathy_contact_get_id (priv->contact),
       priv->initial_audio, priv->initial_video);
 
   req = tp_account_channel_request_new (account, request, timestamp);
index 100a6b9f1dd7e9dca0ed96424fe9bcca604f2c95..cb6f07135a2c619ad169933f9d204a760674c3b8 100644 (file)
@@ -188,7 +188,6 @@ struct _EmpathyStreamedMediaWindowPriv
   GstElement *video_tee;
 
   GstElement *funnel;
-  GstElement *liveadder;
 
   FsElementAddedNotifier *fsnotifier;
 
@@ -638,17 +637,6 @@ create_video_output_widget (EmpathyStreamedMediaWindow *self)
   g_object_unref (bus);
 }
 
-static void
-create_audio_output (EmpathyStreamedMediaWindow *self)
-{
-  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
-
-  g_assert (priv->audio_output == NULL);
-  priv->audio_output = empathy_audio_sink_new ();
-  gst_object_ref (priv->audio_output);
-  gst_object_sink (priv->audio_output);
-}
-
 static void
 create_video_input (EmpathyStreamedMediaWindow *self)
 {
@@ -1131,7 +1119,6 @@ empathy_streamed_media_window_init (EmpathyStreamedMediaWindow *self)
   create_pipeline (self);
   create_video_output_widget (self);
   create_audio_input (self);
-  create_audio_output (self);
   create_video_input (self);
 
   priv->fsnotifier = fs_element_added_notifier_new ();
@@ -1733,18 +1720,10 @@ empathy_streamed_media_window_dispose (GObject *object)
     g_object_unref (priv->audio_input);
   priv->audio_input = NULL;
 
-  if (priv->audio_output != NULL)
-    g_object_unref (priv->audio_output);
-  priv->audio_output = NULL;
-
   if (priv->video_tee != NULL)
     g_object_unref (priv->video_tee);
   priv->video_tee = NULL;
 
-  if (priv->liveadder != NULL)
-    gst_object_unref (priv->liveadder);
-  priv->liveadder = NULL;
-
   if (priv->fsnotifier != NULL)
     g_object_unref (priv->fsnotifier);
   priv->fsnotifier = NULL;
@@ -1877,7 +1856,6 @@ empathy_streamed_media_window_reset_pipeline (EmpathyStreamedMediaWindow *self)
         gtk_widget_destroy (priv->video_preview);
       priv->video_preview = NULL;
 
-      priv->liveadder = NULL;
       priv->funnel = NULL;
 
       create_pipeline (self);
@@ -2051,14 +2029,12 @@ empathy_streamed_media_window_channel_stream_closed_cb (EmpathyStreamedMediaHand
     }
   else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
     {
-      if (priv->liveadder != NULL)
+      if (priv->audio_output != NULL)
         {
           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
-          gst_element_set_state (priv->liveadder, GST_STATE_NULL);
 
           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
-          gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
-          priv->liveadder = NULL;
+          priv->audio_output = NULL;
         }
     }
 }
@@ -2150,112 +2126,47 @@ empathy_streamed_media_window_get_audio_sink_pad (EmpathyStreamedMediaWindow *se
 {
   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
   GstPad *pad;
-  GstElement *filter;
-  GError *gerror = NULL;
+  GstPadTemplate *template;
 
-  if (priv->liveadder == NULL)
+  if (priv->audio_output == NULL)
     {
-      priv->liveadder = gst_element_factory_make ("liveadder", NULL);
+      priv->audio_output = empathy_audio_sink_new ();
 
-      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
-        {
-          g_warning ("Could not add liveadder to the pipeline");
-          goto error_add_liveadder;
-        }
       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
         {
           g_warning ("Could not add audio sink to pipeline");
+          g_object_unref (priv->audio_output);
           goto error_add_output;
         }
 
-      if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
-        {
-          g_warning ("Could not start liveadder");
-          goto error;
-        }
-
       if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
         {
           g_warning ("Could not start audio sink");
           goto error;
         }
-
-      if (GST_PAD_LINK_FAILED (
-              gst_element_link (priv->liveadder, priv->audio_output)))
-        {
-          g_warning ("Could not link liveadder to audio output");
-          goto error;
-        }
-    }
-
-  filter = gst_parse_bin_from_description (
-      "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
-  if (filter == NULL)
-    {
-      g_warning ("Could not make audio conversion filter: %s", gerror->message);
-      g_clear_error (&gerror);
-      goto error;
     }
 
-  if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
-    {
-      g_warning ("Could not add audio conversion filter to pipeline");
-      gst_object_unref (filter);
-      goto error;
-    }
+  template = gst_element_class_get_pad_template (
+    GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
 
-  if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
-    {
-      g_warning ("Could not start audio conversion filter");
-      goto error_filter;
-    }
-
-  if (!gst_element_link (filter, priv->liveadder))
-    {
-      g_warning ("Could not link audio conversion filter to liveadder");
-      goto error_filter;
-    }
-
-  pad = gst_element_get_static_pad (filter, "sink");
+  pad = gst_element_request_pad (priv->audio_output,
+    template, NULL, NULL);
 
   if (pad == NULL)
     {
-      g_warning ("Could not get sink pad from filter");
-      goto error_filter;
+      g_warning ("Could not get sink pad from sink");
+      return NULL;
     }
 
   return pad;
 
- error_filter:
-
-  gst_element_set_locked_state (filter, TRUE);
-  gst_element_set_state (filter, GST_STATE_NULL);
-  gst_bin_remove (GST_BIN (priv->pipeline), filter);
-
- error:
-
-  gst_element_set_locked_state (priv->liveadder, TRUE);
+error:
   gst_element_set_locked_state (priv->audio_output, TRUE);
-
-  gst_element_set_state (priv->liveadder, GST_STATE_NULL);
   gst_element_set_state (priv->audio_output, GST_STATE_NULL);
-
   gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
+  priv->audio_output = NULL;
 
- error_add_output:
-
-  gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
-
-  gst_element_set_locked_state (priv->liveadder, FALSE);
-  gst_element_set_locked_state (priv->audio_output, FALSE);
-
- error_add_liveadder:
-
-  if (priv->liveadder != NULL)
-    {
-      gst_object_unref (priv->liveadder);
-      priv->liveadder = NULL;
-    }
+error_add_output:
 
   return NULL;
 }
@@ -2592,7 +2503,7 @@ empathy_streamed_media_window_src_added_cb (EmpathyStreamedMediaHandler *handler
             }
           else
             {
-              g_debug ("Could not link real sink, linked fakesink instead");
+              DEBUG ("Could not link real sink, linked fakesink instead");
             }
           gst_object_unref (sinkpad);
         }
index 5c865daaa7f38689a502c08092afe55a5b34b015..15d8f79e10d7dd4c1c3309840b24fdb6a2ee64a3 100644 (file)
@@ -24,6 +24,9 @@
 
 #include <gst/interfaces/colorbalance.h>
 
+#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
+#include <libempathy/empathy-debug.h>
+
 #include "empathy-video-src.h"
 
 G_DEFINE_TYPE(EmpathyGstVideoSrc, empathy_video_src, GST_TYPE_BIN)
@@ -143,7 +146,7 @@ empathy_video_src_init (EmpathyGstVideoSrc *obj)
     }
 
   str = gst_caps_to_string (caps);
-  g_debug ("Current video src caps are : %s", str);
+  DEBUG ("Current video src caps are : %s", str);
   g_free (str);
 
   if ((element = empathy_gst_add_to_bin (GST_BIN (obj),
index c9abac2e80df02760b693706a91663b16281d267..6e2b165dc436b8b98c13143379a70ba2fc01fe92 100644 (file)
@@ -44,6 +44,8 @@
 #include <telepathy-glib/connection-manager.h>
 #include <telepathy-glib/interfaces.h>
 
+#include <telepathy-yell/telepathy-yell.h>
+
 #include <telepathy-logger/log-manager.h>
 
 #include <libempathy/empathy-presence-manager.h>
@@ -712,6 +714,8 @@ main (int argc, char *argv[])
   gtk_clutter_init (&argc, &argv);
 #endif
 
+  g_type_init ();
+  tpy_cli_init ();
   empathy_init ();
   gtk_init (&argc, &argv);
   empathy_gtk_init ();
index 9c805372dd780f0c105844f2856c4cccca0c2041..56e5cf982b3e32f500af345e6c512aae2f8fe9a2 100644 (file)
@@ -50,6 +50,7 @@ struct _EvSidebarPrivate {
 
 enum {
        CLOSED,
+       CHANGED,
        LAST_SIGNAL
 };
 
@@ -78,6 +79,15 @@ ev_sidebar_class_init (EvSidebarClass *ev_sidebar_class)
                                g_cclosure_marshal_VOID__VOID,
                                G_TYPE_NONE, 0);
 
+       ev_sidebar_table_signals[CHANGED] =
+               g_signal_new ("changed",
+                               G_TYPE_FROM_CLASS (g_object_class),
+                               G_SIGNAL_RUN_LAST,
+                               G_STRUCT_OFFSET (EvSidebarClass, closed),
+                               NULL, NULL,
+                               g_cclosure_marshal_VOID__STRING,
+                               G_TYPE_NONE, 1, G_TYPE_STRING);
+
 }
 
 static void
@@ -109,11 +119,17 @@ ev_sidebar_combobox_changed_cb (GtkComboBox *combo_box,
                indices = gtk_tree_path_get_indices (path);
 
                if (indices != NULL) {
+                       gchar *page;
+
                        gtk_notebook_set_current_page (GTK_NOTEBOOK (ev_sidebar->priv->notebook), indices[0]);
+                       gtk_tree_model_get (model, &iter, PAGE_COLUMN_ID, &page, -1);
+                       g_signal_emit (G_OBJECT (ev_sidebar), ev_sidebar_table_signals[CHANGED], 0, page);
+                       g_free (page);
                }
 
                gtk_tree_path_free (path);
        }
+
 }
 
 static void
diff --git a/telepathy-yell b/telepathy-yell
new file mode 160000 (submodule)
index 0000000..301cee6
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 301cee6d78085d746647373454c8ac0d2affb891