/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2007 Elliot Fairweather * Copyright (C) 2007 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 * * Authors: Elliot Fairweather * Xavier Claessens */ #include "config.h" #include #include #include #include #include "empathy-tp-call.h" #include "empathy-tp-group.h" #include "empathy-utils.h" #include "empathy-debug.h" #include "empathy-enum-types.h" #include "tp-stream-engine-gen.h" #define DEBUG_DOMAIN "TpCall" #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EMPATHY_TYPE_TP_CALL, EmpathyTpCallPriv)) #define STREAM_ENGINE_BUS_NAME "org.freedesktop.Telepathy.StreamEngine" #define STREAM_ENGINE_OBJECT_PATH "/org/freedesktop/Telepathy/StreamEngine" #define STREAM_ENGINE_INTERFACE "org.freedesktop.Telepathy.StreamEngine" #define CHANNEL_HANDLER_INTERFACE "org.freedesktop.Telepathy.ChannelHandler" typedef struct _EmpathyTpCallPriv EmpathyTpCallPriv; struct _EmpathyTpCallPriv { TpChan *tp_chan; DBusGProxy *streamed_iface; DBusGProxy *se_ch_proxy; DBusGProxy *se_proxy; McAccount *account; EmpathyTpGroup *group; EmpathyContact *contact; EmpathyTpCallStatus status; gboolean is_incoming; guint audio_stream; guint video_stream; }; static void empathy_tp_call_class_init (EmpathyTpCallClass *klass); static void empathy_tp_call_init (EmpathyTpCall *call); enum { PROP_0, PROP_ACCOUNT, PROP_TP_CHAN, PROP_STATUS }; enum { DESTROY, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; G_DEFINE_TYPE (EmpathyTpCall, empathy_tp_call, G_TYPE_OBJECT) static void tp_call_set_status (EmpathyTpCall *call, EmpathyTpCallStatus status) { EmpathyTpCallPriv *priv = GET_PRIV (call); priv->status = status; g_object_notify (G_OBJECT (call), "status"); } static void tp_call_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { EmpathyTpCallPriv *priv = GET_PRIV (object); switch (prop_id) { case PROP_ACCOUNT: priv->account = g_object_ref (g_value_get_object (value)); break; case PROP_TP_CHAN: priv->tp_chan = g_object_ref (g_value_get_object (value)); break; case PROP_STATUS: tp_call_set_status (EMPATHY_TP_CALL (object), g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void tp_call_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { EmpathyTpCallPriv *priv = GET_PRIV (object); switch (prop_id) { case PROP_ACCOUNT: g_value_set_object (value, priv->account); break; case PROP_TP_CHAN: g_value_set_object (value, priv->tp_chan); break; case PROP_STATUS: g_value_set_enum (value, priv->status); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void tp_call_destroy_cb (TpChan *call_chan, EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); empathy_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed"); g_object_unref (priv->tp_chan); priv->tp_chan = NULL; priv->streamed_iface = NULL; g_signal_emit (call, signals[DESTROY], 0); } static void tp_call_closed_cb (TpChan *call_chan, EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); /* The channel is closed, do just like if the proxy was destroyed */ g_signal_handlers_disconnect_by_func (priv->tp_chan, tp_call_destroy_cb, call); tp_call_destroy_cb (call_chan, call); } static void tp_call_stream_added_cb (DBusGProxy *streamed_iface, guint stream_id, guint contact_handle, guint stream_type, EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); empathy_debug (DEBUG_DOMAIN, "Stream added: id=%d, stream_type=%d", stream_id, stream_type); switch (stream_type) { case TP_MEDIA_STREAM_TYPE_AUDIO: priv->audio_stream = stream_id; break; case TP_MEDIA_STREAM_TYPE_VIDEO: priv->video_stream = stream_id; break; default: empathy_debug (DEBUG_DOMAIN, "Unknown stream type: %d", stream_type); } } static void tp_call_stream_removed_cb (DBusGProxy *streamed_iface, guint stream_id, EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); empathy_debug (DEBUG_DOMAIN, "Stream removed: %d", stream_id); if (stream_id == priv->audio_stream) { priv->audio_stream = 0; } else if (stream_id == priv->video_stream) { priv->video_stream = 0; } } static void tp_call_list_streams_cb (DBusGProxy *proxy, GPtrArray *streams, GError *error, gpointer user_data) { guint i; if (error) { empathy_debug (DEBUG_DOMAIN, "Failed to list streams: %s", error->message); return; } for (i = 0; i < streams->len; i++) { GValueArray *values; guint stream_id; guint contact_handle; guint stream_type; values = g_ptr_array_index (streams, i); stream_id = g_value_get_uint (g_value_array_get_nth (values, 0)); contact_handle = g_value_get_uint (g_value_array_get_nth (values, 1)); stream_type = g_value_get_uint (g_value_array_get_nth (values, 2)); tp_call_stream_added_cb (proxy, stream_id, contact_handle, stream_type, user_data); } } static void tp_call_member_added_cb (EmpathyTpGroup *group, EmpathyContact *contact, EmpathyContact *actor, guint reason, const gchar *message, EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); empathy_debug (DEBUG_DOMAIN, "Members added %s (%d)", empathy_contact_get_id (contact), empathy_contact_get_handle (contact)); if (!priv->contact) { if (!empathy_contact_is_user (contact)) { priv->is_incoming = TRUE; priv->contact = g_object_ref (contact); tp_call_set_status (call, EMPATHY_TP_CALL_STATUS_RINGING); } return; } /* We already have the other contact, that means we now have 2 members, * so we can start the call */ tp_call_set_status (call, EMPATHY_TP_CALL_STATUS_RUNNING); } static void tp_call_remote_pending_cb (EmpathyTpGroup *group, EmpathyContact *contact, EmpathyContact *actor, guint reason, const gchar *message, EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); empathy_debug (DEBUG_DOMAIN, "Remote pending: %s (%d)", empathy_contact_get_id (contact), empathy_contact_get_handle (contact)); if (!priv->contact) { priv->is_incoming = FALSE; priv->contact = g_object_ref (contact); tp_call_set_status (call, EMPATHY_TP_CALL_STATUS_RINGING); } } static void tp_call_async_cb (DBusGProxy *proxy, GError *error, gpointer user_data) { if (error) { empathy_debug (DEBUG_DOMAIN, "Failed to %s: %s", user_data, error->message); } } static GObject * tp_call_constructor (GType type, guint n_props, GObjectConstructParam *props) { GObject *call; EmpathyTpCallPriv *priv; TpConn *tp_conn; MissionControl *mc; call = G_OBJECT_CLASS (empathy_tp_call_parent_class)->constructor (type, n_props, props); priv = GET_PRIV (call); priv->group = empathy_tp_group_new (priv->account, priv->tp_chan); priv->streamed_iface = tp_chan_get_interface (priv->tp_chan, TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA); /* Connect signals */ dbus_g_proxy_connect_signal (priv->streamed_iface, "StreamAdded", G_CALLBACK (tp_call_stream_added_cb), call, NULL); dbus_g_proxy_connect_signal (priv->streamed_iface, "StreamRemoved", G_CALLBACK (tp_call_stream_removed_cb), call, NULL); dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed", G_CALLBACK (tp_call_closed_cb), call, NULL); g_signal_connect (priv->tp_chan, "destroy", G_CALLBACK (tp_call_destroy_cb), call); g_signal_connect (priv->group, "member-added", G_CALLBACK (tp_call_member_added_cb), call); g_signal_connect (priv->group, "remote-pending", G_CALLBACK (tp_call_remote_pending_cb), call); /* Start stream engine */ mc = empathy_mission_control_new (); tp_conn = mission_control_get_connection (mc, priv->account, NULL); priv->se_ch_proxy = dbus_g_proxy_new_for_name (tp_get_bus (), STREAM_ENGINE_BUS_NAME, STREAM_ENGINE_OBJECT_PATH, CHANNEL_HANDLER_INTERFACE); priv->se_proxy = dbus_g_proxy_new_for_name (tp_get_bus (), STREAM_ENGINE_BUS_NAME, STREAM_ENGINE_OBJECT_PATH, STREAM_ENGINE_INTERFACE); org_freedesktop_Telepathy_ChannelHandler_handle_channel_async (priv->se_ch_proxy, dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn)), dbus_g_proxy_get_path (DBUS_G_PROXY (tp_conn)), priv->tp_chan->type, dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)), priv->tp_chan->handle_type, priv->tp_chan->handle, tp_call_async_cb, "handle channel"); g_object_unref (tp_conn); g_object_unref (mc); /* Get streams */ tp_chan_type_streamed_media_list_streams_async (priv->streamed_iface, tp_call_list_streams_cb, call); return call; } static void tp_call_finalize (GObject *object) { EmpathyTpCallPriv *priv = GET_PRIV (object); empathy_debug (DEBUG_DOMAIN, "Finalizing: %p", object); if (priv->tp_chan) { GError *error = NULL; g_signal_handlers_disconnect_by_func (priv->tp_chan, tp_call_destroy_cb, object); empathy_debug (DEBUG_DOMAIN, "Closing channel..."); if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) { empathy_debug (DEBUG_DOMAIN, "Error closing text channel: %s", error ? error->message : "No error given"); g_clear_error (&error); } g_object_unref (priv->tp_chan); } g_object_unref (priv->group); g_object_unref (priv->contact); g_object_unref (priv->account); g_object_unref (priv->se_ch_proxy); g_object_unref (priv->se_proxy); G_OBJECT_CLASS (empathy_tp_call_parent_class)->finalize (object); } static void empathy_tp_call_class_init (EmpathyTpCallClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructor = tp_call_constructor; object_class->finalize = tp_call_finalize; object_class->set_property = tp_call_set_property; object_class->get_property = tp_call_get_property; /* Construct-only properties */ g_object_class_install_property (object_class, PROP_ACCOUNT, g_param_spec_object ("account", "channel Account", "The account associated with the channel", MC_TYPE_ACCOUNT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_TP_CHAN, g_param_spec_object ("tp-chan", "telepathy channel", "The media channel for the call", TELEPATHY_CHAN_TYPE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); /* Normal properties */ g_object_class_install_property (object_class, PROP_STATUS, g_param_spec_enum ("status", "call status", "The status of the call", EMPATHY_TYPE_TP_CALL_STATUS, EMPATHY_TP_CALL_STATUS_PREPARING, G_PARAM_READABLE)); /* Signals */ signals[DESTROY] = g_signal_new ("destroy", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_type_class_add_private (klass, sizeof (EmpathyTpCallPriv)); } static void empathy_tp_call_init (EmpathyTpCall *call) { } EmpathyTpCall * empathy_tp_call_new (McAccount *account, TpChan *channel) { return g_object_new (EMPATHY_TYPE_TP_CALL, "account", account, "tp_chan", channel, NULL); } gboolean empathy_tp_call_is_incoming (EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); return priv->is_incoming; } EmpathyTpCallStatus empathy_tp_call_get_status (EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); return priv->status; } EmpathyContact * empathy_tp_call_get_contact (EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); return priv->contact; } void empathy_tp_call_accept (EmpathyTpCall *call) { EmpathyTpCallPriv *priv = GET_PRIV (call); EmpathyContact *contact; contact = empathy_tp_group_get_self_contact (priv->group); empathy_tp_group_add_member (priv->group, contact, ""); } void empathy_tp_call_invite (EmpathyTpCall *call, EmpathyContact *contact) { EmpathyTpCallPriv *priv = GET_PRIV (call); empathy_tp_group_add_member (priv->group, contact, "you're welcome"); } void empathy_tp_call_request_streams (EmpathyTpCall *call, gboolean audio, gboolean video) { EmpathyTpCallPriv *priv = GET_PRIV (call); GArray *stream_types; guint handle; guint type; empathy_debug (DEBUG_DOMAIN, "Requesting streams for audio=%s video=%s", audio ? "Yes" : "No", video ? "Yes" : "No"); stream_types = g_array_new (FALSE, FALSE, sizeof (guint)); if (audio) { type = TP_MEDIA_STREAM_TYPE_AUDIO; g_array_append_val (stream_types, type); } if (video) { type = TP_MEDIA_STREAM_TYPE_VIDEO; g_array_append_val (stream_types, type); } handle = empathy_contact_get_handle (priv->contact); tp_chan_type_streamed_media_request_streams_async (priv->streamed_iface, handle, stream_types, tp_call_list_streams_cb, call); g_array_free (stream_types, TRUE); } void empathy_tp_call_send_video (EmpathyTpCall *call, gboolean send) { EmpathyTpCallPriv *priv = GET_PRIV (call); guint new_direction; if (!priv->video_stream) { return; } if (send) { new_direction = TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL; } else { new_direction = TP_MEDIA_STREAM_DIRECTION_RECEIVE; } tp_chan_type_streamed_media_request_stream_direction_async (priv->streamed_iface, priv->video_stream, new_direction, tp_call_async_cb, "request stream direction"); } void empathy_tp_call_add_preview_window (EmpathyTpCall *call, guint socket_id) { EmpathyTpCallPriv *priv = GET_PRIV (call); org_freedesktop_Telepathy_StreamEngine_add_preview_window_async (priv->se_proxy, socket_id, tp_call_async_cb, "add preview window"); } void empathy_tp_call_remove_preview_window (EmpathyTpCall *call, guint socket_id) { EmpathyTpCallPriv *priv = GET_PRIV (call); org_freedesktop_Telepathy_StreamEngine_remove_preview_window_async (priv->se_proxy, socket_id, tp_call_async_cb, "remove preview window"); } void empathy_tp_call_set_output_window (EmpathyTpCall *call, guint socket_id) { EmpathyTpCallPriv *priv = GET_PRIV (call); org_freedesktop_Telepathy_StreamEngine_set_output_window_async (priv->se_proxy, dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)), priv->video_stream, socket_id, tp_call_async_cb, "set output window"); } void empathy_tp_call_set_output_volume (EmpathyTpCall *call, guint volume) { EmpathyTpCallPriv *priv = GET_PRIV (call); org_freedesktop_Telepathy_StreamEngine_set_output_volume_async (priv->se_proxy, dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)), priv->audio_stream, volume, tp_call_async_cb, "set output volume"); } void empathy_tp_call_mute_output (EmpathyTpCall *call, gboolean is_muted) { EmpathyTpCallPriv *priv = GET_PRIV (call); org_freedesktop_Telepathy_StreamEngine_mute_output_async (priv->se_proxy, dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)), priv->audio_stream, is_muted, tp_call_async_cb, "mute output"); } void empathy_tp_call_mute_input (EmpathyTpCall *call, gboolean is_muted) { EmpathyTpCallPriv *priv = GET_PRIV (call); org_freedesktop_Telepathy_StreamEngine_mute_input_async (priv->se_proxy, dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)), priv->audio_stream, is_muted, tp_call_async_cb, "mute output"); }