]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
Improve dispatcher. Fixes bug #465928.
[empathy.git] / src / empathy-call-window.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-2008 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 <string.h>
25
26 #include <glade/glade.h>
27 #include <glib/gi18n.h>
28
29 #include <telepathy-glib/enums.h>
30
31 #include <libempathy/empathy-contact.h>
32 #include <libempathy/empathy-tp-call.h>
33 #include <libempathy/empathy-utils.h>
34 #include <libempathy-gtk/empathy-ui-utils.h>
35
36 #include "empathy-call-window.h"
37
38 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
39 #include <libempathy/empathy-debug.h>
40
41 typedef struct
42 {
43   EmpathyTpCall *call;
44   GTimeVal start_time;
45   guint timeout_event_id;
46   gboolean is_drawing;
47   guint status; 
48
49   GtkWidget *window;
50   GtkWidget *main_hbox;
51   GtkWidget *controls_vbox;
52   GtkWidget *volume_hbox;
53   GtkWidget *status_label;
54   GtkWidget *input_volume_button;
55   GtkWidget *output_volume_button;
56   GtkWidget *preview_video_socket;
57   GtkWidget *output_video_socket;
58   GtkWidget *video_button;
59   GtkWidget *hang_up_button;
60   GtkWidget *confirmation_dialog;
61   GtkWidget *keypad_expander;
62 } EmpathyCallWindow;
63
64 static GSList *windows = NULL;
65
66 static gboolean
67 call_window_update_timer (gpointer data)
68 {
69   EmpathyCallWindow *window = data;
70   GTimeVal current;
71   gchar *str;
72   glong now, then;
73   glong time, seconds, minutes, hours;
74
75   g_get_current_time (&current);
76
77   now = current.tv_sec;
78   then = (window->start_time).tv_sec;
79
80   time = now - then;
81
82   seconds = time % 60;
83   time /= 60;
84   minutes = time % 60;
85   time /= 60;
86   hours = time % 60;
87
88   if (hours > 0)
89       str = g_strdup_printf ("Connected  -  %02ld : %02ld : %02ld", hours,
90           minutes, seconds);
91   else
92       str = g_strdup_printf ("Connected  -  %02ld : %02ld", minutes, seconds);
93
94   gtk_label_set_text (GTK_LABEL (window->status_label), str);
95
96   g_free (str);
97
98   return TRUE;
99 }
100
101 static void
102 call_window_stop_timeout (EmpathyCallWindow *window)
103 {
104   DEBUG ("Timer stopped");
105
106   if (window->timeout_event_id)
107     {
108       g_source_remove (window->timeout_event_id);
109       window->timeout_event_id = 0;
110     }
111 }
112
113 static void
114 call_window_set_output_video_is_drawing (EmpathyCallWindow *window,
115                                          gboolean is_drawing)
116 {
117   DEBUG ("Setting output video is drawing - %d", is_drawing);
118
119   if (is_drawing && !window->is_drawing)
120     {
121       gtk_window_set_resizable (GTK_WINDOW (window->window), TRUE);
122       gtk_box_pack_end (GTK_BOX (window->main_hbox),
123           window->output_video_socket, TRUE, TRUE, 0);
124       empathy_tp_call_add_output_video (window->call,
125           gtk_socket_get_id (GTK_SOCKET (window->output_video_socket)));
126     }
127   if (!is_drawing && window->is_drawing)
128     {
129       gtk_window_set_resizable (GTK_WINDOW (window->window), FALSE);
130       empathy_tp_call_add_output_video (window->call, 0);
131       gtk_container_remove (GTK_CONTAINER (window->main_hbox),
132           window->output_video_socket);
133     }
134
135   window->is_drawing = is_drawing;
136 }
137
138 static void
139 call_window_finalize (EmpathyCallWindow *window)
140 {
141   gtk_label_set_text (GTK_LABEL (window->status_label), _("Closed"));
142   gtk_widget_set_sensitive (window->hang_up_button, FALSE);
143   gtk_widget_set_sensitive (window->video_button, FALSE);
144   gtk_widget_set_sensitive (window->output_volume_button, FALSE);
145   gtk_widget_set_sensitive (window->input_volume_button, FALSE);
146   gtk_widget_set_sensitive (window->keypad_expander, FALSE);
147
148   if (window->call)
149     { 
150       call_window_stop_timeout (window);
151       call_window_set_output_video_is_drawing (window, FALSE);
152       empathy_tp_call_remove_preview_video (window->call,
153           gtk_socket_get_id (GTK_SOCKET (window->preview_video_socket)));
154       g_object_unref (window->call);
155       window->call = NULL;
156     }
157
158   if (window->confirmation_dialog)
159       gtk_widget_destroy (window->confirmation_dialog);
160 }
161
162 static void
163 call_window_socket_realized_cb (GtkWidget *widget,
164                                 EmpathyCallWindow *window)
165 {
166   if (widget == window->preview_video_socket)
167     {
168       DEBUG ("Preview socket realized");
169       empathy_tp_call_add_preview_video (window->call,
170           gtk_socket_get_id (GTK_SOCKET (window->preview_video_socket)));
171     }
172   else
173       DEBUG ("Output socket realized");
174 }
175
176 static void
177 call_window_video_button_toggled_cb (GtkWidget *button,
178                                      EmpathyCallWindow *window)
179 {
180   gboolean is_sending;
181   guint status;
182
183   is_sending = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
184
185   DEBUG ("Send video toggled - %d", is_sending);
186
187   g_object_get (window->call, "status", &status, NULL);
188   if (status == EMPATHY_TP_CALL_STATUS_ACCEPTED)
189       empathy_tp_call_request_video_stream_direction (window->call, is_sending);
190 }
191
192 static void
193 call_window_hang_up_button_clicked_cb (GtkWidget *widget,
194                                        EmpathyCallWindow *window)
195 {
196   DEBUG ("Call clicked, end call");
197   call_window_finalize (window);
198 }
199
200 static void
201 call_window_output_volume_changed_cb (GtkScaleButton *button,
202                                       gdouble value,
203                                       EmpathyCallWindow *window)
204 {
205   if (!window->call)
206       return;
207
208   if (value <= 0)
209       empathy_tp_call_mute_output (window->call, TRUE);
210   else
211     {
212       empathy_tp_call_mute_output (window->call, FALSE);
213       empathy_tp_call_set_output_volume (window->call, value * 100);
214     }
215 }
216
217 static void
218 call_window_input_volume_changed_cb (GtkScaleButton    *button,
219                                      gdouble            value,
220                                      EmpathyCallWindow *window)
221 {
222   if (!window->call)
223       return;
224
225   if (value <= 0)
226       empathy_tp_call_mute_input (window->call, TRUE);
227   else
228     {
229       empathy_tp_call_mute_input (window->call, FALSE);
230       /* FIXME: Not implemented?
231       empathy_tp_call_set_input_volume (window->call, value * 100);*/
232     }
233 }
234
235 static gboolean
236 call_window_delete_event_cb (GtkWidget *widget,
237                              GdkEvent *event,
238                              EmpathyCallWindow *window)
239 {
240   GtkWidget *dialog;
241   gint result;
242   guint status = EMPATHY_TP_CALL_STATUS_CLOSED;
243
244   DEBUG ("Delete event occurred");
245
246   if (window->call)
247       g_object_get (window->call, "status", &status, NULL);
248
249   if (status == EMPATHY_TP_CALL_STATUS_ACCEPTED)
250     {
251       dialog = gtk_message_dialog_new (GTK_WINDOW (window->window),
252           GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
253           GTK_MESSAGE_WARNING, GTK_BUTTONS_CANCEL, _("End this call?"));
254       gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
255           _("Closing this window will end the call in progress."));
256       gtk_dialog_add_button (GTK_DIALOG (dialog), _("_End Call"), GTK_RESPONSE_OK);
257       gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
258
259       result = gtk_dialog_run (GTK_DIALOG (dialog));
260       gtk_widget_destroy (dialog);
261
262       if (result != GTK_RESPONSE_OK)
263           return TRUE;
264     }
265
266   return FALSE;
267 }
268
269 static void
270 call_window_destroy_cb (GtkWidget *widget,
271                         EmpathyCallWindow *window)
272 {
273   call_window_finalize (window);
274
275   g_object_unref (window->output_video_socket);
276   g_object_unref (window->preview_video_socket);
277
278   windows = g_slist_remove (windows, window);
279   g_slice_free (EmpathyCallWindow, window);
280 }
281
282 static void
283 call_window_confirmation_dialog_response_cb (GtkDialog *dialog,
284                                              gint response,
285                                              EmpathyCallWindow *window)
286 {
287   if (response == GTK_RESPONSE_OK && window->call)
288       empathy_tp_call_accept_incoming_call (window->call);
289   else
290       call_window_finalize (window);
291
292   gtk_widget_destroy (window->confirmation_dialog);
293   window->confirmation_dialog = NULL;
294 }
295
296 static void
297 call_window_show_confirmation_dialog (EmpathyCallWindow *window)
298 {
299   EmpathyContact *contact;
300   GtkWidget *button;
301   GtkWidget *image;
302
303   if (window->confirmation_dialog)
304       return;
305
306   g_object_get (window->call, "contact", &contact, NULL);
307
308   window->confirmation_dialog = gtk_message_dialog_new (GTK_WINDOW (window->window),
309       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
310       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Incoming call"));
311   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (window->confirmation_dialog),
312       _("%s is calling you, do you want to answer?"),
313       empathy_contact_get_name (contact));
314   gtk_dialog_set_default_response (GTK_DIALOG (window->confirmation_dialog),
315       GTK_RESPONSE_OK);
316
317   button = gtk_dialog_add_button (GTK_DIALOG (window->confirmation_dialog),
318       _("_Reject"), GTK_RESPONSE_CANCEL);
319   image = gtk_image_new_from_icon_name (GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON);
320   gtk_button_set_image (GTK_BUTTON (button), image);
321
322   button = gtk_dialog_add_button (GTK_DIALOG (window->confirmation_dialog),
323       _("_Answer"), GTK_RESPONSE_OK);
324   image = gtk_image_new_from_icon_name (GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON);
325   gtk_button_set_image (GTK_BUTTON (button), image);
326
327   g_signal_connect (window->confirmation_dialog, "response",
328       G_CALLBACK (call_window_confirmation_dialog_response_cb),
329       window);
330
331   gtk_widget_show (window->confirmation_dialog);
332   g_object_unref (contact);
333 }
334
335 static void
336 call_window_update (EmpathyCallWindow *window)
337 {
338   EmpathyContact *contact;
339   guint stream_state;
340   EmpathyTpCallStream *audio_stream;
341   EmpathyTpCallStream *video_stream;
342   gboolean is_incoming;
343   gchar *title;
344
345   g_object_get (window->call,
346       "status", &window->status,
347       "audio-stream", &audio_stream,
348       "video-stream", &video_stream,
349       "contact", &contact,
350       "is-incoming", &is_incoming,
351       NULL);
352
353   if (video_stream->state > audio_stream->state)
354       stream_state = video_stream->state;
355   else
356       stream_state = audio_stream->state;
357
358   DEBUG ("Status changed - status: %d, stream state: %d, "
359       "is-incoming: %d video-stream direction: %d",
360       window->status, stream_state, is_incoming, video_stream->direction);
361
362   /* Depending on the status we have to set:
363    * - window's title
364    * - status's label
365    * - sensibility of all buttons
366    * */
367   if (window->status == EMPATHY_TP_CALL_STATUS_READYING)
368     {
369       gtk_window_set_title (GTK_WINDOW (window->window), _("Empathy Call"));
370       gtk_label_set_text (GTK_LABEL (window->status_label), _("Readying"));
371       gtk_widget_set_sensitive (window->video_button, FALSE);
372       gtk_widget_set_sensitive (window->output_volume_button, FALSE);
373       gtk_widget_set_sensitive (window->input_volume_button, FALSE);
374       gtk_widget_set_sensitive (window->hang_up_button, FALSE);
375       gtk_widget_set_sensitive (window->keypad_expander, FALSE);
376     }
377   else if (window->status == EMPATHY_TP_CALL_STATUS_PENDING)
378     {
379       title = g_strdup_printf (_("%s - Empathy Call"),
380           empathy_contact_get_name (contact));
381
382       gtk_window_set_title (GTK_WINDOW (window->window), title);
383       gtk_label_set_text (GTK_LABEL (window->status_label), _("Ringing"));
384       gtk_widget_set_sensitive (window->hang_up_button, TRUE);
385       if (is_incoming)
386           call_window_show_confirmation_dialog (window);
387     }
388   else if (window->status == EMPATHY_TP_CALL_STATUS_ACCEPTED)
389     {
390       gboolean receiving_video;
391       gboolean sending_video;
392
393       if (stream_state == TP_MEDIA_STREAM_STATE_DISCONNECTED)
394           gtk_label_set_text (GTK_LABEL (window->status_label), _("Disconnected"));
395       if (stream_state == TP_MEDIA_STREAM_STATE_CONNECTING)
396           gtk_label_set_text (GTK_LABEL (window->status_label), _("Connecting"));
397       else if (stream_state == TP_MEDIA_STREAM_STATE_CONNECTED &&
398                window->timeout_event_id == 0)
399         {
400           /* The call started, launch the timer */
401           g_get_current_time (&(window->start_time));
402           window->timeout_event_id = g_timeout_add_seconds (1,
403               call_window_update_timer, window);
404           call_window_update_timer (window);
405         }
406
407       receiving_video = video_stream->direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE;
408       sending_video = video_stream->direction & TP_MEDIA_STREAM_DIRECTION_SEND;
409       call_window_set_output_video_is_drawing (window, receiving_video);
410       g_signal_handlers_block_by_func (window->video_button,
411           call_window_video_button_toggled_cb, window);
412       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (window->video_button),
413           sending_video);
414       g_signal_handlers_unblock_by_func (window->video_button,
415           call_window_video_button_toggled_cb, window);
416
417       gtk_widget_set_sensitive (window->video_button, TRUE);
418       gtk_widget_set_sensitive (window->output_volume_button, TRUE);
419       gtk_widget_set_sensitive (window->input_volume_button, TRUE);
420       gtk_widget_set_sensitive (window->hang_up_button, TRUE);
421       gtk_widget_set_sensitive (window->keypad_expander, TRUE);
422     }
423   else if (window->status == EMPATHY_TP_CALL_STATUS_CLOSED)
424       call_window_finalize (window);
425
426   if (contact)
427       g_object_unref (contact);
428 }
429
430 static gboolean
431 call_window_dtmf_button_release_event_cb (GtkWidget *widget,
432                                           GdkEventButton *event,
433                                           EmpathyCallWindow *window)
434 {
435   empathy_tp_call_stop_tone (window->call);
436   return FALSE;
437 }
438
439 static gboolean
440 call_window_dtmf_button_press_event_cb (GtkWidget *widget,
441                                         GdkEventButton *event,
442                                         EmpathyCallWindow *window)
443 {
444   TpDTMFEvent dtmf_event;
445
446   dtmf_event = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "code"));
447   empathy_tp_call_start_tone (window->call, dtmf_event);
448   return FALSE;
449 }
450
451 static void
452 call_window_dtmf_connect (GladeXML *glade,
453                           EmpathyCallWindow *window,
454                           const gchar *name,
455                           TpDTMFEvent event)
456 {
457   GtkWidget *widget;
458
459   widget = glade_xml_get_widget (glade, name);
460   g_object_set_data (G_OBJECT (widget), "code", GUINT_TO_POINTER (event));
461   g_signal_connect (widget, "button-press-event",
462       G_CALLBACK (call_window_dtmf_button_press_event_cb), window);
463   g_signal_connect (widget, "button-release-event",
464       G_CALLBACK (call_window_dtmf_button_release_event_cb), window);
465   /* FIXME: Connect "key-[press/release]-event" to*/
466 }
467
468 GtkWidget *
469 empathy_call_window_new (TpChannel *channel)
470 {
471   EmpathyCallWindow *window;
472   GladeXML *glade;
473   gchar *filename;
474   const gchar *icons[] = {"audio-input-microphone", NULL};
475
476   g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
477
478   window = g_slice_new0 (EmpathyCallWindow);
479   windows = g_slist_prepend (windows, window);
480   window->call = empathy_tp_call_new (channel);
481
482   filename = empathy_file_lookup ("empathy-call-window.glade", "src");
483   glade = empathy_glade_get_file (filename,
484       "window",
485       NULL,
486       "window", &window->window,
487       "main_hbox", &window->main_hbox,
488       "controls_vbox", &window->controls_vbox,
489       "volume_hbox", &window->volume_hbox,
490       "status_label", &window->status_label,
491       "video_button", &window->video_button,
492       "hang_up_button", &window->hang_up_button,
493       "keypad_expander", &window->keypad_expander,
494       NULL);
495   g_free (filename);
496
497   empathy_glade_connect (glade,
498       window,
499       "window", "destroy", call_window_destroy_cb,
500       "window", "delete_event", call_window_delete_event_cb,
501       "hang_up_button", "clicked", call_window_hang_up_button_clicked_cb,
502       "video_button", "toggled", call_window_video_button_toggled_cb,
503       NULL);
504
505   /* Setup DTMF buttons */
506   call_window_dtmf_connect (glade, window, "button_0", TP_DTMF_EVENT_DIGIT_0);
507   call_window_dtmf_connect (glade, window, "button_1", TP_DTMF_EVENT_DIGIT_1);
508   call_window_dtmf_connect (glade, window, "button_2", TP_DTMF_EVENT_DIGIT_2);
509   call_window_dtmf_connect (glade, window, "button_3", TP_DTMF_EVENT_DIGIT_3);
510   call_window_dtmf_connect (glade, window, "button_4", TP_DTMF_EVENT_DIGIT_4);
511   call_window_dtmf_connect (glade, window, "button_5", TP_DTMF_EVENT_DIGIT_5);
512   call_window_dtmf_connect (glade, window, "button_6", TP_DTMF_EVENT_DIGIT_6);
513   call_window_dtmf_connect (glade, window, "button_7", TP_DTMF_EVENT_DIGIT_7);
514   call_window_dtmf_connect (glade, window, "button_8", TP_DTMF_EVENT_DIGIT_8);
515   call_window_dtmf_connect (glade, window, "button_9", TP_DTMF_EVENT_DIGIT_9);
516   call_window_dtmf_connect (glade, window, "button_asterisk", TP_DTMF_EVENT_ASTERISK);
517   call_window_dtmf_connect (glade, window, "button_hash", TP_DTMF_EVENT_HASH);
518
519   g_object_unref (glade);
520
521   /* Output volume button */
522   window->output_volume_button = gtk_volume_button_new ();
523   gtk_scale_button_set_value (GTK_SCALE_BUTTON (window->output_volume_button), 1);
524   gtk_box_pack_start (GTK_BOX (window->volume_hbox),
525       window->output_volume_button, FALSE, FALSE, 0);
526   gtk_widget_show (window->output_volume_button);
527   g_signal_connect (window->output_volume_button, "value-changed",
528       G_CALLBACK (call_window_output_volume_changed_cb), window);
529
530   /* Input volume button */
531   window->input_volume_button = gtk_volume_button_new ();
532   gtk_scale_button_set_icons (GTK_SCALE_BUTTON (window->input_volume_button),
533       icons);
534   gtk_scale_button_set_value (GTK_SCALE_BUTTON (window->input_volume_button), 1);
535   gtk_box_pack_start (GTK_BOX (window->volume_hbox),
536       window->input_volume_button, FALSE, FALSE, 0);
537   gtk_widget_show (window->input_volume_button);
538   g_signal_connect (window->input_volume_button, "value-changed",
539       G_CALLBACK (call_window_input_volume_changed_cb), window);
540
541   /* Output video socket */
542   window->output_video_socket = g_object_ref (gtk_socket_new ());
543   gtk_widget_set_size_request (window->output_video_socket, 400, 300);
544   g_signal_connect (GTK_OBJECT (window->output_video_socket), "realize",
545       G_CALLBACK (call_window_socket_realized_cb), window);
546   gtk_widget_show (window->output_video_socket);
547
548   /* Preview video socket */
549   window->preview_video_socket = g_object_ref (gtk_socket_new ());
550   gtk_widget_set_size_request (window->preview_video_socket, 176, 144);
551   g_signal_connect (GTK_OBJECT (window->preview_video_socket), "realize",
552       G_CALLBACK (call_window_socket_realized_cb), window);
553   gtk_widget_show (window->preview_video_socket);
554
555   /* FIXME: We shouldn't do this if there is no video input */
556   gtk_box_pack_start (GTK_BOX (window->controls_vbox),
557       window->preview_video_socket, FALSE, FALSE, 0);
558   gtk_box_reorder_child (GTK_BOX (window->controls_vbox),
559       window->preview_video_socket, 0);
560
561   g_signal_connect_swapped (G_OBJECT (window->call), "notify",
562       G_CALLBACK (call_window_update),
563       window);
564
565   call_window_update (window);
566   gtk_widget_show (window->window);
567
568   return window->window;
569 }
570
571 GtkWidget *
572 empathy_call_window_find (TpChannel *channel)
573 {
574   GSList *l;
575
576   g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
577
578   for (l = windows; l; l = l->next)
579     {
580       EmpathyCallWindow *window = l->data;
581       TpChannel *this_channel = NULL;
582
583       if (window->call)
584           g_object_get (window->call, "channel", &this_channel, NULL);
585       if (this_channel)
586         {
587           g_object_unref (this_channel);
588           if (empathy_proxy_equal (channel, this_channel))
589               return window->window;
590         }
591     }
592
593   return NULL;
594 }
595
596 void
597 empathy_call_window_set_channel (GtkWidget *window, TpChannel *channel)
598 {
599   GSList *l;
600
601   g_return_if_fail (GTK_IS_WIDGET (window));
602   g_return_if_fail (TP_IS_CHANNEL (channel));
603
604   for (l = windows; l; l = l->next)
605     {
606       EmpathyCallWindow *call_window = l->data;
607
608       if (call_window->window == window)
609         {
610           if (!call_window->call)
611             {
612               call_window->call = empathy_tp_call_new (channel);
613               call_window_update (call_window);
614             }
615           break;
616         }
617     }
618 }
619