diff options
author | Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk> | 2011-06-09 17:28:44 +0800 |
---|---|---|
committer | Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk> | 2011-06-09 17:28:44 +0800 |
commit | 8944f13286ba5293781f2d57980da284cb2aa29d (patch) | |
tree | 91aa682420a072399f03ac7fb456f62c13064add | |
parent | 981a1377f660a9d1e3660dc66613b993b78486fc (diff) | |
parent | 3f52e59aa058c16a9ba115398681a16b1e289cd1 (diff) | |
download | gsoc2013-empathy-8944f13286ba5293781f2d57980da284cb2aa29d.tar gsoc2013-empathy-8944f13286ba5293781f2d57980da284cb2aa29d.tar.gz gsoc2013-empathy-8944f13286ba5293781f2d57980da284cb2aa29d.tar.bz2 gsoc2013-empathy-8944f13286ba5293781f2d57980da284cb2aa29d.tar.lz gsoc2013-empathy-8944f13286ba5293781f2d57980da284cb2aa29d.tar.xz gsoc2013-empathy-8944f13286ba5293781f2d57980da284cb2aa29d.tar.zst gsoc2013-empathy-8944f13286ba5293781f2d57980da284cb2aa29d.zip |
Merge branch 'call'
45 files changed, 6536 insertions, 294 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..a61f97326 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "telepathy-yell"] + path = telepathy-yell + url = git://anongit.freedesktop.org/telepathy/telepathy-yell.git diff --git a/Makefile.am b/Makefile.am index 4d4462d2e..c1bed60f6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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) diff --git a/autogen.sh b/autogen.sh index bb0d1f831..7c73adb9f 100755 --- a/autogen.sh +++ b/autogen.sh @@ -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 diff --git a/configure.ac b/configure.ac index 3e998de91..a8a272be1 100644 --- a/configure.ac +++ b/configure.ac @@ -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,6 +176,8 @@ PKG_CHECK_MODULES(EMPATHY, gcr-3 >= $GCR_REQUIRED ]) +PKG_CHECK_MODULES(YELL, [telepathy-yell]) + PKG_CHECK_MODULES(EMPATHY_AV, [ farsight2-0.10 @@ -173,6 +185,32 @@ PKG_CHECK_MODULES(EMPATHY_AV, ]) # ----------------------------------------------------------- +# 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) # ----------------------------------------------------------- AC_ARG_WITH(eds, @@ -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} " diff --git a/data/.gitignore b/data/.gitignore index 9033e3d36..dffae8466 100644 --- a/data/.gitignore +++ b/data/.gitignore @@ -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 index 000000000..259025d86 --- /dev/null +++ b/data/Empathy.Call.client @@ -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 diff --git a/data/Makefile.am b/data/Makefile.am index d7e416a83..24a7fb9d8 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -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 index 000000000..0496d51ad --- /dev/null +++ b/data/org.freedesktop.Telepathy.Client.Empathy.Call.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.Telepathy.Client.Empathy.Call +Exec=@libexecdir@/empathy-call diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am index bb2565885..7023ecfe5 100644 --- a/libempathy-gtk/Makefile.am +++ b/libempathy-gtk/Makefile.am @@ -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 \ @@ -160,6 +163,7 @@ nodist_libempathy_gtk_la_SOURCES =\ libempathy_gtk_la_LIBADD = \ $(EMPATHY_LIBS) \ + $(EMPATHY_LIBS) \ $(GTK_LIBS) \ $(LIBNOTIFY_LIBS) \ $(CANBERRA_LIBS) \ diff --git a/libempathy-gtk/empathy-call-utils.c b/libempathy-gtk/empathy-call-utils.c new file mode 100644 index 000000000..4e8f6d2cd --- /dev/null +++ b/libempathy-gtk/empathy-call-utils.c @@ -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 index 000000000..99a97f202 --- /dev/null +++ b/libempathy-gtk/empathy-call-utils.h @@ -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__ */ diff --git a/libempathy-gtk/empathy-contact-menu.c b/libempathy-gtk/empathy-contact-menu.c index ecdbee1ba..60e7052ea 100644 --- a/libempathy-gtk/empathy-contact-menu.c +++ b/libempathy-gtk/empathy-contact-menu.c @@ -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 ()); } diff --git a/libempathy-gtk/empathy-individual-menu.c b/libempathy-gtk/empathy-individual-menu.c index 8a2654b8f..a92e5bcd9 100644 --- a/libempathy-gtk/empathy-individual-menu.c +++ b/libempathy-gtk/empathy-individual-menu.c @@ -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 ()); } diff --git a/libempathy-gtk/empathy-new-call-dialog.c b/libempathy-gtk/empathy-new-call-dialog.c index 86430cd24..139eb1de5 100644 --- a/libempathy-gtk/empathy-new-call-dialog.c +++ b/libempathy-gtk/empathy-new-call-dialog.c @@ -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 @@ -88,34 +76,6 @@ create_media_channel_cb (GObject *source, */ 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) { EmpathyNewCallDialogPriv *priv = GET_PRIV (dialog); @@ -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) != diff --git a/libempathy/Makefile.am b/libempathy/Makefile.am index 41cec88fd..48846311f 100644 --- a/libempathy/Makefile.am +++ b/libempathy/Makefile.am @@ -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 diff --git a/libempathy/empathy-channel-factory.c b/libempathy/empathy-channel-factory.c index d1da3047d..d8b1cd941 100644 --- a/libempathy/empathy-channel-factory.c +++ b/libempathy/empathy-channel-factory.c @@ -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); diff --git a/libempathy/empathy-contact.c b/libempathy/empathy-contact.c index a90b20ece..55bc40bdd 100644 --- a/libempathy/empathy-contact.c +++ b/libempathy/empathy-contact.c @@ -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; diff --git a/libempathy/empathy-request-util.h b/libempathy/empathy-request-util.h index 49b98a433..70a607e73 100644 --- a/libempathy/empathy-request-util.h +++ b/libempathy/empathy-request-util.h @@ -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 diff --git a/libempathy/empathy-utils.c b/libempathy/empathy-utils.c index 4d6506b6e..d35f4bced 100644 --- a/libempathy/empathy-utils.c +++ b/libempathy/empathy-utils.c @@ -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) { diff --git a/libempathy/empathy-utils.h b/libempathy/empathy-utils.h index 157569453..9a2f3b924 100644 --- a/libempathy/empathy-utils.h +++ b/libempathy/empathy-utils.h @@ -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 index 000000000..0ba313faa --- /dev/null +++ b/m4/ax_config_dir.m4 @@ -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]) +]) + diff --git a/po/POTFILES.in b/po/POTFILES.in index 7a78f2558..a8821f2f3 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -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 diff --git a/src/.gitignore b/src/.gitignore index 510316854..8047735ac 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -3,4 +3,5 @@ empathy-accounts empathy-debugger empathy-av empathy-auth-client +empathy-call empathy-chat diff --git a/src/Makefile.am b/src/Makefile.am index cf3efb0b3..7fcb9e435 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/empathy-audio-sink.c b/src/empathy-audio-sink.c index 1d2169593..c410d7a30 100644 --- a/src/empathy-audio-sink.c +++ b/src/empathy-audio-sink.c @@ -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); } diff --git a/src/empathy-audio-sink.h b/src/empathy-audio-sink.h index 21ebf2b5a..cc21fc467 100644 --- a/src/empathy-audio-sink.h +++ b/src/empathy-audio-sink.h @@ -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 index 000000000..46ef01aa5 --- /dev/null +++ b/src/empathy-call-factory.c @@ -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 index 000000000..0e5276278 --- /dev/null +++ b/src/empathy-call-factory.h @@ -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 index 000000000..1db5a34ef --- /dev/null +++ b/src/empathy-call-handler.c @@ -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 index 000000000..330d368a5 --- /dev/null +++ b/src/empathy-call-handler.h @@ -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 index 000000000..1b993458b --- /dev/null +++ b/src/empathy-call-window-fullscreen.c @@ -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 index 000000000..7e9bd63e5 --- /dev/null +++ b/src/empathy-call-window-fullscreen.h @@ -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 index 000000000..3dbac9e44 --- /dev/null +++ b/src/empathy-call-window.c @@ -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&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 index 000000000..11237fff6 --- /dev/null +++ b/src/empathy-call-window.h @@ -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__*/ diff --git a/src/empathy-call-window.ui b/src/empathy-call-window.ui index ecd00dfc1..0d32677f9 100644 --- a/src/empathy-call-window.ui +++ b/src/empathy-call-window.ui @@ -26,6 +26,12 @@ </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> <property name="label" translatable="yes">V_ideo</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 index 000000000..d5fa2d17e --- /dev/null +++ b/src/empathy-call.c @@ -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; +} diff --git a/src/empathy-event-manager.c b/src/empathy-event-manager.c index 0dcc2be8a..c413be020 100644 --- a/src/empathy-event-manager.c +++ b/src/empathy-event-manager.c @@ -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) @@ -651,6 +695,79 @@ cdo_invalidated_cb (TpProxy *cdo, } 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) { EmpathyEventManagerPriv *priv = GET_PRIV (approval->manager); @@ -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 diff --git a/src/empathy-event-manager.h b/src/empathy-event-manager.h index c8b032ae0..91c6e1f99 100644 --- a/src/empathy-event-manager.h +++ b/src/empathy-event-manager.h @@ -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; diff --git a/src/empathy-notifications-approver.c b/src/empathy-notifications-approver.c index af6c139d2..6087a6620 100644 --- a/src/empathy-notifications-approver.c +++ b/src/empathy-notifications-approver.c @@ -25,7 +25,10 @@ #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> @@ -119,6 +122,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, EmpathyNotificationsApprover *self) @@ -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: diff --git a/src/empathy-streamed-media-handler.c b/src/empathy-streamed-media-handler.c index 75007f1ab..39e3e560d 100644 --- a/src/empathy-streamed-media-handler.c +++ b/src/empathy-streamed-media-handler.c @@ -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); diff --git a/src/empathy-streamed-media-window.c b/src/empathy-streamed-media-window.c index 100a6b9f1..cb6f07135 100644 --- a/src/empathy-streamed-media-window.c +++ b/src/empathy-streamed-media-window.c @@ -188,7 +188,6 @@ struct _EmpathyStreamedMediaWindowPriv GstElement *video_tee; GstElement *funnel; - GstElement *liveadder; FsElementAddedNotifier *fsnotifier; @@ -639,17 +638,6 @@ create_video_output_widget (EmpathyStreamedMediaWindow *self) } 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) { EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (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); } diff --git a/src/empathy-video-src.c b/src/empathy-video-src.c index 5c865daaa..15d8f79e1 100644 --- a/src/empathy-video-src.c +++ b/src/empathy-video-src.c @@ -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), diff --git a/src/empathy.c b/src/empathy.c index c9abac2e8..6e2b165dc 100644 --- a/src/empathy.c +++ b/src/empathy.c @@ -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 (); diff --git a/src/ev-sidebar.c b/src/ev-sidebar.c index 9c805372d..56e5cf982 100644 --- a/src/ev-sidebar.c +++ b/src/ev-sidebar.c @@ -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 +Subproject 301cee6d78085d746647373454c8ac0d2affb89 |