]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-call.c
Conflicts:
[empathy.git] / libempathy / empathy-tp-call.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Elliot Fairweather
4  * Copyright (C) 2007 Collabora Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * Authors: Elliot Fairweather <elliot.fairweather@collabora.co.uk>
21  *          Xavier Claessens <xclaesse@gmail.com>
22  */
23
24 #include "config.h"
25
26 #include <libtelepathy/tp-chan-type-streamed-media-gen.h>
27 #include <libtelepathy/tp-helpers.h>
28 #include <libtelepathy/tp-conn.h>
29
30 #include <libmissioncontrol/mission-control.h>
31
32 #include "empathy-tp-call.h"
33 #include "empathy-tp-group.h"
34 #include "empathy-utils.h"
35 #include "empathy-debug.h"
36 #include "empathy-enum-types.h"
37 #include "tp-stream-engine-gen.h"
38
39 #define DEBUG_DOMAIN "TpCall"
40
41 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EMPATHY_TYPE_TP_CALL, EmpathyTpCallPriv))
42
43 #define STREAM_ENGINE_BUS_NAME "org.freedesktop.Telepathy.StreamEngine"
44 #define STREAM_ENGINE_OBJECT_PATH "/org/freedesktop/Telepathy/StreamEngine"
45 #define STREAM_ENGINE_INTERFACE "org.freedesktop.Telepathy.StreamEngine"
46 #define CHANNEL_HANDLER_INTERFACE "org.freedesktop.Telepathy.ChannelHandler"
47
48 typedef struct _EmpathyTpCallPriv EmpathyTpCallPriv;
49
50 struct _EmpathyTpCallPriv {
51         TpChan              *tp_chan;
52         DBusGProxy          *streamed_iface;
53         DBusGProxy          *se_ch_proxy;
54         DBusGProxy          *se_proxy;
55         McAccount           *account;
56         EmpathyTpGroup      *group;
57         EmpathyContact      *contact;
58         EmpathyTpCallStatus  status;
59         gboolean             is_incoming;
60         guint                audio_stream;
61         guint                video_stream;
62 };
63
64 static void empathy_tp_call_class_init (EmpathyTpCallClass *klass);
65 static void empathy_tp_call_init       (EmpathyTpCall      *call);
66
67 enum {
68         PROP_0,
69         PROP_ACCOUNT,
70         PROP_TP_CHAN,
71         PROP_STATUS
72 };
73
74 enum {
75         DESTROY,
76         LAST_SIGNAL
77 };
78
79 static guint signals[LAST_SIGNAL];
80
81 G_DEFINE_TYPE (EmpathyTpCall, empathy_tp_call, G_TYPE_OBJECT)
82
83 static void
84 tp_call_set_status (EmpathyTpCall       *call,
85                     EmpathyTpCallStatus  status)
86 {
87         EmpathyTpCallPriv *priv = GET_PRIV (call);
88
89         priv->status = status;
90         g_object_notify (G_OBJECT (call), "status");
91 }
92
93 static void
94 tp_call_set_property (GObject      *object,
95                       guint         prop_id,
96                       const GValue *value,
97                       GParamSpec   *pspec)
98 {
99         EmpathyTpCallPriv *priv = GET_PRIV (object);
100
101         switch (prop_id) {
102         case PROP_ACCOUNT:
103                 priv->account = g_object_ref (g_value_get_object (value));
104                 break;
105         case PROP_TP_CHAN:
106                 priv->tp_chan = g_object_ref (g_value_get_object (value));
107                 break;
108         case PROP_STATUS:
109                 tp_call_set_status (EMPATHY_TP_CALL (object),
110                                     g_value_get_enum (value));
111                 break;
112         default:
113                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
114                 break;
115         }
116 }
117
118 static void
119 tp_call_get_property (GObject    *object,
120                       guint       prop_id,
121                       GValue     *value,
122                       GParamSpec *pspec)
123 {
124   EmpathyTpCallPriv *priv = GET_PRIV (object);
125
126         switch (prop_id) {
127         case PROP_ACCOUNT:
128                 g_value_set_object (value, priv->account);
129                 break;
130         case PROP_TP_CHAN:
131                 g_value_set_object (value, priv->tp_chan);
132                 break;
133         case PROP_STATUS:
134                 g_value_set_enum (value, priv->status);
135                 break;
136         default:
137                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
138                 break;
139         }
140 }
141
142 static void
143 tp_call_destroy_cb (TpChan        *call_chan,
144                     EmpathyTpCall *call)
145 {
146         EmpathyTpCallPriv *priv = GET_PRIV (call);
147
148         empathy_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
149
150         g_object_unref  (priv->tp_chan);
151         priv->tp_chan = NULL;
152         priv->streamed_iface = NULL;
153
154         g_signal_emit (call, signals[DESTROY], 0);
155 }
156
157 static void
158 tp_call_closed_cb (TpChan        *call_chan,
159                    EmpathyTpCall *call)
160 {
161         EmpathyTpCallPriv *priv = GET_PRIV (call);
162
163         /* The channel is closed, do just like if the proxy was destroyed */
164         g_signal_handlers_disconnect_by_func (priv->tp_chan,
165                                               tp_call_destroy_cb,
166                                               call);
167         tp_call_destroy_cb (call_chan, call);
168 }
169
170 static void
171 tp_call_stream_added_cb (DBusGProxy    *streamed_iface,
172                          guint          stream_id,
173                          guint          contact_handle,
174                          guint          stream_type,
175                          EmpathyTpCall *call)
176 {
177         EmpathyTpCallPriv *priv = GET_PRIV (call);
178
179         empathy_debug (DEBUG_DOMAIN, "Stream added: id=%d, stream_type=%d",
180                        stream_id, stream_type);
181
182         switch (stream_type) {
183         case TP_MEDIA_STREAM_TYPE_AUDIO:
184                 priv->audio_stream = stream_id;
185                 break;
186         case TP_MEDIA_STREAM_TYPE_VIDEO:
187                 priv->video_stream = stream_id;
188                 break;
189         default:
190                 empathy_debug (DEBUG_DOMAIN, "Unknown stream type: %d", stream_type);
191         }
192 }
193
194
195 static void
196 tp_call_stream_removed_cb (DBusGProxy    *streamed_iface,
197                            guint          stream_id,
198                            EmpathyTpCall *call)
199 {
200         EmpathyTpCallPriv *priv = GET_PRIV (call);
201
202         empathy_debug (DEBUG_DOMAIN, "Stream removed: %d", stream_id);
203
204         if (stream_id == priv->audio_stream) {
205                 priv->audio_stream = 0;
206         }
207         else if (stream_id == priv->video_stream) {
208                 priv->video_stream = 0;
209         }
210 }
211
212 static void
213 tp_call_list_streams_cb (DBusGProxy *proxy,
214                          GPtrArray  *streams,
215                          GError     *error,
216                          gpointer    user_data)
217 {
218         guint i;
219
220         if (error) {
221                 empathy_debug (DEBUG_DOMAIN, "Failed to list streams: %s",
222                                error->message);
223                 return;
224         }
225
226         for (i = 0; i < streams->len; i++) {
227                 GValueArray *values;
228                 guint        stream_id;
229                 guint        contact_handle;
230                 guint        stream_type;
231
232                 values = g_ptr_array_index (streams, i);
233                 stream_id = g_value_get_uint (g_value_array_get_nth (values, 0));
234                 contact_handle = g_value_get_uint (g_value_array_get_nth (values, 1));
235                 stream_type = g_value_get_uint (g_value_array_get_nth (values, 2));
236
237                 tp_call_stream_added_cb (proxy,
238                                          stream_id,
239                                          contact_handle,
240                                          stream_type,
241                                          user_data);
242         }
243 }
244
245 static void
246 tp_call_member_added_cb (EmpathyTpGroup *group,
247                          EmpathyContact *contact,
248                          EmpathyContact *actor,
249                          guint           reason,
250                          const gchar    *message,
251                          EmpathyTpCall  *call)
252 {
253         EmpathyTpCallPriv *priv = GET_PRIV (call);
254
255         empathy_debug (DEBUG_DOMAIN, "Members added %s (%d)",
256                        empathy_contact_get_id (contact),
257                        empathy_contact_get_handle (contact));
258
259         if (!priv->contact) {
260                 if (!empathy_contact_is_user (contact)) {
261                         priv->is_incoming = TRUE;
262                         priv->contact = g_object_ref (contact);
263                         tp_call_set_status (call, EMPATHY_TP_CALL_STATUS_RINGING);
264                 }
265                 return;
266         }
267
268         /* We already have the other contact, that means we now have 2 members,
269          * so we can start the call */
270         tp_call_set_status (call, EMPATHY_TP_CALL_STATUS_RUNNING);
271 }
272
273 static void
274 tp_call_remote_pending_cb (EmpathyTpGroup *group,
275                            EmpathyContact *contact,
276                            EmpathyContact *actor,
277                            guint           reason,
278                            const gchar    *message,
279                            EmpathyTpCall  *call)
280 {
281         EmpathyTpCallPriv *priv = GET_PRIV (call);
282
283         empathy_debug (DEBUG_DOMAIN, "Remote pending: %s (%d)",
284                        empathy_contact_get_id (contact),
285                        empathy_contact_get_handle (contact));
286
287         if (!priv->contact) {
288                 priv->is_incoming = FALSE;
289                 priv->contact = g_object_ref (contact);
290                 tp_call_set_status (call, EMPATHY_TP_CALL_STATUS_RINGING);
291         }
292 }
293
294 static void
295 tp_call_async_cb (DBusGProxy *proxy,
296                   GError     *error,
297                   gpointer    user_data)
298 {
299         if (error) {
300                 empathy_debug (DEBUG_DOMAIN, "Failed to %s: %s",
301                                user_data,
302                                error->message);
303         }
304 }
305
306 static GObject *
307 tp_call_constructor (GType                  type,
308                      guint                  n_props,
309                      GObjectConstructParam *props)
310 {
311         GObject           *call;
312         EmpathyTpCallPriv *priv;
313         TpConn            *tp_conn;
314         MissionControl    *mc;
315
316         call = G_OBJECT_CLASS (empathy_tp_call_parent_class)->constructor (type, n_props, props);
317         priv = GET_PRIV (call);
318
319         priv->group = empathy_tp_group_new (priv->account, priv->tp_chan);
320         priv->streamed_iface = tp_chan_get_interface (priv->tp_chan,
321                                                       TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA);
322
323         /* Connect signals */
324         dbus_g_proxy_connect_signal (priv->streamed_iface, "StreamAdded",
325                                      G_CALLBACK (tp_call_stream_added_cb),
326                                      call, NULL);
327         dbus_g_proxy_connect_signal (priv->streamed_iface, "StreamRemoved",
328                                      G_CALLBACK (tp_call_stream_removed_cb), 
329                                      call, NULL);
330         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
331                                      G_CALLBACK (tp_call_closed_cb),
332                                      call, NULL);
333         g_signal_connect (priv->tp_chan, "destroy",
334                           G_CALLBACK (tp_call_destroy_cb),
335                           call);
336         g_signal_connect (priv->group, "member-added",
337                           G_CALLBACK (tp_call_member_added_cb),
338                           call);
339         g_signal_connect (priv->group, "remote-pending",
340                           G_CALLBACK (tp_call_remote_pending_cb),
341                           call);
342
343         /* Start stream engine */
344         mc = empathy_mission_control_new ();
345         tp_conn = mission_control_get_connection (mc, priv->account, NULL);
346         priv->se_ch_proxy = dbus_g_proxy_new_for_name (tp_get_bus (),
347                                                        STREAM_ENGINE_BUS_NAME,
348                                                        STREAM_ENGINE_OBJECT_PATH,
349                                                        CHANNEL_HANDLER_INTERFACE);
350         priv->se_proxy = dbus_g_proxy_new_for_name (tp_get_bus (),
351                                                     STREAM_ENGINE_BUS_NAME,
352                                                     STREAM_ENGINE_OBJECT_PATH,
353                                                     STREAM_ENGINE_INTERFACE);
354         org_freedesktop_Telepathy_ChannelHandler_handle_channel_async (priv->se_ch_proxy,
355                 dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn)),
356                 dbus_g_proxy_get_path (DBUS_G_PROXY (tp_conn)),
357                 priv->tp_chan->type,
358                 dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)),
359                 priv->tp_chan->handle_type,
360                 priv->tp_chan->handle,
361                 tp_call_async_cb,
362                 "handle channel");
363         g_object_unref (tp_conn);
364         g_object_unref (mc);
365
366         /* Get streams */
367         tp_chan_type_streamed_media_list_streams_async (priv->streamed_iface,
368                                                         tp_call_list_streams_cb,
369                                                         call);
370
371         return call;
372 }
373
374 static void
375 tp_call_finalize (GObject *object)
376 {
377         EmpathyTpCallPriv *priv = GET_PRIV (object);
378
379         empathy_debug (DEBUG_DOMAIN, "Finalizing: %p", object);
380
381         if (priv->tp_chan) {
382                 GError *error = NULL;
383
384                 g_signal_handlers_disconnect_by_func (priv->tp_chan,
385                                                       tp_call_destroy_cb,
386                                                       object);
387                 empathy_debug (DEBUG_DOMAIN, "Closing channel...");
388                 if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
389                         empathy_debug (DEBUG_DOMAIN, 
390                                       "Error closing text channel: %s",
391                                       error ? error->message : "No error given");
392                         g_clear_error (&error);
393                 }
394                 g_object_unref (priv->tp_chan);
395         }
396
397         g_object_unref (priv->group);
398         g_object_unref (priv->contact);
399         g_object_unref (priv->account);
400         g_object_unref (priv->se_ch_proxy);
401         g_object_unref (priv->se_proxy);
402
403         G_OBJECT_CLASS (empathy_tp_call_parent_class)->finalize (object);
404 }
405
406 static void
407 empathy_tp_call_class_init (EmpathyTpCallClass *klass)
408 {
409         GObjectClass *object_class = G_OBJECT_CLASS (klass);
410
411         object_class->constructor = tp_call_constructor;
412         object_class->finalize = tp_call_finalize;
413         object_class->set_property = tp_call_set_property;
414         object_class->get_property = tp_call_get_property;
415
416         /* Construct-only properties */
417         g_object_class_install_property (object_class,
418                                          PROP_ACCOUNT,
419                                          g_param_spec_object ("account",
420                                                               "channel Account",
421                                                               "The account associated with the channel",
422                                                               MC_TYPE_ACCOUNT,
423                                                               G_PARAM_READWRITE |
424                                                               G_PARAM_CONSTRUCT_ONLY));
425         g_object_class_install_property (object_class,
426                                          PROP_TP_CHAN,
427                                          g_param_spec_object ("tp-chan",
428                                                               "telepathy channel",
429                                                               "The media channel for the call",
430                                                               TELEPATHY_CHAN_TYPE,
431                                                               G_PARAM_READWRITE |
432                                                               G_PARAM_CONSTRUCT_ONLY));
433
434         /* Normal properties */
435         g_object_class_install_property (object_class,
436                                          PROP_STATUS,
437                                          g_param_spec_enum ("status",
438                                                             "call status",
439                                                             "The status of the call",
440                                                             EMPATHY_TYPE_TP_CALL_STATUS,
441                                                             EMPATHY_TP_CALL_STATUS_PREPARING,
442                                                             G_PARAM_READABLE));
443
444         /* Signals */
445         signals[DESTROY] =
446                 g_signal_new ("destroy",
447                               G_TYPE_FROM_CLASS (klass),
448                               G_SIGNAL_RUN_LAST,
449                               0,
450                               NULL, NULL,
451                               g_cclosure_marshal_VOID__VOID,
452                               G_TYPE_NONE,
453                               0);
454
455
456         g_type_class_add_private (klass, sizeof (EmpathyTpCallPriv));
457 }
458
459 static void
460 empathy_tp_call_init (EmpathyTpCall *call)
461 {
462 }
463
464 EmpathyTpCall *
465 empathy_tp_call_new (McAccount *account, TpChan *channel)
466 {
467         return g_object_new (EMPATHY_TYPE_TP_CALL,
468                              "account", account,
469                              "tp_chan", channel,
470                              NULL);
471 }
472
473 gboolean
474 empathy_tp_call_is_incoming (EmpathyTpCall *call)
475 {
476         EmpathyTpCallPriv *priv = GET_PRIV (call);
477
478         return priv->is_incoming;
479 }
480
481 EmpathyTpCallStatus
482 empathy_tp_call_get_status (EmpathyTpCall *call)
483 {
484         EmpathyTpCallPriv *priv = GET_PRIV (call);
485
486         return priv->status;
487 }
488
489 EmpathyContact *
490 empathy_tp_call_get_contact (EmpathyTpCall *call)
491 {
492         EmpathyTpCallPriv *priv = GET_PRIV (call);
493
494         return priv->contact;
495 }
496
497 void
498 empathy_tp_call_accept (EmpathyTpCall *call)
499 {
500         EmpathyTpCallPriv *priv = GET_PRIV (call);
501         EmpathyContact    *contact;
502
503         contact = empathy_tp_group_get_self_contact (priv->group);
504         empathy_tp_group_add_member (priv->group, contact, "");
505 }
506
507 void
508 empathy_tp_call_invite (EmpathyTpCall  *call,
509                         EmpathyContact *contact)
510 {
511         EmpathyTpCallPriv *priv = GET_PRIV (call);
512
513         empathy_tp_group_add_member (priv->group, contact, "you're welcome");
514 }
515
516 void
517 empathy_tp_call_request_streams (EmpathyTpCall *call,
518                                  gboolean       audio,
519                                  gboolean       video)
520 {
521         EmpathyTpCallPriv *priv = GET_PRIV (call);
522         GArray            *stream_types;
523         guint              handle;
524         guint              type;
525
526         empathy_debug (DEBUG_DOMAIN, "Requesting streams for audio=%s video=%s",
527                        audio ? "Yes" : "No",
528                        video ? "Yes" : "No");
529
530         stream_types = g_array_new (FALSE, FALSE, sizeof (guint));
531         if (audio) {
532                 type = TP_MEDIA_STREAM_TYPE_AUDIO;
533                 g_array_append_val (stream_types, type);
534         }
535         if (video) {
536                 type = TP_MEDIA_STREAM_TYPE_VIDEO;
537                 g_array_append_val (stream_types, type);
538         }
539
540         handle = empathy_contact_get_handle (priv->contact);
541         tp_chan_type_streamed_media_request_streams_async (priv->streamed_iface,
542                                                            handle,
543                                                            stream_types,
544                                                            tp_call_list_streams_cb,
545                                                            call);
546
547         g_array_free (stream_types, TRUE);
548 }
549
550 void
551 empathy_tp_call_send_video (EmpathyTpCall *call,
552                             gboolean       send)
553 {
554         EmpathyTpCallPriv *priv = GET_PRIV (call);
555         guint              new_direction;
556
557         if (!priv->video_stream) {
558                 return;
559         }
560
561         if (send) {
562                 new_direction = TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
563         } else {
564                 new_direction = TP_MEDIA_STREAM_DIRECTION_RECEIVE;
565         }
566
567         tp_chan_type_streamed_media_request_stream_direction_async (priv->streamed_iface,
568                                                                     priv->video_stream,
569                                                                     new_direction,
570                                                                     tp_call_async_cb,
571                                                                     "request stream direction");
572 }
573
574 void
575 empathy_tp_call_add_preview_window (EmpathyTpCall *call,
576                                     guint          socket_id)
577 {
578         EmpathyTpCallPriv *priv = GET_PRIV (call);
579
580         org_freedesktop_Telepathy_StreamEngine_add_preview_window_async (priv->se_proxy,
581                                                                          socket_id,
582                                                                          tp_call_async_cb,
583                                                                          "add preview window");
584 }
585
586 void
587 empathy_tp_call_remove_preview_window (EmpathyTpCall *call,
588                                        guint          socket_id)
589 {
590         EmpathyTpCallPriv *priv = GET_PRIV (call);
591
592         org_freedesktop_Telepathy_StreamEngine_remove_preview_window_async (priv->se_proxy,
593                                                                             socket_id,
594                                                                             tp_call_async_cb,
595                                                                             "remove preview window");
596 }
597
598 void
599 empathy_tp_call_set_output_window (EmpathyTpCall *call,
600                                    guint          socket_id)
601 {
602         EmpathyTpCallPriv *priv = GET_PRIV (call);
603
604         org_freedesktop_Telepathy_StreamEngine_set_output_window_async (priv->se_proxy,
605                                                                         dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)),
606                                                                         priv->video_stream,
607                                                                         socket_id,
608                                                                         tp_call_async_cb,
609                                                                         "set output window");
610 }
611
612 void
613 empathy_tp_call_set_output_volume (EmpathyTpCall *call,
614                                    guint          volume)
615 {
616         EmpathyTpCallPriv *priv = GET_PRIV (call);
617
618         org_freedesktop_Telepathy_StreamEngine_set_output_volume_async (priv->se_proxy,
619                                                                         dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)),
620                                                                         priv->audio_stream,
621                                                                         volume,
622                                                                         tp_call_async_cb,
623                                                                         "set output volume");
624 }
625
626
627 void
628 empathy_tp_call_mute_output (EmpathyTpCall *call,
629                              gboolean       is_muted)
630 {
631         EmpathyTpCallPriv *priv = GET_PRIV (call);
632
633         org_freedesktop_Telepathy_StreamEngine_mute_output_async (priv->se_proxy,
634                                                                   dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)),
635                                                                   priv->audio_stream,
636                                                                   is_muted,
637                                                                   tp_call_async_cb,
638                                                                   "mute output");
639 }
640
641
642 void
643 empathy_tp_call_mute_input (EmpathyTpCall *call,
644                             gboolean       is_muted)
645 {
646         EmpathyTpCallPriv *priv = GET_PRIV (call);
647
648         org_freedesktop_Telepathy_StreamEngine_mute_input_async (priv->se_proxy,
649                                                                  dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan)),
650                                                                  priv->audio_stream,
651                                                                  is_muted,
652                                                                  tp_call_async_cb,
653                                                                  "mute output");
654 }
655