~ubuntu-branches/debian/sid/empathy/sid-201208312031

« back to all changes in this revision

Viewing changes to .pc/0001-Call-Fix-floating-toolbar.patch/src/empathy-call-window.c

  • Committer: Package Import Robot
  • Author(s): Sjoerd Simons
  • Date: 2012-05-19 22:31:44 UTC
  • Revision ID: package-import@ubuntu.com-20120519223144-zfpfwtkyant82fcv
Tags: 3.4.2-2
* debian/patches/0001-Call-Fix-floating-toolbar.patch
  - Added. Fix the call toolbar not responding to events (from upstream git)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * empathy-call-window.c - Source for EmpathyCallWindow
 
3
 * Copyright (C) 2008-2011 Collabora Ltd.
 
4
 * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
 
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
 
 
21
#include "config.h"
 
22
 
 
23
#include <stdio.h>
 
24
#include <stdlib.h>
 
25
 
 
26
#include <math.h>
 
27
 
 
28
#include <gdk/gdkkeysyms.h>
 
29
#include <gst/gst.h>
 
30
#include <gtk/gtk.h>
 
31
#include <glib/gi18n.h>
 
32
 
 
33
#include <clutter/clutter.h>
 
34
#include <clutter-gtk/clutter-gtk.h>
 
35
#include <clutter-gst/clutter-gst.h>
 
36
 
 
37
#include <telepathy-glib/util.h>
 
38
#include <telepathy-farstream/telepathy-farstream.h>
 
39
#include <telepathy-glib/util.h>
 
40
 
 
41
#include <farstream/fs-element-added-notifier.h>
 
42
#include <farstream/fs-utils.h>
 
43
 
 
44
#include <libempathy/empathy-camera-monitor.h>
 
45
#include <libempathy/empathy-gsettings.h>
 
46
#include <libempathy/empathy-tp-contact-factory.h>
 
47
#include <libempathy/empathy-request-util.h>
 
48
#include <libempathy/empathy-utils.h>
 
49
 
 
50
#include <libempathy-gtk/empathy-avatar-image.h>
 
51
#include <libempathy-gtk/empathy-dialpad-widget.h>
 
52
#include <libempathy-gtk/empathy-ui-utils.h>
 
53
#include <libempathy-gtk/empathy-sound-manager.h>
 
54
#include <libempathy-gtk/empathy-geometry.h>
 
55
#include <libempathy-gtk/empathy-images.h>
 
56
#include <libempathy-gtk/empathy-call-utils.h>
 
57
 
 
58
#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
 
59
#include <libempathy/empathy-debug.h>
 
60
 
 
61
#include "empathy-call-window.h"
 
62
#include "empathy-call-window-fullscreen.h"
 
63
#include "empathy-call-factory.h"
 
64
#include "empathy-video-widget.h"
 
65
#include "empathy-about-dialog.h"
 
66
#include "empathy-audio-src.h"
 
67
#include "empathy-audio-sink.h"
 
68
#include "empathy-video-src.h"
 
69
#include "empathy-mic-menu.h"
 
70
#include "empathy-preferences.h"
 
71
#include "empathy-rounded-actor.h"
 
72
#include "empathy-rounded-rectangle.h"
 
73
#include "empathy-rounded-texture.h"
 
74
#include "empathy-camera-menu.h"
 
75
 
 
76
#define CONTENT_HBOX_BORDER_WIDTH 6
 
77
#define CONTENT_HBOX_SPACING 3
 
78
#define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
 
79
 
 
80
#define SELF_VIDEO_SECTION_WIDTH 120
 
81
#define SELF_VIDEO_SECTION_HEIGHT 90
 
82
#define SELF_VIDEO_SECTION_MARGIN 2
 
83
#define SELF_VIDEO_SECTION_BORDER SELF_VIDEO_SECTION_MARGIN*2
 
84
 
 
85
#define FLOATING_TOOLBAR_OPACITY 192
 
86
#define FLOATING_TOOLBAR_SPACING 20
 
87
 
 
88
/* The avatar's default width and height are set to the same value because we
 
89
   want a square icon. */
 
90
#define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
 
91
#define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
 
92
  EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
 
93
 
 
94
#define SMALL_TOOLBAR_SIZE 36
 
95
 
 
96
/* If an video input error occurs, the error message will start with "v4l" */
 
97
#define VIDEO_INPUT_ERROR_PREFIX "v4l"
 
98
 
 
99
/* The time interval in milliseconds between 2 outgoing rings */
 
100
#define MS_BETWEEN_RING 500
 
101
 
 
102
/* The roundedness of preview box and placeholders */
 
103
#define PREVIEW_ROUND_FACTOR 16
 
104
 
 
105
G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
 
106
 
 
107
enum {
 
108
  PROP_CALL_HANDLER = 1,
 
109
};
 
110
 
 
111
typedef enum {
 
112
  RINGING,       /* Incoming call */
 
113
  CONNECTING,    /* Outgoing call */
 
114
  CONNECTED,     /* Connected */
 
115
  HELD,          /* Connected, but on hold */
 
116
  DISCONNECTED,  /* Disconnected */
 
117
  REDIALING      /* Redialing (special case of CONNECTING) */
 
118
} CallState;
 
119
 
 
120
typedef enum {
 
121
  CAMERA_STATE_OFF = 0,
 
122
  CAMERA_STATE_ON,
 
123
} CameraState;
 
124
 
 
125
typedef enum {
 
126
  PREVIEW_POS_NONE,
 
127
  PREVIEW_POS_TOP_LEFT,
 
128
  PREVIEW_POS_TOP_RIGHT,
 
129
  PREVIEW_POS_BOTTOM_LEFT,
 
130
  PREVIEW_POS_BOTTOM_RIGHT,
 
131
} PreviewPosition;
 
132
 
 
133
struct _EmpathyCallWindowPriv
 
134
{
 
135
  gboolean dispose_has_run;
 
136
  EmpathyCallHandler *handler;
 
137
 
 
138
  EmpathyContact *contact;
 
139
 
 
140
  EmpathyCameraMonitor *camera_monitor;
 
141
 
 
142
  guint call_state;
 
143
  gboolean outgoing;
 
144
 
 
145
  GtkUIManager *ui_manager;
 
146
  GtkWidget *errors_vbox;
 
147
  /* widget displays the video received from the remote user. This widget is
 
148
   * alive only during call. */
 
149
  ClutterActor *video_output;
 
150
  ClutterActor *video_preview;
 
151
  ClutterActor *drag_preview;
 
152
  ClutterActor *preview_shown_button;
 
153
  ClutterActor *preview_hidden_button;
 
154
  ClutterActor *preview_rectangle1;
 
155
  ClutterActor *preview_rectangle2;
 
156
  ClutterActor *preview_rectangle3;
 
157
  ClutterActor *preview_rectangle4;
 
158
  ClutterActor *preview_spinner_actor;
 
159
  GtkWidget *preview_spinner_widget;
 
160
  GtkWidget *video_container;
 
161
  GtkWidget *remote_user_avatar_widget;
 
162
  GtkWidget *remote_user_avatar_toolbar;
 
163
  GtkWidget *remote_user_name_toolbar;
 
164
  GtkWidget *status_label;
 
165
  GtkWidget *hangup_button;
 
166
  GtkWidget *audio_call_button;
 
167
  GtkWidget *video_call_button;
 
168
  GtkWidget *mic_button;
 
169
  GtkWidget *volume_button;
 
170
  GtkWidget *camera_button;
 
171
  GtkWidget *dialpad_button;
 
172
  GtkWidget *toolbar;
 
173
  GtkWidget *bottom_toolbar;
 
174
  ClutterActor *floating_toolbar;
 
175
  GtkWidget *pane;
 
176
  GtkAction *menu_fullscreen;
 
177
  GtkAction *menu_swap_camera;
 
178
 
 
179
  ClutterState *transitions;
 
180
 
 
181
  /* The main box covering all the stage, contaning remote avatar/video */
 
182
  ClutterActor *video_box;
 
183
  ClutterLayoutManager *video_layout;
 
184
 
 
185
  /* A Box layout manager containing a bin for previews
 
186
   * and the floating toolbar */
 
187
  ClutterActor *overlay_box;
 
188
  ClutterLayoutManager *overlay_layout;
 
189
 
 
190
  /* Bin layout for the previews */
 
191
  ClutterActor *preview_box;
 
192
  ClutterLayoutManager *preview_layout;
 
193
 
 
194
  /* Coordinates of the preview drag event's start. */
 
195
  PreviewPosition preview_pos;
 
196
 
 
197
  /* We keep a reference on the hbox which contains the main content so we can
 
198
     easilly repack everything when toggling fullscreen */
 
199
  GtkWidget *content_hbox;
 
200
 
 
201
  /* These are used to accept or reject an incoming call when the status
 
202
     is RINGING. */
 
203
  GtkWidget *incoming_call_dialog;
 
204
  TpCallChannel *pending_channel;
 
205
  TpChannelDispatchOperation *pending_cdo;
 
206
  TpAddDispatchOperationContext *pending_context;
 
207
 
 
208
  gulong video_output_motion_handler_id;
 
209
  guint bus_message_source_id;
 
210
 
 
211
  GtkWidget *dtmf_panel;
 
212
 
 
213
  /* Details vbox */
 
214
  GtkWidget *details_vbox;
 
215
  GtkWidget *vcodec_encoding_label;
 
216
  GtkWidget *acodec_encoding_label;
 
217
  GtkWidget *vcodec_decoding_label;
 
218
  GtkWidget *acodec_decoding_label;
 
219
 
 
220
  GtkWidget *audio_remote_candidate_label;
 
221
  GtkWidget *audio_local_candidate_label;
 
222
  GtkWidget *video_remote_candidate_label;
 
223
  GtkWidget *video_local_candidate_label;
 
224
  GtkWidget *video_remote_candidate_info_img;
 
225
  GtkWidget *video_local_candidate_info_img;
 
226
  GtkWidget *audio_remote_candidate_info_img;
 
227
  GtkWidget *audio_local_candidate_info_img;
 
228
 
 
229
  GstElement *video_input;
 
230
  GstElement *video_preview_sink;
 
231
  GstElement *video_output_sink;
 
232
  GstElement *audio_input;
 
233
  GstElement *audio_output;
 
234
  gboolean audio_output_added;
 
235
  GstElement *pipeline;
 
236
  GstElement *video_tee;
 
237
 
 
238
  GstElement *funnel;
 
239
 
 
240
  GList *notifiers;
 
241
 
 
242
  GTimer *timer;
 
243
  guint timer_id;
 
244
 
 
245
  GMutex *lock;
 
246
  gboolean call_started;
 
247
  gboolean sending_video;
 
248
  CameraState camera_state;
 
249
 
 
250
  EmpathyCallWindowFullscreen *fullscreen;
 
251
  gboolean is_fullscreen;
 
252
 
 
253
  gboolean got_video;
 
254
  guint got_video_src;
 
255
 
 
256
  guint inactivity_src;
 
257
 
 
258
  /* Those fields represent the state of the window before it actually was in
 
259
     fullscreen mode. */
 
260
  gboolean dialpad_was_visible_before_fs;
 
261
  gint original_width_before_fs;
 
262
  gint original_height_before_fs;
 
263
 
 
264
  gint x, y, w, h, dialpad_width;
 
265
  gboolean maximized;
 
266
 
 
267
  /* TRUE if the call should be started when the pipeline is playing */
 
268
  gboolean start_call_when_playing;
 
269
  /* TRUE if we requested to set the pipeline in the playing state */
 
270
  gboolean pipeline_playing;
 
271
 
 
272
  EmpathySoundManager *sound_mgr;
 
273
 
 
274
  GSettings *settings;
 
275
  EmpathyMicMenu *mic_menu;
 
276
  EmpathyCameraMenu *camera_menu;
 
277
};
 
278
 
 
279
#define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv)
 
280
 
 
281
static void empathy_call_window_realized_cb (GtkWidget *widget,
 
282
  EmpathyCallWindow *window);
 
283
 
 
284
static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
 
285
  GdkEvent *event, EmpathyCallWindow *window);
 
286
 
 
287
static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
 
288
  GdkEventWindowState *event, EmpathyCallWindow *window);
 
289
 
 
290
static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
 
291
  CameraState state);
 
292
 
 
293
static void empathy_call_window_hangup_cb (gpointer object,
 
294
  EmpathyCallWindow *window);
 
295
 
 
296
static void empathy_call_window_fullscreen_cb (gpointer object,
 
297
  EmpathyCallWindow *window);
 
298
 
 
299
static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
 
300
 
 
301
static gboolean empathy_call_window_video_button_press_cb (
 
302
  GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
 
303
 
 
304
static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
 
305
  GdkEventKey *event, EmpathyCallWindow *window);
 
306
 
 
307
static gboolean empathy_call_window_video_output_motion_notify (
 
308
  GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
 
309
 
 
310
static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
 
311
  guint button);
 
312
 
 
313
static void empathy_call_window_connect_handler (EmpathyCallWindow *self);
 
314
 
 
315
static void empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
 
316
  EmpathyCallWindow *window);
 
317
 
 
318
static void empathy_call_window_restart_call (EmpathyCallWindow *window);
 
319
 
 
320
static void empathy_call_window_status_message (EmpathyCallWindow *window,
 
321
  gchar *message);
 
322
 
 
323
static gboolean empathy_call_window_bus_message (GstBus *bus,
 
324
  GstMessage *message, gpointer user_data);
 
325
 
 
326
static void
 
327
make_background_transparent (GtkClutterActor *actor)
 
328
{
 
329
  GdkRGBA transparent = { 0., 0., 0., 0. };
 
330
  GtkWidget *widget;
 
331
 
 
332
  widget = gtk_clutter_actor_get_widget (actor);
 
333
  gtk_widget_override_background_color (widget, GTK_STATE_FLAG_NORMAL, &transparent);
 
334
}
 
335
 
 
336
static void
 
337
empathy_call_window_show_hangup_button (EmpathyCallWindow *self,
 
338
    gboolean show)
 
339
{
 
340
  gtk_widget_set_visible (self->priv->hangup_button, show);
 
341
  gtk_widget_set_visible (self->priv->audio_call_button, !show);
 
342
  gtk_widget_set_visible (self->priv->video_call_button, !show);
 
343
}
 
344
 
 
345
static void
 
346
empathy_call_window_audio_call_cb (GtkToggleToolButton *button,
 
347
    EmpathyCallWindow *self)
 
348
{
 
349
  g_object_set (self->priv->handler, "initial-video", FALSE, NULL);
 
350
  empathy_call_window_restart_call (self);
 
351
}
 
352
 
 
353
static void
 
354
empathy_call_window_video_call_cb (GtkToggleToolButton *button,
 
355
    EmpathyCallWindow *self)
 
356
{
 
357
  empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
 
358
  g_object_set (self->priv->handler, "initial-video", TRUE, NULL);
 
359
  empathy_call_window_restart_call (self);
 
360
}
 
361
 
 
362
static void
 
363
dtmf_start_tone_cb (EmpathyDialpadWidget *dialpad,
 
364
    TpDTMFEvent event,
 
365
    EmpathyCallWindow *self)
 
366
{
 
367
  TpCallChannel *call;
 
368
  gchar tones[2];
 
369
 
 
370
  g_object_get (self->priv->handler, "call-channel", &call, NULL);
 
371
 
 
372
  tones[0] = tp_dtmf_event_to_char (event);
 
373
  tones[1] = '\0';
 
374
  tp_call_channel_send_tones_async (call, tones, NULL, NULL, NULL);
 
375
 
 
376
  g_object_unref (call);
 
377
}
 
378
 
 
379
static void
 
380
empathy_call_window_show_video_output (EmpathyCallWindow *self,
 
381
    gboolean show)
 
382
{
 
383
  if (self->priv->video_output != NULL)
 
384
    g_object_set (self->priv->video_output, "visible", show, NULL);
 
385
 
 
386
  gtk_widget_set_visible (self->priv->remote_user_avatar_widget, !show);
 
387
 
 
388
  clutter_actor_raise_top (self->priv->overlay_box);
 
389
}
 
390
 
 
391
static void
 
392
create_video_output_widget (EmpathyCallWindow *self)
 
393
{
 
394
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
395
 
 
396
  g_assert (priv->video_output == NULL);
 
397
  g_assert (priv->pipeline != NULL);
 
398
 
 
399
  priv->video_output = clutter_texture_new ();
 
400
 
 
401
  clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (priv->video_output),
 
402
      TRUE);
 
403
 
 
404
  priv->video_output_sink = gst_element_factory_make ("cluttersink", NULL);
 
405
  if (priv->video_output_sink == NULL)
 
406
    g_error ("Missing cluttersink");
 
407
  else
 
408
    g_object_set (priv->video_output_sink, "texture", priv->video_output, NULL);
 
409
 
 
410
  clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
 
411
      priv->video_output);
 
412
 
 
413
  gtk_widget_add_events (priv->video_container,
 
414
      GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
 
415
  g_signal_connect (G_OBJECT (priv->video_container), "button-press-event",
 
416
      G_CALLBACK (empathy_call_window_video_button_press_cb), self);
 
417
}
 
418
 
 
419
static void
 
420
create_video_input (EmpathyCallWindow *self)
 
421
{
 
422
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
423
 
 
424
  g_assert (priv->video_input == NULL);
 
425
  priv->video_input = empathy_video_src_new ();
 
426
  gst_object_ref_sink (priv->video_input);
 
427
}
 
428
 
 
429
static gboolean
 
430
audio_control_volume_to_element (GBinding *binding,
 
431
  const GValue *source_value,
 
432
  GValue *target_value,
 
433
  gpointer user_data)
 
434
{
 
435
  /* AudioControl volume is 0-255, with -1 for unknown */
 
436
  gint hv;
 
437
 
 
438
  hv = g_value_get_int (source_value);
 
439
  if (hv < 0)
 
440
    return FALSE;
 
441
 
 
442
  hv = MIN (hv, 255);
 
443
  g_value_set_double (target_value, hv/255.0);
 
444
 
 
445
  return TRUE;
 
446
}
 
447
 
 
448
static gboolean
 
449
element_volume_to_audio_control (GBinding *binding,
 
450
  const GValue *source_value,
 
451
  GValue *target_value,
 
452
  gpointer user_data)
 
453
{
 
454
  gdouble ev;
 
455
 
 
456
  ev = g_value_get_double (source_value);
 
457
  ev = CLAMP (ev, 0.0, 1.0);
 
458
 
 
459
  g_value_set_int (target_value, ev * 255);
 
460
  return TRUE;
 
461
}
 
462
 
 
463
static void
 
464
create_audio_input (EmpathyCallWindow *self)
 
465
{
 
466
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
467
 
 
468
  g_assert (priv->audio_input == NULL);
 
469
  priv->audio_input = empathy_audio_src_new ();
 
470
  gst_object_ref_sink (priv->audio_input);
 
471
 
 
472
  g_object_bind_property (priv->mic_button, "active",
 
473
    priv->audio_input, "mute",
 
474
    G_BINDING_BIDIRECTIONAL |
 
475
      G_BINDING_INVERT_BOOLEAN | G_BINDING_SYNC_CREATE);
 
476
}
 
477
 
 
478
static void
 
479
add_video_preview_to_pipeline (EmpathyCallWindow *self)
 
480
{
 
481
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
482
  GstElement *preview;
 
483
 
 
484
  g_assert (priv->video_preview != NULL);
 
485
  g_assert (priv->pipeline != NULL);
 
486
  g_assert (priv->video_input != NULL);
 
487
  g_assert (priv->video_tee != NULL);
 
488
 
 
489
  preview = priv->video_preview_sink;
 
490
 
 
491
  if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
 
492
    {
 
493
      g_warning ("Could not add video input to pipeline");
 
494
      return;
 
495
    }
 
496
 
 
497
  if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
 
498
    {
 
499
      g_warning ("Could not add video preview to pipeline");
 
500
      return;
 
501
    }
 
502
 
 
503
  if (!gst_element_link (priv->video_input, priv->video_tee))
 
504
    {
 
505
      g_warning ("Could not link video input to video tee");
 
506
      return;
 
507
    }
 
508
 
 
509
  if (!gst_element_link (priv->video_tee, preview))
 
510
    {
 
511
      g_warning ("Could not link video tee to video preview");
 
512
      return;
 
513
    }
 
514
}
 
515
 
 
516
static void
 
517
empathy_call_window_disable_camera_cb (GtkAction *action,
 
518
    EmpathyCallWindow *self)
 
519
{
 
520
  clutter_actor_destroy (self->priv->preview_hidden_button);
 
521
 
 
522
  gtk_toggle_tool_button_set_active (
 
523
      GTK_TOGGLE_TOOL_BUTTON (self->priv->camera_button), FALSE);
 
524
}
 
525
 
 
526
static void
 
527
empathy_call_window_minimise_camera_cb (GtkAction *action,
 
528
    EmpathyCallWindow *self)
 
529
{
 
530
  clutter_actor_hide (self->priv->video_preview);
 
531
  clutter_actor_show (self->priv->preview_hidden_button);
 
532
}
 
533
 
 
534
static void
 
535
empathy_call_window_maximise_camera_cb (GtkAction *action,
 
536
    EmpathyCallWindow *self)
 
537
{
 
538
  clutter_actor_show (self->priv->video_preview);
 
539
  clutter_actor_hide (self->priv->preview_hidden_button);
 
540
}
 
541
 
 
542
static void
 
543
empathy_call_window_swap_camera_cb (GtkAction *action,
 
544
    EmpathyCallWindow *self)
 
545
{
 
546
  const GList *cameras, *l;
 
547
  gchar *current_cam;
 
548
 
 
549
  DEBUG ("Swapping the camera");
 
550
 
 
551
  cameras = empathy_camera_monitor_get_cameras (self->priv->camera_monitor);
 
552
  current_cam = empathy_video_src_dup_device (
 
553
      EMPATHY_GST_VIDEO_SRC (self->priv->video_input));
 
554
 
 
555
  for (l = cameras; l != NULL; l = l->next)
 
556
    {
 
557
      EmpathyCamera *camera = l->data;
 
558
 
 
559
      if (!tp_strdiff (camera->device, current_cam))
 
560
        {
 
561
          EmpathyCamera *next;
 
562
 
 
563
          if (l->next != NULL)
 
564
            next = l->next->data;
 
565
          else
 
566
            next = cameras->data;
 
567
 
 
568
          /* EmpathyCameraMenu will update itself and do the actual change
 
569
           * for us */
 
570
          g_settings_set_string (self->priv->settings,
 
571
              EMPATHY_PREFS_CALL_CAMERA_DEVICE,
 
572
              next->device);
 
573
 
 
574
          break;
 
575
        }
 
576
    }
 
577
 
 
578
  g_free (current_cam);
 
579
}
 
580
 
 
581
static void
 
582
empathy_call_window_camera_added_cb (EmpathyCameraMonitor *monitor,
 
583
    EmpathyCamera *camera,
 
584
    EmpathyCallWindow *self)
 
585
{
 
586
  const GList *cameras = empathy_camera_monitor_get_cameras (monitor);
 
587
 
 
588
  gtk_action_set_visible (self->priv->menu_swap_camera,
 
589
      g_list_length ((GList *) cameras) >= 2);
 
590
}
 
591
 
 
592
static void
 
593
empathy_call_window_camera_removed_cb (EmpathyCameraMonitor *monitor,
 
594
    EmpathyCamera *camera,
 
595
    EmpathyCallWindow *self)
 
596
{
 
597
  const GList *cameras = empathy_camera_monitor_get_cameras (monitor);
 
598
 
 
599
  gtk_action_set_visible (self->priv->menu_swap_camera,
 
600
      g_list_length ((GList *) cameras) >= 2);
 
601
}
 
602
 
 
603
static void
 
604
empathy_call_window_preview_button_clicked_cb (GtkButton *button,
 
605
    EmpathyCallWindow *self)
 
606
{
 
607
  GtkWidget *menu;
 
608
 
 
609
  menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
 
610
      "/preview-menu");
 
611
  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
 
612
      0, gtk_get_current_event_time ());
 
613
  gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
 
614
}
 
615
 
 
616
static void
 
617
empathy_call_window_preview_hidden_button_clicked_cb (GtkButton *button,
 
618
    EmpathyCallWindow *self)
 
619
{
 
620
  GtkWidget *menu;
 
621
 
 
622
  menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
 
623
      "/preview-hidden-menu");
 
624
  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
 
625
      0, gtk_get_current_event_time ());
 
626
  gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
 
627
}
 
628
 
 
629
static ClutterActor *
 
630
empathy_call_window_create_preview_rectangle (EmpathyCallWindow *self,
 
631
    ClutterBinAlignment x,
 
632
    ClutterBinAlignment y)
 
633
{
 
634
  ClutterActor *rectangle;
 
635
 
 
636
  rectangle = CLUTTER_ACTOR (empathy_rounded_rectangle_new (
 
637
      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGHT,
 
638
      PREVIEW_ROUND_FACTOR));
 
639
 
 
640
  clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (self->priv->preview_layout),
 
641
      rectangle, x, y);
 
642
  clutter_actor_hide (rectangle);
 
643
 
 
644
  return rectangle;
 
645
}
 
646
 
 
647
static void
 
648
empathy_call_window_create_preview_rectangles (EmpathyCallWindow *self)
 
649
{
 
650
  ClutterActor *box;
 
651
 
 
652
  self->priv->preview_layout = clutter_bin_layout_new (
 
653
      CLUTTER_BIN_ALIGNMENT_CENTER, CLUTTER_BIN_ALIGNMENT_CENTER);
 
654
  self->priv->preview_box = box = clutter_box_new (self->priv->preview_layout);
 
655
 
 
656
  clutter_box_layout_pack (CLUTTER_BOX_LAYOUT (self->priv->overlay_layout),
 
657
      box, TRUE, TRUE, TRUE,
 
658
      CLUTTER_BOX_ALIGNMENT_CENTER, CLUTTER_BOX_ALIGNMENT_START);
 
659
 
 
660
  self->priv->preview_rectangle1 =
 
661
      empathy_call_window_create_preview_rectangle (self,
 
662
          CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_START);
 
663
  self->priv->preview_rectangle2 =
 
664
      empathy_call_window_create_preview_rectangle (self,
 
665
          CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_END);
 
666
  self->priv->preview_rectangle3 =
 
667
      empathy_call_window_create_preview_rectangle (self,
 
668
          CLUTTER_BIN_ALIGNMENT_END, CLUTTER_BIN_ALIGNMENT_START);
 
669
  self->priv->preview_rectangle4 =
 
670
      empathy_call_window_create_preview_rectangle (self,
 
671
          CLUTTER_BIN_ALIGNMENT_END, CLUTTER_BIN_ALIGNMENT_END);
 
672
}
 
673
 
 
674
static void
 
675
empathy_call_window_show_preview_rectangles (EmpathyCallWindow *self,
 
676
    gboolean show)
 
677
{
 
678
  g_object_set (self->priv->preview_rectangle1, "visible", show, NULL);
 
679
  g_object_set (self->priv->preview_rectangle2, "visible", show, NULL);
 
680
  g_object_set (self->priv->preview_rectangle3, "visible", show, NULL);
 
681
  g_object_set (self->priv->preview_rectangle4, "visible", show, NULL);
 
682
}
 
683
 
 
684
static void
 
685
empathy_call_window_get_preview_coordinates (EmpathyCallWindow *self,
 
686
    PreviewPosition pos,
 
687
    guint *x,
 
688
    guint *y)
 
689
{
 
690
  guint ret_x = 0, ret_y = 0;
 
691
  ClutterGeometry box;
 
692
 
 
693
  if (!clutter_actor_has_allocation (self->priv->preview_box))
 
694
    goto out;
 
695
 
 
696
  clutter_actor_get_geometry (self->priv->preview_box, &box);
 
697
 
 
698
  switch (pos)
 
699
    {
 
700
      case PREVIEW_POS_TOP_LEFT:
 
701
        ret_x = ret_y = SELF_VIDEO_SECTION_MARGIN;
 
702
        break;
 
703
      case PREVIEW_POS_TOP_RIGHT:
 
704
        ret_x = box.width - SELF_VIDEO_SECTION_MARGIN
 
705
            - SELF_VIDEO_SECTION_WIDTH;
 
706
        ret_y = SELF_VIDEO_SECTION_MARGIN;
 
707
        break;
 
708
      case PREVIEW_POS_BOTTOM_LEFT:
 
709
        ret_x = SELF_VIDEO_SECTION_MARGIN;
 
710
        ret_y = box.height - SELF_VIDEO_SECTION_MARGIN
 
711
            - SELF_VIDEO_SECTION_HEIGHT;
 
712
        break;
 
713
      case PREVIEW_POS_BOTTOM_RIGHT:
 
714
        ret_x = box.width - SELF_VIDEO_SECTION_MARGIN
 
715
            - SELF_VIDEO_SECTION_WIDTH;
 
716
        ret_y = box.height - SELF_VIDEO_SECTION_MARGIN
 
717
            - SELF_VIDEO_SECTION_HEIGHT;
 
718
        break;
 
719
      default:
 
720
        g_warn_if_reached ();
 
721
    }
 
722
 
 
723
out:
 
724
  if (x != NULL)
 
725
    *x = ret_x;
 
726
 
 
727
  if (y != NULL)
 
728
    *y = ret_y;
 
729
}
 
730
 
 
731
static PreviewPosition
 
732
empathy_call_window_get_preview_position (EmpathyCallWindow *self,
 
733
    gfloat event_x,
 
734
    gfloat event_y)
 
735
{
 
736
  ClutterGeometry box;
 
737
  PreviewPosition pos = PREVIEW_POS_NONE;
 
738
 
 
739
  if (!clutter_actor_has_allocation (self->priv->preview_box))
 
740
    return pos;
 
741
 
 
742
  clutter_actor_get_geometry (self->priv->preview_box, &box);
 
743
 
 
744
  if (0 + SELF_VIDEO_SECTION_MARGIN <= event_x &&
 
745
      event_x <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_WIDTH) &&
 
746
      0 + SELF_VIDEO_SECTION_MARGIN <= event_y &&
 
747
      event_y <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_HEIGHT))
 
748
    {
 
749
      pos = PREVIEW_POS_TOP_LEFT;
 
750
    }
 
751
  else if (box.width - SELF_VIDEO_SECTION_MARGIN >= event_x &&
 
752
      event_x >= (box.width - SELF_VIDEO_SECTION_MARGIN - (gint) SELF_VIDEO_SECTION_WIDTH) &&
 
753
      0 + SELF_VIDEO_SECTION_MARGIN <= event_y &&
 
754
      event_y <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_HEIGHT))
 
755
    {
 
756
      pos = PREVIEW_POS_TOP_RIGHT;
 
757
    }
 
758
  else if (0 + SELF_VIDEO_SECTION_MARGIN <= event_x &&
 
759
      event_x <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_WIDTH) &&
 
760
      box.height - SELF_VIDEO_SECTION_MARGIN >= event_y &&
 
761
      event_y >= (box.height - SELF_VIDEO_SECTION_MARGIN - (gint) SELF_VIDEO_SECTION_HEIGHT))
 
762
    {
 
763
      pos = PREVIEW_POS_BOTTOM_LEFT;
 
764
    }
 
765
  else if (box.width - SELF_VIDEO_SECTION_MARGIN >= event_x &&
 
766
      event_x >= (box.width - SELF_VIDEO_SECTION_MARGIN - (gint) SELF_VIDEO_SECTION_WIDTH) &&
 
767
      box.height - 2 * SELF_VIDEO_SECTION_MARGIN >= event_y &&
 
768
      event_y >= (box.height - SELF_VIDEO_SECTION_MARGIN - (gint) SELF_VIDEO_SECTION_HEIGHT))
 
769
    {
 
770
      pos = PREVIEW_POS_BOTTOM_RIGHT;
 
771
    }
 
772
 
 
773
  return pos;
 
774
}
 
775
 
 
776
static ClutterActor *
 
777
empathy_call_window_get_preview_rectangle (EmpathyCallWindow *self,
 
778
    PreviewPosition pos)
 
779
{
 
780
  ClutterActor *rectangle;
 
781
 
 
782
  switch (pos)
 
783
    {
 
784
      case PREVIEW_POS_TOP_LEFT:
 
785
        rectangle = self->priv->preview_rectangle1;
 
786
        break;
 
787
      case PREVIEW_POS_TOP_RIGHT:
 
788
        rectangle = self->priv->preview_rectangle3;
 
789
        break;
 
790
      case PREVIEW_POS_BOTTOM_LEFT:
 
791
        rectangle = self->priv->preview_rectangle2;
 
792
        break;
 
793
      case PREVIEW_POS_BOTTOM_RIGHT:
 
794
        rectangle = self->priv->preview_rectangle4;
 
795
        break;
 
796
      default:
 
797
        rectangle = NULL;
 
798
    }
 
799
 
 
800
  return rectangle;
 
801
}
 
802
 
 
803
static void
 
804
empathy_call_window_move_video_preview (EmpathyCallWindow *self,
 
805
    PreviewPosition pos)
 
806
{
 
807
  ClutterBinLayout *layout = CLUTTER_BIN_LAYOUT (self->priv->preview_layout);
 
808
 
 
809
  DEBUG ("moving the video preview to %d", pos);
 
810
 
 
811
  self->priv->preview_pos = pos;
 
812
 
 
813
  switch (pos)
 
814
    {
 
815
      case PREVIEW_POS_TOP_LEFT:
 
816
        clutter_bin_layout_set_alignment (layout,
 
817
            self->priv->video_preview,
 
818
            CLUTTER_BIN_ALIGNMENT_START,
 
819
            CLUTTER_BIN_ALIGNMENT_START);
 
820
        break;
 
821
      case PREVIEW_POS_TOP_RIGHT:
 
822
        clutter_bin_layout_set_alignment (layout,
 
823
            self->priv->video_preview,
 
824
            CLUTTER_BIN_ALIGNMENT_END,
 
825
            CLUTTER_BIN_ALIGNMENT_START);
 
826
        break;
 
827
      case PREVIEW_POS_BOTTOM_LEFT:
 
828
        clutter_bin_layout_set_alignment (layout,
 
829
            self->priv->video_preview,
 
830
            CLUTTER_BIN_ALIGNMENT_START,
 
831
            CLUTTER_BIN_ALIGNMENT_END);
 
832
        break;
 
833
      case PREVIEW_POS_BOTTOM_RIGHT:
 
834
        clutter_bin_layout_set_alignment (layout,
 
835
            self->priv->video_preview,
 
836
            CLUTTER_BIN_ALIGNMENT_END,
 
837
            CLUTTER_BIN_ALIGNMENT_END);
 
838
        break;
 
839
      default:
 
840
        g_warn_if_reached ();
 
841
    }
 
842
 
 
843
  g_settings_set_enum (self->priv->settings, "camera-position", pos);
 
844
}
 
845
 
 
846
static void
 
847
_clutter_color_from_rgba (ClutterColor *color,
 
848
                          const GdkRGBA *rgba)
 
849
{
 
850
  color->red = (guint8) floor (rgba->red * 255);
 
851
  color->green = (guint8) floor (rgba->green * 255);
 
852
  color->blue = (guint8) floor (rgba->blue * 255);
 
853
  color->alpha = (guint8) floor (rgba->alpha * 255);
 
854
}
 
855
 
 
856
static void
 
857
empathy_call_window_highlight_preview_rectangle (EmpathyCallWindow *self,
 
858
    PreviewPosition pos)
 
859
{
 
860
  ClutterActor *rectangle;
 
861
  GtkStyleContext *context;
 
862
  GdkRGBA rgba;
 
863
  ClutterColor color, highlight;
 
864
 
 
865
  rectangle = empathy_call_window_get_preview_rectangle (self, pos);
 
866
  context = gtk_widget_get_style_context (GTK_WIDGET (self));
 
867
  gtk_style_context_get_color (context, 0, &rgba);
 
868
 
 
869
  _clutter_color_from_rgba (&color, &rgba);
 
870
  clutter_color_shade (&color, 1.4, &highlight);
 
871
 
 
872
  empathy_rounded_rectangle_set_border_width (
 
873
      EMPATHY_ROUNDED_RECTANGLE (rectangle), 2 * SELF_VIDEO_SECTION_MARGIN);
 
874
  empathy_rounded_rectangle_set_border_color (
 
875
      EMPATHY_ROUNDED_RECTANGLE (rectangle), &highlight);
 
876
}
 
877
 
 
878
static void
 
879
empathy_call_window_darken_preview_rectangle (EmpathyCallWindow *self,
 
880
    ClutterActor *rectangle)
 
881
{
 
882
  GtkStyleContext *context;
 
883
  GdkRGBA rgba;
 
884
  ClutterColor color, darker;
 
885
 
 
886
  context = gtk_widget_get_style_context (GTK_WIDGET (self));
 
887
  gtk_style_context_get_background_color (context, 0, &rgba);
 
888
 
 
889
  _clutter_color_from_rgba (&color, &rgba);
 
890
  clutter_color_shade (&color, 0.55, &darker);
 
891
 
 
892
  empathy_rounded_rectangle_set_border_width (
 
893
      EMPATHY_ROUNDED_RECTANGLE (rectangle), 1);
 
894
  empathy_rounded_rectangle_set_border_color (
 
895
      EMPATHY_ROUNDED_RECTANGLE (rectangle), &darker);
 
896
}
 
897
 
 
898
static void
 
899
empathy_call_window_darken_preview_rectangles (EmpathyCallWindow *self)
 
900
{
 
901
  ClutterActor *rectangle;
 
902
 
 
903
  rectangle = empathy_call_window_get_preview_rectangle (self,
 
904
      self->priv->preview_pos);
 
905
 
 
906
  /* We don't want to darken the rectangle where the preview
 
907
   * currently is. */
 
908
 
 
909
  if (self->priv->preview_rectangle1 != rectangle)
 
910
    empathy_call_window_darken_preview_rectangle (self,
 
911
        self->priv->preview_rectangle1);
 
912
 
 
913
  if (self->priv->preview_rectangle2 != rectangle)
 
914
    empathy_call_window_darken_preview_rectangle (self,
 
915
        self->priv->preview_rectangle2);
 
916
 
 
917
  if (self->priv->preview_rectangle3 != rectangle)
 
918
    empathy_call_window_darken_preview_rectangle (self,
 
919
        self->priv->preview_rectangle3);
 
920
 
 
921
  if (self->priv->preview_rectangle4 != rectangle)
 
922
    empathy_call_window_darken_preview_rectangle (self,
 
923
        self->priv->preview_rectangle4);
 
924
}
 
925
 
 
926
static void
 
927
empathy_call_window_preview_on_drag_begin_cb (ClutterDragAction *action,
 
928
    ClutterActor *actor,
 
929
    gfloat event_x,
 
930
    gfloat event_y,
 
931
    ClutterModifierType modifiers,
 
932
    EmpathyCallWindow *self)
 
933
{
 
934
  ClutterActor *stage = clutter_actor_get_stage (actor);
 
935
  gfloat rel_x, rel_y;
 
936
 
 
937
  self->priv->drag_preview = clutter_clone_new (actor);
 
938
 
 
939
  clutter_container_add_actor (CLUTTER_CONTAINER (stage),
 
940
      self->priv->drag_preview);
 
941
 
 
942
  clutter_actor_transform_stage_point (actor, event_x, event_y,
 
943
      &rel_x, &rel_y);
 
944
 
 
945
  clutter_actor_set_position (self->priv->drag_preview,
 
946
      event_x - rel_x, event_y - rel_y);
 
947
 
 
948
  clutter_drag_action_set_drag_handle (action,
 
949
      self->priv->drag_preview);
 
950
 
 
951
  clutter_actor_set_opacity (actor, 0);
 
952
  clutter_actor_hide (self->priv->preview_shown_button);
 
953
 
 
954
  empathy_call_window_show_preview_rectangles (self, TRUE);
 
955
  empathy_call_window_darken_preview_rectangles (self);
 
956
}
 
957
 
 
958
static void
 
959
empathy_call_window_on_animation_completed_cb (ClutterAnimation *animation,
 
960
    ClutterActor *actor)
 
961
{
 
962
  clutter_actor_set_opacity (actor, 255);
 
963
}
 
964
 
 
965
static void
 
966
empathy_call_window_preview_on_drag_end_cb (ClutterDragAction *action,
 
967
    ClutterActor *actor,
 
968
    gfloat event_x,
 
969
    gfloat event_y,
 
970
    ClutterModifierType modifiers,
 
971
    EmpathyCallWindow *self)
 
972
{
 
973
  PreviewPosition pos;
 
974
  guint x, y;
 
975
 
 
976
  /* Get the position before destroying the drag actor, otherwise the
 
977
   * preview_box allocation won't be valid and we won't be able to
 
978
   * calculate the position. */
 
979
  pos = empathy_call_window_get_preview_position (self, event_x, event_y);
 
980
 
 
981
  empathy_call_window_get_preview_coordinates (self,
 
982
      pos != PREVIEW_POS_NONE ? pos : self->priv->preview_pos,
 
983
      &x, &y);
 
984
 
 
985
  /* Move the preview to the destination and destroy it afterwards */
 
986
  clutter_actor_animate (self->priv->drag_preview, CLUTTER_LINEAR, 500,
 
987
      "x", (gfloat) x,
 
988
      "y", (gfloat) y,
 
989
      "signal-swapped-after::completed",
 
990
        clutter_actor_destroy, self->priv->drag_preview,
 
991
      "signal-swapped-after::completed",
 
992
        clutter_actor_show, self->priv->preview_shown_button,
 
993
      "signal::completed",
 
994
        empathy_call_window_on_animation_completed_cb, actor,
 
995
      NULL);
 
996
 
 
997
  self->priv->drag_preview = NULL;
 
998
 
 
999
  if (pos != PREVIEW_POS_NONE)
 
1000
    empathy_call_window_move_video_preview (self, pos);
 
1001
 
 
1002
  empathy_call_window_show_preview_rectangles (self, FALSE);
 
1003
}
 
1004
 
 
1005
static void
 
1006
empathy_call_window_preview_on_drag_motion_cb (ClutterDragAction *action,
 
1007
    ClutterActor *actor,
 
1008
    gfloat delta_x,
 
1009
    gfloat delta_y,
 
1010
    EmpathyCallWindow *self)
 
1011
{
 
1012
  PreviewPosition pos;
 
1013
  gfloat event_x, event_y;
 
1014
 
 
1015
  clutter_drag_action_get_motion_coords (action, &event_x, &event_y);
 
1016
 
 
1017
  pos = empathy_call_window_get_preview_position (self, event_x, event_y);
 
1018
 
 
1019
  if (pos != PREVIEW_POS_NONE)
 
1020
    empathy_call_window_highlight_preview_rectangle (self, pos);
 
1021
  else
 
1022
    empathy_call_window_darken_preview_rectangles (self);
 
1023
}
 
1024
 
 
1025
static gboolean
 
1026
empathy_call_window_preview_enter_event_cb (ClutterActor *actor,
 
1027
    ClutterCrossingEvent *event,
 
1028
    EmpathyCallWindow *self)
 
1029
{
 
1030
  ClutterActor *rectangle;
 
1031
 
 
1032
  rectangle = empathy_call_window_get_preview_rectangle (self,
 
1033
      self->priv->preview_pos);
 
1034
 
 
1035
  empathy_call_window_highlight_preview_rectangle (self,
 
1036
      self->priv->preview_pos);
 
1037
 
 
1038
  clutter_actor_show (rectangle);
 
1039
 
 
1040
  return FALSE;
 
1041
}
 
1042
 
 
1043
static gboolean
 
1044
empathy_call_window_preview_leave_event_cb (ClutterActor *actor,
 
1045
    ClutterCrossingEvent *event,
 
1046
    EmpathyCallWindow *self)
 
1047
{
 
1048
  ClutterActor *rectangle;
 
1049
 
 
1050
  rectangle = empathy_call_window_get_preview_rectangle (self,
 
1051
      self->priv->preview_pos);
 
1052
 
 
1053
  empathy_call_window_darken_preview_rectangle (self, rectangle);
 
1054
 
 
1055
  clutter_actor_hide (rectangle);
 
1056
 
 
1057
  return FALSE;
 
1058
}
 
1059
 
 
1060
static void
 
1061
create_video_preview (EmpathyCallWindow *self)
 
1062
{
 
1063
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
1064
  ClutterLayoutManager *layout;
 
1065
  ClutterActor *preview;
 
1066
  ClutterActor *b;
 
1067
  ClutterAction *action;
 
1068
  GtkWidget *button;
 
1069
  PreviewPosition pos;
 
1070
 
 
1071
  g_assert (priv->video_preview == NULL);
 
1072
 
 
1073
  pos = g_settings_get_enum (priv->settings, "camera-position");
 
1074
 
 
1075
  preview = empathy_rounded_texture_new ();
 
1076
  clutter_actor_set_size (preview,
 
1077
      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGHT);
 
1078
 
 
1079
  priv->video_preview_sink = gst_element_factory_make ("cluttersink", NULL);
 
1080
  if (priv->video_preview_sink == NULL)
 
1081
      g_error ("Missing cluttersink, check your clutter-gst installation");
 
1082
  g_object_set (priv->video_preview_sink, "texture", preview, NULL);
 
1083
  g_object_add_weak_pointer (G_OBJECT (priv->video_preview_sink), (gpointer) &priv->video_preview_sink);
 
1084
 
 
1085
  /* Add a little offset to the video preview */
 
1086
  layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
 
1087
      CLUTTER_BIN_ALIGNMENT_CENTER);
 
1088
  priv->video_preview = clutter_box_new (layout);
 
1089
  clutter_actor_set_size (priv->video_preview,
 
1090
      SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_SECTION_MARGIN,
 
1091
      SELF_VIDEO_SECTION_HEIGHT + 2 * SELF_VIDEO_SECTION_MARGIN);
 
1092
 
 
1093
  /* Spinner for when changing the camera device */
 
1094
  priv->preview_spinner_widget = gtk_spinner_new ();
 
1095
  priv->preview_spinner_actor = empathy_rounded_actor_new (PREVIEW_ROUND_FACTOR);
 
1096
 
 
1097
  g_object_set (priv->preview_spinner_widget, "expand", TRUE, NULL);
 
1098
  make_background_transparent (GTK_CLUTTER_ACTOR (priv->preview_spinner_actor));
 
1099
  gtk_widget_show (priv->preview_spinner_widget);
 
1100
 
 
1101
  gtk_container_add (
 
1102
      GTK_CONTAINER (gtk_clutter_actor_get_widget (
 
1103
          GTK_CLUTTER_ACTOR (priv->preview_spinner_actor))),
 
1104
      priv->preview_spinner_widget);
 
1105
  clutter_actor_set_size (priv->preview_spinner_actor,
 
1106
      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGHT);
 
1107
  clutter_actor_set_opacity (priv->preview_spinner_actor, 128);
 
1108
  clutter_actor_hide (priv->preview_spinner_actor);
 
1109
 
 
1110
  clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview),
 
1111
      preview);
 
1112
  clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview),
 
1113
      priv->preview_spinner_actor);
 
1114
 
 
1115
  g_object_set (priv->video_preview_sink,
 
1116
      "sync", FALSE,
 
1117
      "async", FALSE,
 
1118
      NULL);
 
1119
 
 
1120
  /* Translators: this is an "Info" label. It should be as short
 
1121
   * as possible. */
 
1122
  button = gtk_button_new_with_label (_("i"));
 
1123
  priv->preview_shown_button = b = gtk_clutter_actor_new_with_contents (button);
 
1124
  clutter_actor_set_size (b, 24, 24);
 
1125
  clutter_actor_set_margin_right (b, 4);
 
1126
  clutter_actor_set_margin_bottom (b, 2);
 
1127
  make_background_transparent (GTK_CLUTTER_ACTOR (b));
 
1128
 
 
1129
  clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout), b,
 
1130
      CLUTTER_BIN_ALIGNMENT_END, CLUTTER_BIN_ALIGNMENT_END);
 
1131
 
 
1132
  g_signal_connect (button, "clicked",
 
1133
      G_CALLBACK (empathy_call_window_preview_button_clicked_cb),
 
1134
      self);
 
1135
 
 
1136
  /* Translators: this is an "Info" label. It should be as short
 
1137
   * as possible. */
 
1138
  button = gtk_button_new_with_label (_("i"));
 
1139
  priv->preview_hidden_button = b = gtk_clutter_actor_new_with_contents (button);
 
1140
  clutter_actor_set_size (b, 24, 24);
 
1141
  make_background_transparent (GTK_CLUTTER_ACTOR (b));
 
1142
 
 
1143
  clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->preview_layout),
 
1144
      priv->preview_hidden_button,
 
1145
      CLUTTER_BIN_ALIGNMENT_START,
 
1146
      CLUTTER_BIN_ALIGNMENT_END);
 
1147
 
 
1148
  self->priv->preview_pos = PREVIEW_POS_BOTTOM_LEFT;
 
1149
 
 
1150
  clutter_actor_hide (priv->preview_hidden_button);
 
1151
 
 
1152
  g_signal_connect (button, "clicked",
 
1153
      G_CALLBACK (empathy_call_window_preview_hidden_button_clicked_cb),
 
1154
      self);
 
1155
 
 
1156
  clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->preview_layout),
 
1157
      priv->video_preview,
 
1158
      CLUTTER_BIN_ALIGNMENT_START,
 
1159
      CLUTTER_BIN_ALIGNMENT_END);
 
1160
 
 
1161
  empathy_call_window_move_video_preview (self, pos);
 
1162
 
 
1163
  action = clutter_drag_action_new ();
 
1164
  g_signal_connect (action, "drag-begin",
 
1165
      G_CALLBACK (empathy_call_window_preview_on_drag_begin_cb), self);
 
1166
  g_signal_connect (action, "drag-end",
 
1167
      G_CALLBACK (empathy_call_window_preview_on_drag_end_cb), self);
 
1168
  g_signal_connect (action, "drag-motion",
 
1169
      G_CALLBACK (empathy_call_window_preview_on_drag_motion_cb), self);
 
1170
 
 
1171
  g_signal_connect (preview, "enter-event",
 
1172
      G_CALLBACK (empathy_call_window_preview_enter_event_cb), self);
 
1173
  g_signal_connect (preview, "leave-event",
 
1174
      G_CALLBACK (empathy_call_window_preview_leave_event_cb), self);
 
1175
 
 
1176
  clutter_actor_add_action (preview, action);
 
1177
  clutter_actor_set_reactive (preview, TRUE);
 
1178
  clutter_actor_set_reactive (priv->preview_shown_button, TRUE);
 
1179
}
 
1180
 
 
1181
static void
 
1182
empathy_call_window_start_camera_spinning (EmpathyCallWindow *self)
 
1183
{
 
1184
  clutter_actor_show (self->priv->preview_spinner_actor);
 
1185
  gtk_spinner_start (GTK_SPINNER (self->priv->preview_spinner_widget));
 
1186
}
 
1187
 
 
1188
static void
 
1189
empathy_call_window_stop_camera_spinning (EmpathyCallWindow *self)
 
1190
{
 
1191
  clutter_actor_hide (self->priv->preview_spinner_actor);
 
1192
  gtk_spinner_stop (GTK_SPINNER (self->priv->preview_spinner_widget));
 
1193
}
 
1194
 
 
1195
void
 
1196
empathy_call_window_play_camera (EmpathyCallWindow *self,
 
1197
    gboolean play)
 
1198
{
 
1199
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
1200
  GstElement *preview;
 
1201
  GstState state;
 
1202
 
 
1203
  if (priv->video_preview == NULL)
 
1204
    {
 
1205
      create_video_preview (self);
 
1206
      add_video_preview_to_pipeline (self);
 
1207
    }
 
1208
 
 
1209
  if (play)
 
1210
    {
 
1211
      state = GST_STATE_PLAYING;
 
1212
    }
 
1213
  else
 
1214
    {
 
1215
      empathy_call_window_start_camera_spinning (self);
 
1216
      state = GST_STATE_NULL;
 
1217
    }
 
1218
 
 
1219
  preview = priv->video_preview_sink;
 
1220
 
 
1221
  gst_element_set_state (preview, state);
 
1222
  gst_element_set_state (priv->video_tee, state);
 
1223
  gst_element_set_state (priv->video_input, state);
 
1224
}
 
1225
 
 
1226
static void
 
1227
display_video_preview (EmpathyCallWindow *self,
 
1228
    gboolean display)
 
1229
{
 
1230
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
1231
 
 
1232
  if (priv->video_preview == NULL)
 
1233
    {
 
1234
      create_video_preview (self);
 
1235
      add_video_preview_to_pipeline (self);
 
1236
    }
 
1237
 
 
1238
  if (display)
 
1239
    {
 
1240
      /* Display the video preview */
 
1241
      DEBUG ("Show video preview");
 
1242
 
 
1243
      empathy_call_window_play_camera (self, TRUE);
 
1244
      clutter_actor_show (priv->video_preview);
 
1245
      clutter_actor_raise_top (priv->floating_toolbar);
 
1246
    }
 
1247
  else
 
1248
    {
 
1249
      /* Hide the video preview */
 
1250
      DEBUG ("Hide video preview");
 
1251
 
 
1252
      if (priv->video_preview != NULL)
 
1253
        {
 
1254
          clutter_actor_hide (priv->video_preview);
 
1255
          empathy_call_window_play_camera (self, FALSE);
 
1256
        }
 
1257
    }
 
1258
}
 
1259
 
 
1260
static void
 
1261
empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
 
1262
{
 
1263
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
1264
 
 
1265
  empathy_call_window_status_message (window, _("Connecting…"));
 
1266
  priv->call_state = CONNECTING;
 
1267
 
 
1268
  /* Show the toolbar */
 
1269
  clutter_state_set_state (priv->transitions, "fade-in");
 
1270
 
 
1271
  if (priv->outgoing)
 
1272
    empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
 
1273
        EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
 
1274
}
 
1275
 
 
1276
static void
 
1277
disable_camera (EmpathyCallWindow *self)
 
1278
{
 
1279
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
1280
 
 
1281
  if (priv->camera_state == CAMERA_STATE_OFF)
 
1282
    return;
 
1283
 
 
1284
  DEBUG ("Disable camera");
 
1285
 
 
1286
  empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
 
1287
 
 
1288
  priv->camera_state = CAMERA_STATE_OFF;
 
1289
}
 
1290
 
 
1291
static void
 
1292
enable_camera (EmpathyCallWindow *self)
 
1293
{
 
1294
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
1295
 
 
1296
  if (priv->camera_state == CAMERA_STATE_ON)
 
1297
    return;
 
1298
 
 
1299
  if (priv->video_input == NULL)
 
1300
    {
 
1301
      DEBUG ("Can't enable camera, no input");
 
1302
      return;
 
1303
    }
 
1304
 
 
1305
  DEBUG ("Enable camera");
 
1306
 
 
1307
  empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
 
1308
 
 
1309
  priv->camera_state = CAMERA_STATE_ON;
 
1310
}
 
1311
 
 
1312
static void
 
1313
empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
 
1314
  EmpathyCallWindow *self)
 
1315
{
 
1316
  if (gtk_toggle_tool_button_get_active (toggle))
 
1317
    enable_camera (self);
 
1318
  else
 
1319
    disable_camera (self);
 
1320
}
 
1321
 
 
1322
static void
 
1323
create_pipeline (EmpathyCallWindow *self)
 
1324
{
 
1325
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
1326
  GstBus *bus;
 
1327
 
 
1328
  g_assert (priv->pipeline == NULL);
 
1329
 
 
1330
  priv->pipeline = gst_pipeline_new (NULL);
 
1331
  priv->pipeline_playing = FALSE;
 
1332
 
 
1333
  priv->video_tee = gst_element_factory_make ("tee", NULL);
 
1334
  gst_object_ref_sink (priv->video_tee);
 
1335
 
 
1336
  gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee);
 
1337
 
 
1338
  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
 
1339
  priv->bus_message_source_id = gst_bus_add_watch (bus,
 
1340
      empathy_call_window_bus_message, self);
 
1341
 
 
1342
  g_object_unref (bus);
 
1343
}
 
1344
 
 
1345
static void
 
1346
empathy_call_window_settings_cb (GtkAction *action,
 
1347
    EmpathyCallWindow *self)
 
1348
{
 
1349
  gchar *args = g_strdup_printf ("-p %s",
 
1350
      empathy_preferences_tab_to_string (EMPATHY_PREFERENCES_TAB_CALLS));
 
1351
 
 
1352
  empathy_launch_program (BIN_DIR, "empathy", args);
 
1353
 
 
1354
  g_free (args);
 
1355
}
 
1356
 
 
1357
static void
 
1358
empathy_call_window_contents_cb (GtkAction *action,
 
1359
    EmpathyCallWindow *self)
 
1360
{
 
1361
  empathy_url_show (GTK_WIDGET (self), "help:empathy/audio-video");
 
1362
}
 
1363
 
 
1364
static void
 
1365
empathy_call_window_debug_cb (GtkAction *action,
 
1366
    EmpathyCallWindow *self)
 
1367
{
 
1368
  empathy_launch_program (BIN_DIR, "empathy-debugger", "-s Empathy.Call");
 
1369
}
 
1370
 
 
1371
static void
 
1372
empathy_call_window_about_cb (GtkAction *action,
 
1373
    EmpathyCallWindow *self)
 
1374
{
 
1375
  empathy_about_dialog_new (GTK_WINDOW (self));
 
1376
}
 
1377
 
 
1378
static gboolean
 
1379
empathy_call_window_toolbar_timeout (gpointer data)
 
1380
{
 
1381
  EmpathyCallWindow *self = data;
 
1382
 
 
1383
  /* We don't want to hide the toolbar if we're not in a call, as
 
1384
   * to show the call status all the time. */
 
1385
  if (self->priv->call_state != CONNECTING &&
 
1386
      self->priv->call_state != DISCONNECTED)
 
1387
    clutter_state_set_state (self->priv->transitions, "fade-out");
 
1388
 
 
1389
  return TRUE;
 
1390
}
 
1391
 
 
1392
static gboolean
 
1393
empathy_call_window_motion_notify_cb (GtkWidget *widget,
 
1394
    GdkEvent *event,
 
1395
    EmpathyCallWindow *self)
 
1396
{
 
1397
  clutter_state_set_state (self->priv->transitions, "fade-in");
 
1398
 
 
1399
  if (self->priv->inactivity_src > 0)
 
1400
    g_source_remove (self->priv->inactivity_src);
 
1401
 
 
1402
  self->priv->inactivity_src = g_timeout_add_seconds (3,
 
1403
      empathy_call_window_toolbar_timeout, self);
 
1404
 
 
1405
  return FALSE;
 
1406
}
 
1407
 
 
1408
static gboolean
 
1409
empathy_call_window_configure_event_cb (GtkWidget *widget,
 
1410
    GdkEvent  *event,
 
1411
    EmpathyCallWindow *self)
 
1412
{
 
1413
  GdkWindow *gdk_window;
 
1414
  GdkWindowState window_state;
 
1415
 
 
1416
  gtk_window_get_position (GTK_WINDOW (self), &self->priv->x, &self->priv->y);
 
1417
  gtk_window_get_size (GTK_WINDOW (self), &self->priv->w, &self->priv->h);
 
1418
 
 
1419
  gtk_widget_get_preferred_width (self->priv->dtmf_panel,
 
1420
      &self->priv->dialpad_width, NULL);
 
1421
 
 
1422
  gdk_window = gtk_widget_get_window (widget);
 
1423
  window_state = gdk_window_get_state (gdk_window);
 
1424
  self->priv->maximized = (window_state & GDK_WINDOW_STATE_MAXIMIZED);
 
1425
 
 
1426
  return FALSE;
 
1427
}
 
1428
 
 
1429
static void
 
1430
empathy_call_window_destroyed_cb (GtkWidget *object,
 
1431
    EmpathyCallWindow *self)
 
1432
{
 
1433
  if (gtk_widget_get_visible (self->priv->dtmf_panel))
 
1434
    {
 
1435
      /* Save the geometry as if the dialpad was hidden. */
 
1436
      empathy_geometry_save_values (GTK_WINDOW (self),
 
1437
          self->priv->x, self->priv->y,
 
1438
          self->priv->w - self->priv->dialpad_width, self->priv->h,
 
1439
          self->priv->maximized);
 
1440
    }
 
1441
}
 
1442
 
 
1443
static void
 
1444
empathy_call_window_incoming_call_response_cb (GtkDialog *dialog,
 
1445
    gint response_id,
 
1446
    EmpathyCallWindow *self)
 
1447
{
 
1448
  switch (response_id)
 
1449
    {
 
1450
      case GTK_RESPONSE_ACCEPT:
 
1451
        tp_channel_dispatch_operation_handle_with_async (
 
1452
            self->priv->pending_cdo, EMPATHY_CALL_BUS_NAME, NULL, NULL);
 
1453
 
 
1454
        tp_clear_object (&self->priv->pending_cdo);
 
1455
        tp_clear_object (&self->priv->pending_channel);
 
1456
        tp_clear_object (&self->priv->pending_context);
 
1457
 
 
1458
        break;
 
1459
      case GTK_RESPONSE_CANCEL:
 
1460
        tp_channel_dispatch_operation_close_channels_async (
 
1461
            self->priv->pending_cdo, NULL, NULL);
 
1462
 
 
1463
        empathy_call_window_status_message (self, _("Disconnected"));
 
1464
        self->priv->call_state = DISCONNECTED;
 
1465
        break;
 
1466
      default:
 
1467
        g_warn_if_reached ();
 
1468
    }
 
1469
}
 
1470
 
 
1471
static void
 
1472
empathy_call_window_set_state_ringing (EmpathyCallWindow *self)
 
1473
{
 
1474
  gboolean video;
 
1475
 
 
1476
  g_assert (self->priv->call_state != CONNECTED);
 
1477
 
 
1478
  video = tp_call_channel_has_initial_video (self->priv->pending_channel, NULL);
 
1479
 
 
1480
  empathy_call_window_status_message (self, _("Incoming call"));
 
1481
  self->priv->call_state = RINGING;
 
1482
 
 
1483
  self->priv->incoming_call_dialog = gtk_message_dialog_new (
 
1484
      GTK_WINDOW (self), GTK_DIALOG_MODAL,
 
1485
      GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
 
1486
      video ? _("Incoming video call from %s") : _("Incoming call from %s"),
 
1487
      empathy_contact_get_alias (self->priv->contact));
 
1488
 
 
1489
  gtk_dialog_add_buttons (GTK_DIALOG (self->priv->incoming_call_dialog),
 
1490
      _("Reject"), GTK_RESPONSE_CANCEL,
 
1491
      _("Answer"), GTK_RESPONSE_ACCEPT,
 
1492
      NULL);
 
1493
 
 
1494
  g_signal_connect (self->priv->incoming_call_dialog, "response",
 
1495
      G_CALLBACK (empathy_call_window_incoming_call_response_cb), self);
 
1496
  gtk_widget_show (self->priv->incoming_call_dialog);
 
1497
}
 
1498
 
 
1499
static void
 
1500
empathy_call_window_cdo_invalidated_cb (TpProxy *channel,
 
1501
    guint domain,
 
1502
    gint code,
 
1503
    gchar *message,
 
1504
    EmpathyCallWindow *self)
 
1505
{
 
1506
  tp_clear_object (&self->priv->pending_cdo);
 
1507
  tp_clear_object (&self->priv->pending_channel);
 
1508
  tp_clear_object (&self->priv->pending_context);
 
1509
 
 
1510
  /* We don't know if the incoming call has been accepted or not, so we
 
1511
   * assume it hasn't and if it has, we'll set the proper status when
 
1512
   * we get the new handler. */
 
1513
  empathy_call_window_status_message (self, _("Disconnected"));
 
1514
  self->priv->call_state = DISCONNECTED;
 
1515
 
 
1516
  gtk_widget_destroy (self->priv->incoming_call_dialog);
 
1517
  self->priv->incoming_call_dialog = NULL;
 
1518
}
 
1519
 
 
1520
void
 
1521
empathy_call_window_start_ringing (EmpathyCallWindow *self,
 
1522
    TpCallChannel *channel,
 
1523
    TpChannelDispatchOperation *dispatch_operation,
 
1524
    TpAddDispatchOperationContext *context)
 
1525
{
 
1526
  g_assert (self->priv->pending_channel == NULL);
 
1527
  g_assert (self->priv->pending_context == NULL);
 
1528
  g_assert (self->priv->pending_cdo == NULL);
 
1529
 
 
1530
  /* Start ringing and delay until the user answers or hangs. */
 
1531
  self->priv->pending_channel = g_object_ref (channel);
 
1532
  self->priv->pending_context = g_object_ref (context);
 
1533
  self->priv->pending_cdo = g_object_ref (dispatch_operation);
 
1534
 
 
1535
  g_signal_connect (self->priv->pending_cdo, "invalidated",
 
1536
      G_CALLBACK (empathy_call_window_cdo_invalidated_cb), self);
 
1537
 
 
1538
  empathy_call_window_set_state_ringing (self);
 
1539
  tp_add_dispatch_operation_context_accept (context);
 
1540
}
 
1541
 
 
1542
static void
 
1543
empathy_call_window_init (EmpathyCallWindow *self)
 
1544
{
 
1545
  EmpathyCallWindowPriv *priv;
 
1546
  GtkBuilder *gui;
 
1547
  GtkWidget *top_vbox;
 
1548
  gchar *filename;
 
1549
  ClutterConstraint *constraint;
 
1550
  ClutterActor *remote_avatar;
 
1551
  GtkStyleContext *context;
 
1552
  GtkCssProvider *provider;
 
1553
  GdkRGBA rgba;
 
1554
  ClutterColor bg;
 
1555
 
 
1556
  priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
 
1557
    EMPATHY_TYPE_CALL_WINDOW, EmpathyCallWindowPriv);
 
1558
 
 
1559
  priv->settings = g_settings_new (EMPATHY_PREFS_CALL_SCHEMA);
 
1560
 
 
1561
  filename = empathy_file_lookup ("empathy-call-window.ui", "src");
 
1562
  gui = empathy_builder_get_file (filename,
 
1563
    "call_window_vbox", &top_vbox,
 
1564
    "errors_vbox", &priv->errors_vbox,
 
1565
    "pane", &priv->pane,
 
1566
    "remote_user_name_toolbar", &priv->remote_user_name_toolbar,
 
1567
    "remote_user_avatar_toolbar", &priv->remote_user_avatar_toolbar,
 
1568
    "status_label", &priv->status_label,
 
1569
    "audiocall", &priv->audio_call_button,
 
1570
    "videocall", &priv->video_call_button,
 
1571
    "microphone", &priv->mic_button,
 
1572
    "volume", &priv->volume_button,
 
1573
    "camera", &priv->camera_button,
 
1574
    "hangup", &priv->hangup_button,
 
1575
    "dialpad", &priv->dialpad_button,
 
1576
    "toolbar", &priv->toolbar,
 
1577
    "bottom_toolbar", &priv->bottom_toolbar,
 
1578
    "ui_manager", &priv->ui_manager,
 
1579
    "menufullscreen", &priv->menu_fullscreen,
 
1580
    "menupreviewswap", &priv->menu_swap_camera,
 
1581
    "details_vbox",  &priv->details_vbox,
 
1582
    "vcodec_encoding_label", &priv->vcodec_encoding_label,
 
1583
    "acodec_encoding_label", &priv->acodec_encoding_label,
 
1584
    "acodec_decoding_label", &priv->acodec_decoding_label,
 
1585
    "vcodec_decoding_label", &priv->vcodec_decoding_label,
 
1586
    "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
 
1587
    "audio_local_candidate_label", &priv->audio_local_candidate_label,
 
1588
    "video_remote_candidate_label", &priv->video_remote_candidate_label,
 
1589
    "video_local_candidate_label", &priv->video_local_candidate_label,
 
1590
    "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
 
1591
    "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
 
1592
    "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
 
1593
    "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
 
1594
    NULL);
 
1595
  g_free (filename);
 
1596
 
 
1597
  empathy_builder_connect (gui, self,
 
1598
    "hangup", "clicked", empathy_call_window_hangup_cb,
 
1599
    "audiocall", "clicked", empathy_call_window_audio_call_cb,
 
1600
    "videocall", "clicked", empathy_call_window_video_call_cb,
 
1601
    "camera", "toggled", empathy_call_window_camera_toggled_cb,
 
1602
    "dialpad", "toggled", empathy_call_window_dialpad_cb,
 
1603
    "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
 
1604
    "menusettings", "activate", empathy_call_window_settings_cb,
 
1605
    "menucontents", "activate", empathy_call_window_contents_cb,
 
1606
    "menudebug", "activate", empathy_call_window_debug_cb,
 
1607
    "menuabout", "activate", empathy_call_window_about_cb,
 
1608
    "menupreviewdisable", "activate", empathy_call_window_disable_camera_cb,
 
1609
    "menupreviewminimise", "activate", empathy_call_window_minimise_camera_cb,
 
1610
    "menupreviewmaximise", "activate", empathy_call_window_maximise_camera_cb,
 
1611
    "menupreviewswap", "activate", empathy_call_window_swap_camera_cb,
 
1612
    NULL);
 
1613
 
 
1614
  /* FIXME: we should use a stock "OSD" style class for the toolbar,
 
1615
   * once it's available in GTK+/Adwaita.
 
1616
   */
 
1617
  provider = gtk_css_provider_new ();
 
1618
  gtk_css_provider_load_from_data (provider,
 
1619
      "#CallFloatingToolbar { border-radius: 6px; }", -1, NULL);
 
1620
  gtk_style_context_add_provider (
 
1621
      gtk_widget_get_style_context (priv->bottom_toolbar),
 
1622
      GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
 
1623
  g_object_unref (provider);
 
1624
 
 
1625
  gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
 
1626
 
 
1627
  priv->camera_monitor = empathy_camera_monitor_dup_singleton ();
 
1628
 
 
1629
  g_object_bind_property (priv->camera_monitor, "available",
 
1630
      priv->camera_button, "sensitive",
 
1631
      G_BINDING_SYNC_CREATE);
 
1632
 
 
1633
  g_signal_connect (priv->camera_monitor, "added",
 
1634
      G_CALLBACK (empathy_call_window_camera_added_cb), self);
 
1635
  g_signal_connect (priv->camera_monitor, "removed",
 
1636
      G_CALLBACK (empathy_call_window_camera_removed_cb), self);
 
1637
 
 
1638
  priv->lock = g_mutex_new ();
 
1639
 
 
1640
  gtk_container_add (GTK_CONTAINER (self), top_vbox);
 
1641
 
 
1642
  priv->content_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL,
 
1643
      CONTENT_HBOX_SPACING);
 
1644
  gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
 
1645
                                  CONTENT_HBOX_BORDER_WIDTH);
 
1646
  gtk_box_pack_start (GTK_BOX (priv->pane), priv->content_hbox,
 
1647
      TRUE, TRUE, 0);
 
1648
 
 
1649
  /* main contents remote avatar/video box */
 
1650
  priv->video_layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
 
1651
      CLUTTER_BIN_ALIGNMENT_CENTER);
 
1652
 
 
1653
  priv->video_box = clutter_box_new (priv->video_layout);
 
1654
 
 
1655
  priv->video_container = gtk_clutter_embed_new ();
 
1656
 
 
1657
  gtk_widget_set_size_request (priv->video_container,
 
1658
      EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
 
1659
 
 
1660
  /* Set the background color to that of the rest of the window */
 
1661
  context = gtk_widget_get_style_context (priv->content_hbox);
 
1662
  gtk_style_context_get_background_color (context,
 
1663
      GTK_STATE_FLAG_NORMAL, &rgba);
 
1664
  bg.red = CLAMP (rgba.red * 255.0, 0, 255);
 
1665
  bg.green = CLAMP (rgba.green * 255.0, 0, 255);
 
1666
  bg.blue = CLAMP (rgba.blue * 255.0, 0, 255);
 
1667
  bg.alpha = CLAMP (rgba.alpha * 255.0, 0, 255);
 
1668
  clutter_stage_set_color (
 
1669
      CLUTTER_STAGE (gtk_clutter_embed_get_stage (
 
1670
          GTK_CLUTTER_EMBED (priv->video_container))),
 
1671
      &bg);
 
1672
 
 
1673
  clutter_container_add (
 
1674
      CLUTTER_CONTAINER (gtk_clutter_embed_get_stage (
 
1675
          GTK_CLUTTER_EMBED (priv->video_container))),
 
1676
      priv->video_box,
 
1677
      NULL);
 
1678
 
 
1679
  constraint = clutter_bind_constraint_new (
 
1680
      gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)),
 
1681
      CLUTTER_BIND_SIZE, 0);
 
1682
  clutter_actor_add_constraint (priv->video_box, constraint);
 
1683
 
 
1684
  priv->remote_user_avatar_widget = gtk_image_new ();
 
1685
  remote_avatar = gtk_clutter_actor_new_with_contents (
 
1686
      priv->remote_user_avatar_widget);
 
1687
 
 
1688
  clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
 
1689
      remote_avatar);
 
1690
 
 
1691
  /* create the overlay box */
 
1692
  priv->overlay_layout = clutter_box_layout_new ();
 
1693
  clutter_box_layout_set_vertical (
 
1694
      CLUTTER_BOX_LAYOUT (priv->overlay_layout), TRUE);
 
1695
  priv->overlay_box = clutter_box_new (priv->overlay_layout);
 
1696
 
 
1697
  clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout),
 
1698
      priv->overlay_box,
 
1699
      CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL);
 
1700
 
 
1701
  empathy_call_window_create_preview_rectangles (self);
 
1702
 
 
1703
  gtk_box_pack_start (GTK_BOX (priv->content_hbox),
 
1704
      priv->video_container, TRUE, TRUE,
 
1705
      CONTENT_HBOX_CHILDREN_PACKING_PADDING);
 
1706
 
 
1707
  create_pipeline (self);
 
1708
  create_video_output_widget (self);
 
1709
  create_audio_input (self);
 
1710
  create_video_input (self);
 
1711
 
 
1712
  priv->floating_toolbar = gtk_clutter_actor_new ();
 
1713
  make_background_transparent (GTK_CLUTTER_ACTOR (priv->floating_toolbar));
 
1714
 
 
1715
  gtk_widget_reparent (priv->bottom_toolbar,
 
1716
      gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->floating_toolbar)));
 
1717
 
 
1718
  clutter_box_layout_pack (CLUTTER_BOX_LAYOUT (priv->overlay_layout),
 
1719
      priv->floating_toolbar, FALSE, FALSE, FALSE,
 
1720
      CLUTTER_BOX_ALIGNMENT_CENTER, CLUTTER_BOX_ALIGNMENT_END);
 
1721
 
 
1722
  clutter_actor_set_opacity (priv->floating_toolbar, FLOATING_TOOLBAR_OPACITY);
 
1723
 
 
1724
  clutter_actor_raise_top (priv->floating_toolbar);
 
1725
 
 
1726
  /* Transitions for the floating toolbar */
 
1727
  priv->transitions = clutter_state_new ();
 
1728
 
 
1729
  /* all transitions last for 2s */
 
1730
  clutter_state_set_duration (priv->transitions, NULL, NULL, 2000);
 
1731
 
 
1732
  /* transition from any state to "fade-out" state */
 
1733
  clutter_state_set (priv->transitions, NULL, "fade-out",
 
1734
      priv->floating_toolbar,
 
1735
      "opacity", CLUTTER_EASE_OUT_QUAD, 0,
 
1736
      NULL);
 
1737
 
 
1738
  /* transition from any state to "fade-in" state */
 
1739
  clutter_state_set (priv->transitions, NULL, "fade-in",
 
1740
      priv->floating_toolbar,
 
1741
      "opacity", CLUTTER_EASE_OUT_QUAD, FLOATING_TOOLBAR_OPACITY,
 
1742
      NULL);
 
1743
 
 
1744
  /* put the actor into the "fade-in" state with no animation */
 
1745
  clutter_state_warp_to_state (priv->transitions, "fade-in");
 
1746
 
 
1747
  /* The call will be started as soon the pipeline is playing */
 
1748
  priv->start_call_when_playing = TRUE;
 
1749
 
 
1750
  priv->dtmf_panel = empathy_dialpad_widget_new ();
 
1751
  g_signal_connect (priv->dtmf_panel, "start-tone",
 
1752
      G_CALLBACK (dtmf_start_tone_cb), self);
 
1753
 
 
1754
  gtk_box_pack_start (GTK_BOX (priv->pane), priv->dtmf_panel,
 
1755
      FALSE, FALSE, 6);
 
1756
 
 
1757
  gtk_box_pack_start (GTK_BOX (priv->pane), priv->details_vbox,
 
1758
      FALSE, FALSE, 0);
 
1759
 
 
1760
  gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
 
1761
 
 
1762
  gtk_widget_show_all (top_vbox);
 
1763
 
 
1764
  gtk_widget_hide (priv->dtmf_panel);
 
1765
  gtk_widget_hide (priv->details_vbox);
 
1766
 
 
1767
  priv->fullscreen = empathy_call_window_fullscreen_new (self);
 
1768
 
 
1769
  empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
 
1770
      priv->video_container);
 
1771
 
 
1772
  /* We hide the bottom toolbar after 3s of inactivity and show it
 
1773
   * again on mouse movement */
 
1774
  priv->inactivity_src = g_timeout_add_seconds (3,
 
1775
      empathy_call_window_toolbar_timeout, self);
 
1776
 
 
1777
  g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
 
1778
      "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
 
1779
 
 
1780
  g_signal_connect (G_OBJECT (self), "realize",
 
1781
    G_CALLBACK (empathy_call_window_realized_cb), self);
 
1782
 
 
1783
  g_signal_connect (G_OBJECT (self), "delete-event",
 
1784
    G_CALLBACK (empathy_call_window_delete_cb), self);
 
1785
 
 
1786
  g_signal_connect (G_OBJECT (self), "window-state-event",
 
1787
    G_CALLBACK (empathy_call_window_state_event_cb), self);
 
1788
 
 
1789
  g_signal_connect (G_OBJECT (self), "key-press-event",
 
1790
      G_CALLBACK (empathy_call_window_key_press_cb), self);
 
1791
 
 
1792
  g_signal_connect (self, "motion-notify-event",
 
1793
      G_CALLBACK (empathy_call_window_motion_notify_cb), self);
 
1794
 
 
1795
  priv->timer = g_timer_new ();
 
1796
 
 
1797
  g_object_ref (priv->ui_manager);
 
1798
  g_object_unref (gui);
 
1799
 
 
1800
  priv->sound_mgr = empathy_sound_manager_dup_singleton ();
 
1801
  priv->mic_menu = empathy_mic_menu_new (self);
 
1802
  priv->camera_menu = empathy_camera_menu_new (self);
 
1803
 
 
1804
  empathy_call_window_show_hangup_button (self, TRUE);
 
1805
 
 
1806
  empathy_geometry_bind (GTK_WINDOW (self), "call-window");
 
1807
  /* These signals are used to track the window position and save it
 
1808
   * when the window is destroyed. We need to do this as we don't want
 
1809
   * the window geometry to be saved with the dialpad taken into account. */
 
1810
  g_signal_connect (self, "destroy",
 
1811
      G_CALLBACK (empathy_call_window_destroyed_cb), self);
 
1812
  g_signal_connect (self, "configure-event",
 
1813
      G_CALLBACK (empathy_call_window_configure_event_cb), self);
 
1814
  g_signal_connect (self, "window-state-event",
 
1815
      G_CALLBACK (empathy_call_window_configure_event_cb), self);
 
1816
 
 
1817
  /* Don't display labels in both toolbars */
 
1818
  gtk_toolbar_set_style (GTK_TOOLBAR (priv->toolbar), GTK_TOOLBAR_ICONS);
 
1819
}
 
1820
 
 
1821
/* Instead of specifying a width and a height, we specify only one size. That's
 
1822
   because we want a square avatar icon.  */
 
1823
static void
 
1824
init_contact_avatar_with_size (EmpathyContact *contact,
 
1825
    GtkWidget *image_widget,
 
1826
    gint size)
 
1827
{
 
1828
  GdkPixbuf *pixbuf_avatar = NULL;
 
1829
 
 
1830
  if (contact != NULL)
 
1831
    {
 
1832
      pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
 
1833
        size, size);
 
1834
    }
 
1835
 
 
1836
  if (pixbuf_avatar == NULL)
 
1837
    {
 
1838
      pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
 
1839
          EMPATHY_IMAGE_AVATAR_DEFAULT, size);
 
1840
    }
 
1841
 
 
1842
  gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
 
1843
 
 
1844
  if (pixbuf_avatar != NULL)
 
1845
    g_object_unref (pixbuf_avatar);
 
1846
}
 
1847
 
 
1848
static void
 
1849
set_window_title (EmpathyCallWindow *self)
 
1850
{
 
1851
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
1852
  gchar *tmp;
 
1853
 
 
1854
  if (priv->contact != NULL)
 
1855
    {
 
1856
      /* translators: Call is a noun and %s is the contact name. This string
 
1857
       * is used in the window title */
 
1858
      tmp = g_strdup_printf (_("Call with %s"),
 
1859
          empathy_contact_get_alias (priv->contact));
 
1860
      gtk_window_set_title (GTK_WINDOW (self), tmp);
 
1861
      g_free (tmp);
 
1862
    }
 
1863
  else
 
1864
    {
 
1865
      g_warning ("Unknown remote contact!");
 
1866
    }
 
1867
}
 
1868
 
 
1869
static void
 
1870
set_remote_user_name (EmpathyCallWindow *self,
 
1871
  EmpathyContact *contact)
 
1872
{
 
1873
  const gchar *alias = empathy_contact_get_alias (contact);
 
1874
  const gchar *status = empathy_contact_get_status (contact);
 
1875
  gchar *label;
 
1876
 
 
1877
  label = g_strdup_printf ("%s\n<small>%s</small>", alias, status);
 
1878
  gtk_label_set_markup (GTK_LABEL (self->priv->remote_user_name_toolbar),
 
1879
      label);
 
1880
  g_free (label);
 
1881
}
 
1882
 
 
1883
static void
 
1884
contact_name_changed_cb (EmpathyContact *contact,
 
1885
    GParamSpec *pspec,
 
1886
    EmpathyCallWindow *self)
 
1887
{
 
1888
  set_window_title (self);
 
1889
  set_remote_user_name (self, contact);
 
1890
}
 
1891
 
 
1892
static void
 
1893
contact_presence_changed_cb (EmpathyContact *contact,
 
1894
    GParamSpec *pspec,
 
1895
    EmpathyCallWindow *self)
 
1896
{
 
1897
  set_remote_user_name (self, contact);
 
1898
}
 
1899
 
 
1900
static void
 
1901
contact_avatar_changed_cb (EmpathyContact *contact,
 
1902
    GParamSpec *pspec,
 
1903
    EmpathyCallWindow *self)
 
1904
{
 
1905
  int size;
 
1906
  GtkAllocation allocation;
 
1907
  GtkWidget *avatar_widget;
 
1908
 
 
1909
  avatar_widget = self->priv->remote_user_avatar_widget;
 
1910
 
 
1911
  gtk_widget_get_allocation (avatar_widget, &allocation);
 
1912
  size = allocation.height;
 
1913
 
 
1914
  if (size == 0)
 
1915
    {
 
1916
      /* the widget is not allocated yet, set a default size */
 
1917
      size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
 
1918
          REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
 
1919
    }
 
1920
 
 
1921
  init_contact_avatar_with_size (contact, avatar_widget, size);
 
1922
 
 
1923
  avatar_widget = self->priv->remote_user_avatar_toolbar;
 
1924
 
 
1925
  gtk_widget_get_allocation (avatar_widget, &allocation);
 
1926
  size = allocation.height;
 
1927
 
 
1928
  if (size == 0)
 
1929
    {
 
1930
      /* the widget is not allocated yet, set a default size */
 
1931
      size = SMALL_TOOLBAR_SIZE;
 
1932
    }
 
1933
 
 
1934
  init_contact_avatar_with_size (contact, avatar_widget, size);
 
1935
}
 
1936
 
 
1937
static void
 
1938
empathy_call_window_setup_avatars (EmpathyCallWindow *self,
 
1939
    EmpathyCallHandler *handler)
 
1940
{
 
1941
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
1942
 
 
1943
  tp_g_signal_connect_object (priv->contact, "notify::name",
 
1944
      G_CALLBACK (contact_name_changed_cb), self, 0);
 
1945
  tp_g_signal_connect_object (priv->contact, "notify::avatar",
 
1946
    G_CALLBACK (contact_avatar_changed_cb), self, 0);
 
1947
  tp_g_signal_connect_object (priv->contact, "notify::presence",
 
1948
      G_CALLBACK (contact_presence_changed_cb), self, 0);
 
1949
 
 
1950
  set_window_title (self);
 
1951
  set_remote_user_name (self, priv->contact);
 
1952
 
 
1953
  init_contact_avatar_with_size (priv->contact,
 
1954
      priv->remote_user_avatar_widget,
 
1955
      MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
 
1956
          REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
 
1957
 
 
1958
  init_contact_avatar_with_size (priv->contact,
 
1959
      priv->remote_user_avatar_toolbar,
 
1960
      SMALL_TOOLBAR_SIZE);
 
1961
 
 
1962
  /* The remote avatar is shown by default and will be hidden when we receive
 
1963
     video from the remote side. */
 
1964
  clutter_actor_hide (priv->video_output);
 
1965
  gtk_widget_show (priv->remote_user_avatar_widget);
 
1966
}
 
1967
 
 
1968
static void
 
1969
update_send_codec (EmpathyCallWindow *self,
 
1970
    gboolean audio)
 
1971
{
 
1972
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
1973
  FsCodec *codec;
 
1974
  GtkWidget *widget;
 
1975
  gchar *tmp;
 
1976
 
 
1977
  if (audio)
 
1978
    {
 
1979
      codec = empathy_call_handler_get_send_audio_codec (priv->handler);
 
1980
      widget = priv->acodec_encoding_label;
 
1981
    }
 
1982
  else
 
1983
    {
 
1984
      codec = empathy_call_handler_get_send_video_codec (priv->handler);
 
1985
      widget = priv->vcodec_encoding_label;
 
1986
    }
 
1987
 
 
1988
  if (codec == NULL)
 
1989
    return;
 
1990
 
 
1991
  tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
 
1992
  gtk_label_set_text (GTK_LABEL (widget), tmp);
 
1993
  g_free (tmp);
 
1994
}
 
1995
 
 
1996
static void
 
1997
send_audio_codec_notify_cb (GObject *object,
 
1998
    GParamSpec *pspec,
 
1999
    gpointer user_data)
 
2000
{
 
2001
  EmpathyCallWindow *self = user_data;
 
2002
 
 
2003
  update_send_codec (self, TRUE);
 
2004
}
 
2005
 
 
2006
static void
 
2007
send_video_codec_notify_cb (GObject *object,
 
2008
    GParamSpec *pspec,
 
2009
    gpointer user_data)
 
2010
{
 
2011
  EmpathyCallWindow *self = user_data;
 
2012
 
 
2013
  update_send_codec (self, FALSE);
 
2014
}
 
2015
 
 
2016
static void
 
2017
update_recv_codec (EmpathyCallWindow *self,
 
2018
    gboolean audio)
 
2019
{
 
2020
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2021
  GList *codecs, *l;
 
2022
  GtkWidget *widget;
 
2023
  GString *str = NULL;
 
2024
 
 
2025
  if (audio)
 
2026
    {
 
2027
      codecs = empathy_call_handler_get_recv_audio_codecs (priv->handler);
 
2028
      widget = priv->acodec_decoding_label;
 
2029
    }
 
2030
  else
 
2031
    {
 
2032
      codecs = empathy_call_handler_get_recv_video_codecs (priv->handler);
 
2033
      widget = priv->vcodec_decoding_label;
 
2034
    }
 
2035
 
 
2036
  if (codecs == NULL)
 
2037
    return;
 
2038
 
 
2039
  for (l = codecs; l != NULL; l = g_list_next (l))
 
2040
    {
 
2041
      FsCodec *codec = l->data;
 
2042
 
 
2043
      if (str == NULL)
 
2044
        str = g_string_new (NULL);
 
2045
      else
 
2046
        g_string_append (str, ", ");
 
2047
 
 
2048
      g_string_append_printf (str, "%s/%u", codec->encoding_name,
 
2049
          codec->clock_rate);
 
2050
    }
 
2051
 
 
2052
  gtk_label_set_text (GTK_LABEL (widget), str->str);
 
2053
  g_string_free (str, TRUE);
 
2054
}
 
2055
 
 
2056
static void
 
2057
recv_audio_codecs_notify_cb (GObject *object,
 
2058
    GParamSpec *pspec,
 
2059
    gpointer user_data)
 
2060
{
 
2061
  EmpathyCallWindow *self = user_data;
 
2062
 
 
2063
  update_recv_codec (self, TRUE);
 
2064
}
 
2065
 
 
2066
static void
 
2067
recv_video_codecs_notify_cb (GObject *object,
 
2068
    GParamSpec *pspec,
 
2069
    gpointer user_data)
 
2070
{
 
2071
  EmpathyCallWindow *self = user_data;
 
2072
 
 
2073
  update_recv_codec (self, FALSE);
 
2074
}
 
2075
 
 
2076
static const gchar *
 
2077
candidate_type_to_str (FsCandidate *candidate)
 
2078
{
 
2079
  switch (candidate->type)
 
2080
    {
 
2081
      case FS_CANDIDATE_TYPE_HOST:
 
2082
        return "host";
 
2083
      case FS_CANDIDATE_TYPE_SRFLX:
 
2084
        return "server reflexive";
 
2085
      case FS_CANDIDATE_TYPE_PRFLX:
 
2086
        return "peer reflexive";
 
2087
      case FS_CANDIDATE_TYPE_RELAY:
 
2088
        return "relay";
 
2089
      case FS_CANDIDATE_TYPE_MULTICAST:
 
2090
        return "multicast";
 
2091
    }
 
2092
 
 
2093
  return NULL;
 
2094
}
 
2095
 
 
2096
static const gchar *
 
2097
candidate_type_to_desc (FsCandidate *candidate)
 
2098
{
 
2099
  switch (candidate->type)
 
2100
    {
 
2101
      case FS_CANDIDATE_TYPE_HOST:
 
2102
        return _("The IP address as seen by the machine");
 
2103
      case FS_CANDIDATE_TYPE_SRFLX:
 
2104
        return _("The IP address as seen by a server on the Internet");
 
2105
      case FS_CANDIDATE_TYPE_PRFLX:
 
2106
        return _("The IP address of the peer as seen by the other side");
 
2107
      case FS_CANDIDATE_TYPE_RELAY:
 
2108
        return _("The IP address of a relay server");
 
2109
      case FS_CANDIDATE_TYPE_MULTICAST:
 
2110
        return _("The IP address of the multicast group");
 
2111
    }
 
2112
 
 
2113
  return NULL;
 
2114
}
 
2115
 
 
2116
static void
 
2117
update_candidat_widget (EmpathyCallWindow *self,
 
2118
    GtkWidget *label,
 
2119
    GtkWidget *img,
 
2120
    FsCandidate *candidate)
 
2121
{
 
2122
  gchar *str;
 
2123
 
 
2124
  g_assert (candidate != NULL);
 
2125
  str = g_strdup_printf ("%s %u (%s)", candidate->ip,
 
2126
      candidate->port, candidate_type_to_str (candidate));
 
2127
 
 
2128
  gtk_label_set_text (GTK_LABEL (label), str);
 
2129
  gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
 
2130
 
 
2131
  g_free (str);
 
2132
}
 
2133
 
 
2134
static void
 
2135
candidates_changed_cb (GObject *object,
 
2136
    FsMediaType type,
 
2137
    EmpathyCallWindow *self)
 
2138
{
 
2139
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2140
  FsCandidate *candidate = NULL;
 
2141
 
 
2142
  if (type == FS_MEDIA_TYPE_VIDEO)
 
2143
    {
 
2144
      /* Update remote candidate */
 
2145
      candidate = empathy_call_handler_get_video_remote_candidate (
 
2146
          priv->handler);
 
2147
 
 
2148
      update_candidat_widget (self, priv->video_remote_candidate_label,
 
2149
          priv->video_remote_candidate_info_img, candidate);
 
2150
 
 
2151
      /* Update local candidate */
 
2152
      candidate = empathy_call_handler_get_video_local_candidate (
 
2153
          priv->handler);
 
2154
 
 
2155
      update_candidat_widget (self, priv->video_local_candidate_label,
 
2156
          priv->video_local_candidate_info_img, candidate);
 
2157
    }
 
2158
  else
 
2159
    {
 
2160
      /* Update remote candidate */
 
2161
      candidate = empathy_call_handler_get_audio_remote_candidate (
 
2162
          priv->handler);
 
2163
 
 
2164
      update_candidat_widget (self, priv->audio_remote_candidate_label,
 
2165
          priv->audio_remote_candidate_info_img, candidate);
 
2166
 
 
2167
      /* Update local candidate */
 
2168
      candidate = empathy_call_handler_get_audio_local_candidate (
 
2169
          priv->handler);
 
2170
 
 
2171
      update_candidat_widget (self, priv->audio_local_candidate_label,
 
2172
          priv->audio_local_candidate_info_img, candidate);
 
2173
    }
 
2174
}
 
2175
 
 
2176
static void
 
2177
empathy_call_window_constructed (GObject *object)
 
2178
{
 
2179
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
 
2180
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2181
  TpCallChannel *call;
 
2182
  TpCallState state;
 
2183
 
 
2184
  g_assert (priv->handler != NULL);
 
2185
 
 
2186
  g_object_get (priv->handler, "call-channel", &call, NULL);
 
2187
  state = tp_call_channel_get_state (call, NULL, NULL, NULL);
 
2188
  priv->outgoing = (state == TP_CALL_STATE_PENDING_INITIATOR);
 
2189
  tp_clear_object (&call);
 
2190
 
 
2191
  priv->contact = empathy_call_handler_get_contact (priv->handler);
 
2192
  g_assert (priv->contact != NULL);
 
2193
  g_object_ref (priv->contact);
 
2194
 
 
2195
  if (!empathy_contact_can_voip_video (priv->contact))
 
2196
    {
 
2197
      gtk_widget_set_sensitive (priv->video_call_button, FALSE);
 
2198
      gtk_widget_set_sensitive (priv->camera_button, FALSE);
 
2199
    }
 
2200
 
 
2201
  empathy_call_window_setup_avatars (self, priv->handler);
 
2202
  empathy_call_window_set_state_connecting (self);
 
2203
 
 
2204
  if (!empathy_call_handler_has_initial_video (priv->handler))
 
2205
    {
 
2206
      gtk_toggle_tool_button_set_active (
 
2207
          GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
 
2208
    }
 
2209
  /* If call has InitialVideo, the preview will be started once the call has
 
2210
   * been started (start_call()). */
 
2211
 
 
2212
  update_send_codec (self, TRUE);
 
2213
  update_send_codec (self, FALSE);
 
2214
  update_recv_codec (self, TRUE);
 
2215
  update_recv_codec (self, FALSE);
 
2216
 
 
2217
  tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
 
2218
      G_CALLBACK (send_audio_codec_notify_cb), self, 0);
 
2219
  tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
 
2220
      G_CALLBACK (send_video_codec_notify_cb), self, 0);
 
2221
  tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
 
2222
      G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
 
2223
  tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
 
2224
      G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
 
2225
 
 
2226
  tp_g_signal_connect_object (priv->handler, "candidates-changed",
 
2227
      G_CALLBACK (candidates_changed_cb), self, 0);
 
2228
}
 
2229
 
 
2230
static void empathy_call_window_dispose (GObject *object);
 
2231
static void empathy_call_window_finalize (GObject *object);
 
2232
 
 
2233
static void
 
2234
empathy_call_window_set_property (GObject *object,
 
2235
  guint property_id, const GValue *value, GParamSpec *pspec)
 
2236
{
 
2237
  EmpathyCallWindowPriv *priv = GET_PRIV (object);
 
2238
 
 
2239
  switch (property_id)
 
2240
    {
 
2241
      case PROP_CALL_HANDLER:
 
2242
        priv->handler = g_value_dup_object (value);
 
2243
        break;
 
2244
      default:
 
2245
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 
2246
    }
 
2247
}
 
2248
 
 
2249
static void
 
2250
empathy_call_window_get_property (GObject *object,
 
2251
  guint property_id, GValue *value, GParamSpec *pspec)
 
2252
{
 
2253
  EmpathyCallWindowPriv *priv = GET_PRIV (object);
 
2254
 
 
2255
  switch (property_id)
 
2256
    {
 
2257
      case PROP_CALL_HANDLER:
 
2258
        g_value_set_object (value, priv->handler);
 
2259
        break;
 
2260
      default:
 
2261
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 
2262
    }
 
2263
}
 
2264
 
 
2265
static void
 
2266
empathy_call_window_class_init (
 
2267
  EmpathyCallWindowClass *empathy_call_window_class)
 
2268
{
 
2269
  GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
 
2270
  GParamSpec *param_spec;
 
2271
 
 
2272
  g_type_class_add_private (empathy_call_window_class,
 
2273
    sizeof (EmpathyCallWindowPriv));
 
2274
 
 
2275
  object_class->constructed = empathy_call_window_constructed;
 
2276
  object_class->set_property = empathy_call_window_set_property;
 
2277
  object_class->get_property = empathy_call_window_get_property;
 
2278
 
 
2279
  object_class->dispose = empathy_call_window_dispose;
 
2280
  object_class->finalize = empathy_call_window_finalize;
 
2281
 
 
2282
  param_spec = g_param_spec_object ("handler",
 
2283
    "handler", "The call handler",
 
2284
    EMPATHY_TYPE_CALL_HANDLER,
 
2285
    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
2286
  g_object_class_install_property (object_class,
 
2287
    PROP_CALL_HANDLER, param_spec);
 
2288
}
 
2289
 
 
2290
void
 
2291
empathy_call_window_dispose (GObject *object)
 
2292
{
 
2293
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
 
2294
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2295
 
 
2296
  if (priv->dispose_has_run)
 
2297
    return;
 
2298
 
 
2299
  priv->dispose_has_run = TRUE;
 
2300
 
 
2301
  if (priv->handler != NULL)
 
2302
    {
 
2303
      empathy_call_handler_stop_call (priv->handler);
 
2304
      tp_clear_object (&priv->handler);
 
2305
    }
 
2306
 
 
2307
  if (priv->bus_message_source_id != 0)
 
2308
    {
 
2309
      g_source_remove (priv->bus_message_source_id);
 
2310
      priv->bus_message_source_id = 0;
 
2311
    }
 
2312
 
 
2313
  if (priv->got_video_src > 0)
 
2314
    {
 
2315
      g_source_remove (priv->got_video_src);
 
2316
      priv->got_video_src = 0;
 
2317
    }
 
2318
 
 
2319
  if (priv->inactivity_src > 0)
 
2320
    {
 
2321
      g_source_remove (priv->inactivity_src);
 
2322
      priv->inactivity_src = 0;
 
2323
    }
 
2324
 
 
2325
  tp_clear_object (&priv->pipeline);
 
2326
  tp_clear_object (&priv->video_input);
 
2327
  tp_clear_object (&priv->audio_input);
 
2328
  tp_clear_object (&priv->video_tee);
 
2329
  tp_clear_object (&priv->ui_manager);
 
2330
  tp_clear_object (&priv->fullscreen);
 
2331
  tp_clear_object (&priv->camera_monitor);
 
2332
  tp_clear_object (&priv->settings);
 
2333
  tp_clear_object (&priv->sound_mgr);
 
2334
  tp_clear_object (&priv->mic_menu);
 
2335
  tp_clear_object (&priv->camera_menu);
 
2336
  tp_clear_object (&priv->transitions);
 
2337
 
 
2338
  g_list_free_full (priv->notifiers, g_object_unref);
 
2339
 
 
2340
  if (priv->timer_id != 0)
 
2341
    g_source_remove (priv->timer_id);
 
2342
  priv->timer_id = 0;
 
2343
 
 
2344
  tp_clear_object (&priv->contact);
 
2345
 
 
2346
  G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
 
2347
}
 
2348
 
 
2349
static void
 
2350
disconnect_video_output_motion_handler (EmpathyCallWindow *self)
 
2351
{
 
2352
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2353
 
 
2354
  if (priv->video_output_motion_handler_id != 0)
 
2355
    {
 
2356
      g_signal_handler_disconnect (G_OBJECT (priv->video_container),
 
2357
          priv->video_output_motion_handler_id);
 
2358
      priv->video_output_motion_handler_id = 0;
 
2359
    }
 
2360
}
 
2361
 
 
2362
void
 
2363
empathy_call_window_finalize (GObject *object)
 
2364
{
 
2365
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
 
2366
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2367
 
 
2368
  disconnect_video_output_motion_handler (self);
 
2369
 
 
2370
  /* free any data held directly by the object here */
 
2371
  g_mutex_free (priv->lock);
 
2372
 
 
2373
  g_timer_destroy (priv->timer);
 
2374
 
 
2375
  G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
 
2376
}
 
2377
 
 
2378
 
 
2379
EmpathyCallWindow *
 
2380
empathy_call_window_new (EmpathyCallHandler *handler)
 
2381
{
 
2382
  return EMPATHY_CALL_WINDOW (
 
2383
    g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
 
2384
}
 
2385
 
 
2386
void
 
2387
empathy_call_window_present (EmpathyCallWindow *self,
 
2388
    EmpathyCallHandler *handler)
 
2389
{
 
2390
  g_return_if_fail (EMPATHY_IS_CALL_HANDLER (handler));
 
2391
 
 
2392
  empathy_window_present (GTK_WINDOW (self));
 
2393
 
 
2394
  if (self->priv->call_state == DISCONNECTED)
 
2395
    {
 
2396
      /* start a new call if one is not already in progress */
 
2397
      tp_clear_object (&self->priv->handler);
 
2398
      self->priv->handler = g_object_ref (handler);
 
2399
      empathy_call_window_connect_handler (self);
 
2400
 
 
2401
      empathy_call_window_restart_call (self);
 
2402
    }
 
2403
}
 
2404
 
 
2405
static void
 
2406
empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
 
2407
  GstElement *conference, gpointer user_data)
 
2408
{
 
2409
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
 
2410
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2411
  FsElementAddedNotifier *notifier;
 
2412
  GKeyFile *keyfile;
 
2413
 
 
2414
  DEBUG ("Conference added");
 
2415
 
 
2416
  /* Add notifier to set the various element properties as needed */
 
2417
  notifier = fs_element_added_notifier_new ();
 
2418
  keyfile = fs_utils_get_default_element_properties (conference);
 
2419
 
 
2420
  if (keyfile != NULL)
 
2421
    fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
 
2422
 
 
2423
  fs_element_added_notifier_add (notifier, GST_BIN (priv->pipeline));
 
2424
 
 
2425
  priv->notifiers = g_list_prepend (priv->notifiers, notifier);
 
2426
 
 
2427
  gst_bin_add (GST_BIN (priv->pipeline), conference);
 
2428
  gst_element_set_state (conference, GST_STATE_PLAYING);
 
2429
}
 
2430
 
 
2431
static void
 
2432
empathy_call_window_conference_removed_cb (EmpathyCallHandler *handler,
 
2433
  GstElement *conference, gpointer user_data)
 
2434
{
 
2435
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
 
2436
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2437
 
 
2438
  gst_bin_remove (GST_BIN (priv->pipeline), conference);
 
2439
  gst_element_set_state (conference, GST_STATE_NULL);
 
2440
}
 
2441
 
 
2442
static gboolean
 
2443
empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
 
2444
{
 
2445
  GstStateChangeReturn state_change_return;
 
2446
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2447
 
 
2448
  if (priv->pipeline == NULL)
 
2449
    return TRUE;
 
2450
 
 
2451
  if (priv->bus_message_source_id != 0)
 
2452
    {
 
2453
      g_source_remove (priv->bus_message_source_id);
 
2454
      priv->bus_message_source_id = 0;
 
2455
    }
 
2456
 
 
2457
  state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
 
2458
 
 
2459
  if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
 
2460
        state_change_return == GST_STATE_CHANGE_NO_PREROLL)
 
2461
    {
 
2462
      if (priv->pipeline != NULL)
 
2463
        g_object_unref (priv->pipeline);
 
2464
      priv->pipeline = NULL;
 
2465
 
 
2466
      if (priv->audio_output != NULL)
 
2467
        g_object_unref (priv->audio_output);
 
2468
      priv->audio_output = NULL;
 
2469
      priv->audio_output_added = FALSE;
 
2470
 
 
2471
      if (priv->video_tee != NULL)
 
2472
        g_object_unref (priv->video_tee);
 
2473
      priv->video_tee = NULL;
 
2474
 
 
2475
      if (priv->video_preview != NULL)
 
2476
        clutter_actor_destroy (priv->video_preview);
 
2477
      priv->video_preview = NULL;
 
2478
 
 
2479
      /* If we destroy the preview while it's being dragged, we won't
 
2480
       * get the ::drag-end signal, so manually destroy the clone */
 
2481
      if (priv->drag_preview != NULL)
 
2482
        {
 
2483
          clutter_actor_destroy (priv->drag_preview);
 
2484
          empathy_call_window_show_preview_rectangles (self, FALSE);
 
2485
          priv->drag_preview = NULL;
 
2486
        }
 
2487
 
 
2488
      priv->funnel = NULL;
 
2489
 
 
2490
      create_pipeline (self);
 
2491
      /* Call will be started when user will hit the 'redial' button */
 
2492
      priv->start_call_when_playing = FALSE;
 
2493
      gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
 
2494
 
 
2495
      return TRUE;
 
2496
    }
 
2497
  else
 
2498
    {
 
2499
      g_message ("Error: could not destroy pipeline. Closing call window");
 
2500
      gtk_widget_destroy (GTK_WIDGET (self));
 
2501
 
 
2502
      return FALSE;
 
2503
    }
 
2504
}
 
2505
 
 
2506
static void
 
2507
reset_details_pane (EmpathyCallWindow *self)
 
2508
{
 
2509
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2510
 
 
2511
  gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
 
2512
  gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
 
2513
  gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
 
2514
  gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
 
2515
}
 
2516
 
 
2517
static gboolean
 
2518
empathy_call_window_disconnected (EmpathyCallWindow *self,
 
2519
    gboolean restart)
 
2520
{
 
2521
  gboolean could_disconnect = FALSE;
 
2522
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2523
  gboolean could_reset_pipeline;
 
2524
 
 
2525
  /* Leave full screen mode if needed */
 
2526
  gtk_window_unfullscreen (GTK_WINDOW (self));
 
2527
 
 
2528
  gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
 
2529
  gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
 
2530
 
 
2531
  could_reset_pipeline = empathy_call_window_reset_pipeline (self);
 
2532
 
 
2533
  if (priv->call_state == CONNECTING)
 
2534
      empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
 
2535
 
 
2536
  if (priv->call_state != REDIALING)
 
2537
    priv->call_state = DISCONNECTED;
 
2538
 
 
2539
  /* Show the toolbar */
 
2540
  clutter_state_set_state (priv->transitions, "fade-in");
 
2541
 
 
2542
  if (could_reset_pipeline)
 
2543
    {
 
2544
      g_mutex_lock (priv->lock);
 
2545
 
 
2546
      g_timer_stop (priv->timer);
 
2547
 
 
2548
      if (priv->timer_id != 0)
 
2549
        g_source_remove (priv->timer_id);
 
2550
      priv->timer_id = 0;
 
2551
 
 
2552
      g_mutex_unlock (priv->lock);
 
2553
 
 
2554
      if (!restart)
 
2555
        /* We are about to destroy the window, no need to update it or create
 
2556
         * a video preview */
 
2557
        return TRUE;
 
2558
 
 
2559
      empathy_call_window_status_message (self, _("Disconnected"));
 
2560
 
 
2561
      empathy_call_window_show_hangup_button (self, FALSE);
 
2562
 
 
2563
      /* Unsensitive the camera and mic button */
 
2564
      gtk_widget_set_sensitive (priv->camera_button, FALSE);
 
2565
      gtk_widget_set_sensitive (priv->mic_button, FALSE);
 
2566
 
 
2567
      /* Be sure that the mic button is enabled */
 
2568
      gtk_toggle_tool_button_set_active (
 
2569
          GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
 
2570
 
 
2571
      if (priv->camera_state == CAMERA_STATE_ON)
 
2572
        {
 
2573
          /* Restart the preview with the new pipeline. */
 
2574
          display_video_preview (self, TRUE);
 
2575
        }
 
2576
 
 
2577
      /* destroy the video output; it will be recreated when we'll redial */
 
2578
      disconnect_video_output_motion_handler (self);
 
2579
      if (priv->video_output != NULL)
 
2580
        clutter_actor_destroy (priv->video_output);
 
2581
      priv->video_output = NULL;
 
2582
      if (priv->got_video_src > 0)
 
2583
        {
 
2584
          g_source_remove (priv->got_video_src);
 
2585
          priv->got_video_src = 0;
 
2586
        }
 
2587
 
 
2588
      gtk_widget_show (priv->remote_user_avatar_widget);
 
2589
 
 
2590
      reset_details_pane (self);
 
2591
 
 
2592
      priv->sending_video = FALSE;
 
2593
      priv->call_started = FALSE;
 
2594
 
 
2595
      could_disconnect = TRUE;
 
2596
 
 
2597
      /* TODO: display the self avatar of the preview (depends if the "Always
 
2598
       * Show Video Preview" is enabled or not) */
 
2599
    }
 
2600
 
 
2601
  return could_disconnect;
 
2602
}
 
2603
 
 
2604
 
 
2605
static void
 
2606
empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
 
2607
    gpointer user_data)
 
2608
{
 
2609
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
 
2610
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2611
 
 
2612
  if (empathy_call_window_disconnected (self, TRUE) &&
 
2613
      priv->call_state == REDIALING)
 
2614
      empathy_call_window_restart_call (self);
 
2615
}
 
2616
 
 
2617
static gboolean
 
2618
empathy_call_window_content_is_raw (TfContent *content)
 
2619
{
 
2620
  FsConference *conference;
 
2621
  gboolean israw;
 
2622
 
 
2623
  g_object_get (content, "fs-conference", &conference, NULL);
 
2624
  g_assert (conference != NULL);
 
2625
 
 
2626
  /* FIXME: Ugly hack, update when moving a packetization property into
 
2627
   * farstream */
 
2628
  israw = g_str_has_prefix (GST_OBJECT_NAME (conference), "fsrawconf");
 
2629
  gst_object_unref (conference);
 
2630
 
 
2631
  return israw;
 
2632
}
 
2633
 
 
2634
static gboolean
 
2635
empathy_call_window_content_removed_cb (EmpathyCallHandler *handler,
 
2636
    TfContent *content,
 
2637
    EmpathyCallWindow *self)
 
2638
{
 
2639
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2640
  FsMediaType media_type;
 
2641
 
 
2642
  DEBUG ("removing content");
 
2643
 
 
2644
  g_object_get (content, "media-type", &media_type, NULL);
 
2645
 
 
2646
  /*
 
2647
   * This assumes that there is only one video stream per channel...
 
2648
   */
 
2649
 
 
2650
  if ((guint) media_type == FS_MEDIA_TYPE_VIDEO)
 
2651
    {
 
2652
      if (priv->funnel != NULL)
 
2653
        {
 
2654
          GstElement *output;
 
2655
 
 
2656
          output = priv->video_output_sink;
 
2657
 
 
2658
          gst_element_set_state (output, GST_STATE_NULL);
 
2659
          gst_element_set_state (priv->funnel, GST_STATE_NULL);
 
2660
 
 
2661
          gst_bin_remove (GST_BIN (priv->pipeline), output);
 
2662
          gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
 
2663
          priv->funnel = NULL;
 
2664
        }
 
2665
    }
 
2666
  else if (media_type == FS_MEDIA_TYPE_AUDIO)
 
2667
    {
 
2668
      if (priv->audio_output != NULL)
 
2669
        {
 
2670
          gst_element_set_state (priv->audio_output, GST_STATE_NULL);
 
2671
 
 
2672
          if (priv->audio_output_added)
 
2673
            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
 
2674
          priv->audio_output = NULL;
 
2675
          priv->audio_output_added = FALSE;
 
2676
        }
 
2677
    }
 
2678
  else
 
2679
    {
 
2680
      g_assert_not_reached ();
 
2681
    }
 
2682
 
 
2683
  return TRUE;
 
2684
}
 
2685
 
 
2686
static void
 
2687
empathy_call_window_framerate_changed_cb (EmpathyCallHandler *handler,
 
2688
    guint framerate,
 
2689
    EmpathyCallWindow *self)
 
2690
{
 
2691
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2692
 
 
2693
  DEBUG ("Framerate changed to %u", framerate);
 
2694
 
 
2695
  if (priv->video_input != NULL)
 
2696
    empathy_video_src_set_framerate (priv->video_input, framerate);
 
2697
}
 
2698
 
 
2699
static void
 
2700
empathy_call_window_resolution_changed_cb (EmpathyCallHandler *handler,
 
2701
    guint width,
 
2702
    guint height,
 
2703
    EmpathyCallWindow *self)
 
2704
{
 
2705
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2706
 
 
2707
  DEBUG ("Resolution changed to %ux%u", width, height);
 
2708
 
 
2709
  if (priv->video_input != NULL)
 
2710
    {
 
2711
      empathy_video_src_set_resolution (priv->video_input, width, height);
 
2712
    }
 
2713
}
 
2714
 
 
2715
/* Called with global lock held */
 
2716
static GstPad *
 
2717
empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
 
2718
{
 
2719
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2720
  GstPad *pad;
 
2721
  GstElement *output;
 
2722
 
 
2723
  if (priv->funnel == NULL)
 
2724
    {
 
2725
      output = priv->video_output_sink;
 
2726
 
 
2727
      priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
 
2728
 
 
2729
      if (!priv->funnel)
 
2730
        {
 
2731
          g_warning ("Could not create fsfunnel");
 
2732
          return NULL;
 
2733
        }
 
2734
 
 
2735
      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
 
2736
        {
 
2737
          gst_object_unref (priv->funnel);
 
2738
          priv->funnel = NULL;
 
2739
          g_warning ("Could  not add funnel to pipeline");
 
2740
          return NULL;
 
2741
        }
 
2742
 
 
2743
      if (!gst_bin_add (GST_BIN (priv->pipeline), output))
 
2744
        {
 
2745
          g_warning ("Could not add the video output widget to the pipeline");
 
2746
          goto error;
 
2747
        }
 
2748
 
 
2749
      if (!gst_element_link (priv->funnel, output))
 
2750
        {
 
2751
          g_warning ("Could not link output sink to funnel");
 
2752
          goto error_output_added;
 
2753
        }
 
2754
 
 
2755
      if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
 
2756
        {
 
2757
          g_warning ("Could not start video sink");
 
2758
          goto error_output_added;
 
2759
        }
 
2760
 
 
2761
      if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
 
2762
        {
 
2763
          g_warning ("Could not start funnel");
 
2764
          goto error_output_added;
 
2765
        }
 
2766
    }
 
2767
 
 
2768
  pad = gst_element_get_request_pad (priv->funnel, "sink%d");
 
2769
 
 
2770
  if (!pad)
 
2771
    g_warning ("Could not get request pad from funnel");
 
2772
 
 
2773
  return pad;
 
2774
 
 
2775
 
 
2776
 error_output_added:
 
2777
 
 
2778
  gst_element_set_locked_state (priv->funnel, TRUE);
 
2779
  gst_element_set_locked_state (output, TRUE);
 
2780
 
 
2781
  gst_element_set_state (priv->funnel, GST_STATE_NULL);
 
2782
  gst_element_set_state (output, GST_STATE_NULL);
 
2783
 
 
2784
  gst_bin_remove (GST_BIN (priv->pipeline), output);
 
2785
  gst_element_set_locked_state (output, FALSE);
 
2786
 
 
2787
 error:
 
2788
 
 
2789
  gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
 
2790
  priv->funnel = NULL;
 
2791
 
 
2792
  return NULL;
 
2793
}
 
2794
 
 
2795
/* Called with global lock held */
 
2796
static GstPad *
 
2797
empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self,
 
2798
  TfContent *content)
 
2799
{
 
2800
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2801
  GstPad *pad;
 
2802
  GstPadTemplate *template;
 
2803
 
 
2804
  if (!priv->audio_output_added)
 
2805
    {
 
2806
      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
 
2807
        {
 
2808
          g_warning ("Could not add audio sink to pipeline");
 
2809
          g_object_unref (priv->audio_output);
 
2810
          goto error_add_output;
 
2811
        }
 
2812
 
 
2813
      if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
 
2814
        {
 
2815
          g_warning ("Could not start audio sink");
 
2816
          goto error;
 
2817
        }
 
2818
    }
 
2819
 
 
2820
  template = gst_element_class_get_pad_template (
 
2821
    GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
 
2822
 
 
2823
  pad = gst_element_request_pad (priv->audio_output,
 
2824
    template, NULL, NULL);
 
2825
 
 
2826
  if (pad == NULL)
 
2827
    {
 
2828
      g_warning ("Could not get sink pad from sink");
 
2829
      return NULL;
 
2830
    }
 
2831
 
 
2832
  return pad;
 
2833
 
 
2834
error:
 
2835
  gst_element_set_locked_state (priv->audio_output, TRUE);
 
2836
  gst_element_set_state (priv->audio_output, GST_STATE_NULL);
 
2837
  gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
 
2838
  priv->audio_output = NULL;
 
2839
 
 
2840
error_add_output:
 
2841
 
 
2842
  return NULL;
 
2843
}
 
2844
 
 
2845
static gboolean
 
2846
empathy_call_window_update_timer (gpointer user_data)
 
2847
{
 
2848
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
 
2849
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2850
  const gchar *status;
 
2851
  gchar *str;
 
2852
  gdouble time_;
 
2853
 
 
2854
  time_ = g_timer_elapsed (priv->timer, NULL);
 
2855
 
 
2856
  if (priv->call_state == HELD)
 
2857
    status = _("On hold");
 
2858
  else if (!gtk_toggle_tool_button_get_active (
 
2859
      GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
 
2860
    status = _("Mute");
 
2861
  else
 
2862
    status = _("Duration");
 
2863
 
 
2864
  /* Translators: 'status - minutes:seconds' the caller has been connected */
 
2865
  str = g_strdup_printf (_("%s — %d:%02dm"),
 
2866
      status,
 
2867
      (int) time_ / 60, (int) time_ % 60);
 
2868
  empathy_call_window_status_message (self, str);
 
2869
  g_free (str);
 
2870
 
 
2871
  return TRUE;
 
2872
}
 
2873
 
 
2874
enum
 
2875
{
 
2876
  EMP_RESPONSE_BALANCE
 
2877
};
 
2878
 
 
2879
static void
 
2880
on_error_infobar_response_cb (GtkInfoBar *info_bar,
 
2881
    gint response_id,
 
2882
    gpointer user_data)
 
2883
{
 
2884
  switch (response_id)
 
2885
    {
 
2886
      case GTK_RESPONSE_CLOSE:
 
2887
        gtk_widget_destroy (GTK_WIDGET (info_bar));
 
2888
        break;
 
2889
      case EMP_RESPONSE_BALANCE:
 
2890
        empathy_url_show (GTK_WIDGET (info_bar),
 
2891
            g_object_get_data (G_OBJECT (info_bar), "uri"));
 
2892
        break;
 
2893
    }
 
2894
}
 
2895
 
 
2896
static void
 
2897
display_error (EmpathyCallWindow *self,
 
2898
    const gchar *img,
 
2899
    const gchar *title,
 
2900
    const gchar *desc,
 
2901
    const gchar *details,
 
2902
    const gchar *button_text,
 
2903
    const gchar *uri,
 
2904
    gint button_response)
 
2905
{
 
2906
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2907
  GtkWidget *info_bar;
 
2908
  GtkWidget *content_area;
 
2909
  GtkWidget *hbox;
 
2910
  GtkWidget *vbox;
 
2911
  GtkWidget *image;
 
2912
  GtkWidget *label;
 
2913
  gchar *txt;
 
2914
 
 
2915
  /* Create info bar */
 
2916
  info_bar = gtk_info_bar_new ();
 
2917
 
 
2918
  if (button_text != NULL)
 
2919
    {
 
2920
      gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
 
2921
          button_text, button_response);
 
2922
      g_object_set_data_full (G_OBJECT (info_bar),
 
2923
          "uri", g_strdup (uri), g_free);
 
2924
    }
 
2925
 
 
2926
  gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
 
2927
      GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
 
2928
 
 
2929
  gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
 
2930
 
 
2931
  content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
 
2932
 
 
2933
  /* hbox containing the image and the messages vbox */
 
2934
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
 
2935
  gtk_container_add (GTK_CONTAINER (content_area), hbox);
 
2936
 
 
2937
  /* Add image */
 
2938
  image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
 
2939
  gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
 
2940
 
 
2941
  /* vbox containing the main message and the details expander */
 
2942
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
 
2943
  gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
 
2944
 
 
2945
  /* Add text */
 
2946
  txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
 
2947
 
 
2948
  label = gtk_label_new (NULL);
 
2949
  gtk_label_set_markup (GTK_LABEL (label), txt);
 
2950
  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
 
2951
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
 
2952
  g_free (txt);
 
2953
 
 
2954
  gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
 
2955
 
 
2956
  /* Add details */
 
2957
  if (details != NULL)
 
2958
    {
 
2959
      GtkWidget *expander;
 
2960
 
 
2961
      expander = gtk_expander_new (_("Technical Details"));
 
2962
 
 
2963
      txt = g_strdup_printf ("<i>%s</i>", details);
 
2964
 
 
2965
      label = gtk_label_new (NULL);
 
2966
      gtk_label_set_markup (GTK_LABEL (label), txt);
 
2967
      gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
 
2968
      gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
 
2969
      g_free (txt);
 
2970
 
 
2971
      gtk_container_add (GTK_CONTAINER (expander), label);
 
2972
      gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
 
2973
    }
 
2974
 
 
2975
  g_signal_connect (info_bar, "response",
 
2976
      G_CALLBACK (on_error_infobar_response_cb), NULL);
 
2977
 
 
2978
  gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
 
2979
      FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
 
2980
  gtk_widget_show_all (info_bar);
 
2981
}
 
2982
 
 
2983
#if 0
 
2984
static gchar *
 
2985
media_stream_error_to_txt (EmpathyCallWindow *self,
 
2986
    TpCallChannel *call,
 
2987
    gboolean audio,
 
2988
    TpMediaStreamError error)
 
2989
{
 
2990
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
2991
  const gchar *cm = NULL;
 
2992
  gchar *url;
 
2993
  gchar *result;
 
2994
 
 
2995
  switch (error)
 
2996
    {
 
2997
      case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
 
2998
        if (audio)
 
2999
          return g_strdup_printf (
 
3000
              _("%s's software does not understand any of the audio formats "
 
3001
                "supported by your computer"),
 
3002
            empathy_contact_get_alias (priv->contact));
 
3003
        else
 
3004
          return g_strdup_printf (
 
3005
              _("%s's software does not understand any of the video formats "
 
3006
                "supported by your computer"),
 
3007
            empathy_contact_get_alias (priv->contact));
 
3008
 
 
3009
      case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
 
3010
        return g_strdup_printf (
 
3011
            _("Can't establish a connection to %s. "
 
3012
              "One of you might be on a network that does not allow "
 
3013
              "direct connections."),
 
3014
          empathy_contact_get_alias (priv->contact));
 
3015
 
 
3016
      case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
 
3017
          return g_strdup (_("There was a failure on the network"));
 
3018
 
 
3019
      case TP_MEDIA_STREAM_ERROR_NO_CODECS:
 
3020
        if (audio)
 
3021
          return g_strdup (_("The audio formats necessary for this call "
 
3022
                "are not installed on your computer"));
 
3023
        else
 
3024
          return g_strdup (_("The video formats necessary for this call "
 
3025
                "are not installed on your computer"));
 
3026
 
 
3027
      case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
 
3028
        tp_connection_parse_object_path (
 
3029
            tp_channel_borrow_connection (TP_CHANNEL (call)),
 
3030
            NULL, &cm);
 
3031
 
 
3032
        url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
 
3033
            "product=Telepathy&amp;component=%s", cm);
 
3034
 
 
3035
        result = g_strdup_printf (
 
3036
            _("Something unexpected happened in a Telepathy component. "
 
3037
              "Please <a href=\"%s\">report this bug</a> and attach "
 
3038
              "logs gathered from the 'Debug' window in the Help menu."), url);
 
3039
 
 
3040
        g_free (url);
 
3041
        g_free (cm);
 
3042
        return result;
 
3043
 
 
3044
      case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
 
3045
        return g_strdup (_("There was a failure in the call engine"));
 
3046
 
 
3047
      case TP_MEDIA_STREAM_ERROR_EOS:
 
3048
        return g_strdup (_("The end of the stream was reached"));
 
3049
 
 
3050
      case TP_MEDIA_STREAM_ERROR_UNKNOWN:
 
3051
      default:
 
3052
        return NULL;
 
3053
    }
 
3054
}
 
3055
 
 
3056
static void
 
3057
empathy_call_window_stream_error (EmpathyCallWindow *self,
 
3058
    TpCallChannel *call,
 
3059
    gboolean audio,
 
3060
    guint code,
 
3061
    const gchar *msg,
 
3062
    const gchar *icon,
 
3063
    const gchar *title)
 
3064
{
 
3065
  gchar *desc;
 
3066
 
 
3067
  desc = media_stream_error_to_txt (self, call, audio, code);
 
3068
  if (desc == NULL)
 
3069
    {
 
3070
      /* No description, use the error message. That's not great as it's not
 
3071
       * localized but it's better than nothing. */
 
3072
      display_error (self, call, icon, title, msg, NULL);
 
3073
    }
 
3074
  else
 
3075
    {
 
3076
      display_error (self, call, icon, title, desc, msg);
 
3077
      g_free (desc);
 
3078
    }
 
3079
}
 
3080
 
 
3081
static void
 
3082
empathy_call_window_audio_stream_error (TpCallChannel *call,
 
3083
    guint code,
 
3084
    const gchar *msg,
 
3085
    EmpathyCallWindow *self)
 
3086
{
 
3087
  empathy_call_window_stream_error (self, call, TRUE, code, msg,
 
3088
      "gnome-stock-mic", _("Can't establish audio stream"));
 
3089
}
 
3090
 
 
3091
static void
 
3092
empathy_call_window_video_stream_error (TpCallChannel *call,
 
3093
    guint code,
 
3094
    const gchar *msg,
 
3095
    EmpathyCallWindow *self)
 
3096
{
 
3097
  empathy_call_window_stream_error (self, call, FALSE, code, msg,
 
3098
      "camera-web", _("Can't establish video stream"));
 
3099
}
 
3100
#endif
 
3101
 
 
3102
static void
 
3103
show_balance_error (EmpathyCallWindow *self)
 
3104
{
 
3105
  TpChannel *call;
 
3106
  TpConnection *conn;
 
3107
  gchar *balance, *tmp;
 
3108
  const gchar *uri, *currency;
 
3109
  gint amount;
 
3110
  guint scale;
 
3111
 
 
3112
  g_object_get (self->priv->handler,
 
3113
      "call-channel", &call,
 
3114
      NULL);
 
3115
 
 
3116
  conn = tp_channel_borrow_connection (call);
 
3117
  g_object_unref (call);
 
3118
 
 
3119
  uri = tp_connection_get_balance_uri (conn);
 
3120
 
 
3121
  if (!tp_connection_get_balance (conn, &amount, &scale, &currency))
 
3122
    {
 
3123
      /* unknown balance */
 
3124
      balance = g_strdup ("(--)");
 
3125
    }
 
3126
  else
 
3127
    {
 
3128
      char *money = empathy_format_currency (amount, scale, currency);
 
3129
 
 
3130
      balance = g_strdup_printf ("%s %s",
 
3131
          currency, money);
 
3132
      g_free (money);
 
3133
    }
 
3134
 
 
3135
  tmp = g_strdup_printf (_("Your current balance is %s."), balance),
 
3136
 
 
3137
  display_error (self,
 
3138
      NULL,
 
3139
      _("Sorry, you don’t have enough credit for that call."),
 
3140
      tmp, NULL,
 
3141
      _("Top Up"),
 
3142
      uri,
 
3143
      EMP_RESPONSE_BALANCE);
 
3144
 
 
3145
  g_free (tmp);
 
3146
  g_free (balance);
 
3147
}
 
3148
 
 
3149
static void
 
3150
empathy_call_window_state_changed_cb (EmpathyCallHandler *handler,
 
3151
    TpCallState state,
 
3152
    gchar *reason,
 
3153
    EmpathyCallWindow *self)
 
3154
{
 
3155
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
3156
  TpCallChannel *call;
 
3157
  gboolean can_send_video;
 
3158
 
 
3159
  if (state == TP_CALL_STATE_ENDED)
 
3160
    {
 
3161
      DEBUG ("Call ended: %s", (reason != NULL && reason[0] != '\0') ? reason : "unspecified reason");
 
3162
      empathy_call_window_disconnected (self, TRUE);
 
3163
      if (!tp_strdiff (reason, TP_ERROR_STR_INSUFFICIENT_BALANCE))
 
3164
          show_balance_error (self);
 
3165
      return;
 
3166
    }
 
3167
 
 
3168
  if (state != TP_CALL_STATE_ACCEPTED)
 
3169
    return;
 
3170
 
 
3171
  if (priv->call_state == CONNECTED)
 
3172
    return;
 
3173
 
 
3174
  g_timer_start (priv->timer);
 
3175
  priv->call_state = CONNECTED;
 
3176
 
 
3177
  empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
 
3178
 
 
3179
  can_send_video = priv->video_input != NULL &&
 
3180
    empathy_contact_can_voip_video (priv->contact) &&
 
3181
    empathy_camera_monitor_get_available (priv->camera_monitor);
 
3182
 
 
3183
  g_object_get (priv->handler, "call-channel", &call, NULL);
 
3184
 
 
3185
  if (tp_call_channel_has_dtmf (call))
 
3186
    gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
 
3187
 
 
3188
  if (priv->video_input == NULL)
 
3189
    empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
 
3190
 
 
3191
  gtk_widget_set_sensitive (priv->camera_button, can_send_video);
 
3192
 
 
3193
  empathy_call_window_show_hangup_button (self, TRUE);
 
3194
 
 
3195
  gtk_widget_set_sensitive (priv->mic_button, TRUE);
 
3196
 
 
3197
  clutter_actor_hide (priv->video_output);
 
3198
  gtk_widget_show (priv->remote_user_avatar_widget);
 
3199
 
 
3200
  g_object_unref (call);
 
3201
 
 
3202
  g_mutex_lock (priv->lock);
 
3203
 
 
3204
  priv->timer_id = g_timeout_add_seconds (1,
 
3205
    empathy_call_window_update_timer, self);
 
3206
 
 
3207
  g_mutex_unlock (priv->lock);
 
3208
 
 
3209
  empathy_call_window_update_timer (self);
 
3210
 
 
3211
  gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
 
3212
}
 
3213
 
 
3214
static gboolean
 
3215
empathy_call_window_show_video_output_cb (gpointer user_data)
 
3216
{
 
3217
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
 
3218
 
 
3219
  if (self->priv->video_output != NULL)
 
3220
    {
 
3221
      gtk_widget_hide (self->priv->remote_user_avatar_widget);
 
3222
      clutter_actor_show (self->priv->video_output);
 
3223
      clutter_actor_raise_top (self->priv->overlay_box);
 
3224
    }
 
3225
 
 
3226
  return FALSE;
 
3227
}
 
3228
 
 
3229
static gboolean
 
3230
empathy_call_window_check_video_cb (gpointer data)
 
3231
{
 
3232
  EmpathyCallWindow *self = data;
 
3233
 
 
3234
  if (self->priv->got_video)
 
3235
    {
 
3236
      self->priv->got_video = FALSE;
 
3237
      return TRUE;
 
3238
    }
 
3239
 
 
3240
  /* No video in the last N seconds, display the remote avatar */
 
3241
  empathy_call_window_show_video_output (self, FALSE);
 
3242
 
 
3243
  return TRUE;
 
3244
}
 
3245
 
 
3246
/* Called from the streaming thread */
 
3247
static gboolean
 
3248
empathy_call_window_video_probe_cb (GstPad *pad,
 
3249
    GstMiniObject *mini_obj,
 
3250
    EmpathyCallWindow *self)
 
3251
{
 
3252
  /* Ignore events */
 
3253
  if (GST_IS_EVENT (mini_obj))
 
3254
    return TRUE;
 
3255
 
 
3256
  if (G_UNLIKELY (!self->priv->got_video))
 
3257
    {
 
3258
      /* show the remote video */
 
3259
      g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
 
3260
          empathy_call_window_show_video_output_cb,
 
3261
          g_object_ref (self), g_object_unref);
 
3262
 
 
3263
      self->priv->got_video = TRUE;
 
3264
    }
 
3265
 
 
3266
  return TRUE;
 
3267
}
 
3268
 
 
3269
/* Called from the streaming thread */
 
3270
static gboolean
 
3271
empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
 
3272
  TfContent *content, GstPad *src, gpointer user_data)
 
3273
{
 
3274
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
 
3275
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
3276
  gboolean retval = FALSE;
 
3277
  guint media_type;
 
3278
 
 
3279
  GstPad *pad;
 
3280
 
 
3281
  g_mutex_lock (priv->lock);
 
3282
 
 
3283
  g_object_get (content, "media-type", &media_type, NULL);
 
3284
 
 
3285
  switch (media_type)
 
3286
    {
 
3287
      case TP_MEDIA_STREAM_TYPE_AUDIO:
 
3288
        pad = empathy_call_window_get_audio_sink_pad (self, content);
 
3289
        break;
 
3290
      case TP_MEDIA_STREAM_TYPE_VIDEO:
 
3291
        g_idle_add (empathy_call_window_show_video_output_cb, self);
 
3292
        pad = empathy_call_window_get_video_sink_pad (self);
 
3293
 
 
3294
        gst_pad_add_data_probe (src,
 
3295
            G_CALLBACK (empathy_call_window_video_probe_cb), self);
 
3296
        if (priv->got_video_src > 0)
 
3297
          g_source_remove (priv->got_video_src);
 
3298
        priv->got_video_src = g_timeout_add_seconds (1,
 
3299
            empathy_call_window_check_video_cb, self);
 
3300
        break;
 
3301
      default:
 
3302
        g_assert_not_reached ();
 
3303
    }
 
3304
 
 
3305
  if (pad == NULL)
 
3306
    goto out;
 
3307
 
 
3308
  if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
 
3309
      g_warning ("Could not link %s sink pad",
 
3310
          media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
 
3311
  else
 
3312
      retval = TRUE;
 
3313
 
 
3314
  gst_object_unref (pad);
 
3315
 
 
3316
 out:
 
3317
 
 
3318
  /* If no sink could be linked, try to add fakesink to prevent the whole call
 
3319
   * aborting */
 
3320
 
 
3321
  if (!retval)
 
3322
    {
 
3323
      GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
 
3324
 
 
3325
      if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
 
3326
        {
 
3327
          GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
 
3328
          if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
 
3329
              GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
 
3330
            {
 
3331
              gst_element_set_locked_state (fakesink, TRUE);
 
3332
              gst_element_set_state (fakesink, GST_STATE_NULL);
 
3333
              gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
 
3334
            }
 
3335
          else
 
3336
            {
 
3337
              DEBUG ("Could not link real sink, linked fakesink instead");
 
3338
            }
 
3339
          gst_object_unref (sinkpad);
 
3340
        }
 
3341
      else
 
3342
        {
 
3343
          gst_object_unref (fakesink);
 
3344
        }
 
3345
    }
 
3346
 
 
3347
 
 
3348
  g_mutex_unlock (priv->lock);
 
3349
 
 
3350
  return TRUE;
 
3351
}
 
3352
 
 
3353
static void
 
3354
empathy_call_window_prepare_audio_output (EmpathyCallWindow *self,
 
3355
  TfContent *content)
 
3356
{
 
3357
  EmpathyCallWindowPriv *priv = self->priv;
 
3358
 
 
3359
  g_assert (priv->audio_output_added == FALSE);
 
3360
  g_assert (priv->audio_output == FALSE);
 
3361
 
 
3362
  priv->audio_output = empathy_audio_sink_new ();
 
3363
  g_object_ref_sink (priv->audio_output);
 
3364
 
 
3365
  /* volume button to output volume linking */
 
3366
  g_object_bind_property (priv->audio_output, "volume",
 
3367
    priv->volume_button, "value",
 
3368
    G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
 
3369
 
 
3370
  g_object_bind_property_full (content, "requested-output-volume",
 
3371
    priv->audio_output, "volume",
 
3372
    G_BINDING_DEFAULT,
 
3373
    audio_control_volume_to_element,
 
3374
    element_volume_to_audio_control,
 
3375
    NULL, NULL);
 
3376
 
 
3377
  /* Link volumes together, sync the current audio input volume property
 
3378
    * back to farstream first */
 
3379
  g_object_bind_property_full (priv->audio_output, "volume",
 
3380
    content, "reported-output-volume",
 
3381
    G_BINDING_SYNC_CREATE,
 
3382
    element_volume_to_audio_control,
 
3383
    audio_control_volume_to_element,
 
3384
    NULL, NULL);
 
3385
 
 
3386
  /* For raw audio conferences assume that the producer of the raw data
 
3387
   * has already processed it, so turn off any echo cancellation and any
 
3388
   * other audio improvements that come with it */
 
3389
  empathy_audio_sink_set_echo_cancel (
 
3390
    EMPATHY_GST_AUDIO_SINK (priv->audio_output),
 
3391
    !empathy_call_window_content_is_raw (content));
 
3392
}
 
3393
 
 
3394
 
 
3395
static gboolean
 
3396
empathy_call_window_content_added_cb (EmpathyCallHandler *handler,
 
3397
  TfContent *content, gpointer user_data)
 
3398
{
 
3399
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
 
3400
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
3401
  GstPad *sink, *pad;
 
3402
  FsMediaType media_type;
 
3403
  gboolean retval = FALSE;
 
3404
 
 
3405
  g_object_get (content, "media-type", &media_type, "sink-pad", &sink, NULL);
 
3406
  g_assert (sink != NULL);
 
3407
 
 
3408
  switch (media_type)
 
3409
    {
 
3410
      case FS_MEDIA_TYPE_AUDIO:
 
3411
 
 
3412
        /* For raw audio conferences assume that the receiver of the raw data
 
3413
         * wants it unprocessed, so turn off any echo cancellation and any
 
3414
         * other audio improvements that come with it */
 
3415
        empathy_audio_src_set_echo_cancel (
 
3416
          EMPATHY_GST_AUDIO_SRC (priv->audio_input),
 
3417
          !empathy_call_window_content_is_raw (content));
 
3418
 
 
3419
        /* Link volumes together, sync the current audio input volume property
 
3420
         * back to farstream first */
 
3421
        g_object_bind_property_full (content, "requested-input-volume",
 
3422
          priv->audio_input, "volume",
 
3423
          G_BINDING_DEFAULT,
 
3424
          audio_control_volume_to_element,
 
3425
          element_volume_to_audio_control,
 
3426
          NULL, NULL);
 
3427
 
 
3428
        g_object_bind_property_full (priv->audio_input, "volume",
 
3429
          content, "reported-input-volume",
 
3430
          G_BINDING_SYNC_CREATE,
 
3431
          element_volume_to_audio_control,
 
3432
          audio_control_volume_to_element,
 
3433
          NULL, NULL);
 
3434
 
 
3435
        if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
 
3436
          {
 
3437
            g_warning ("Could not add audio source to pipeline");
 
3438
            break;
 
3439
          }
 
3440
 
 
3441
        pad = gst_element_get_static_pad (priv->audio_input, "src");
 
3442
        if (!pad)
 
3443
          {
 
3444
            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
 
3445
            g_warning ("Could not get source pad from audio source");
 
3446
            break;
 
3447
          }
 
3448
 
 
3449
        if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
 
3450
          {
 
3451
            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
 
3452
            gst_object_unref (pad);
 
3453
            g_warning ("Could not link audio source to farsight");
 
3454
            break;
 
3455
          }
 
3456
        gst_object_unref (pad);
 
3457
 
 
3458
        if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
 
3459
          {
 
3460
            g_warning ("Could not start audio source");
 
3461
            gst_element_set_state (priv->audio_input, GST_STATE_NULL);
 
3462
            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
 
3463
            break;
 
3464
          }
 
3465
 
 
3466
        /* Prepare our audio output, not added yet though */
 
3467
        empathy_call_window_prepare_audio_output (self, content);
 
3468
 
 
3469
        retval = TRUE;
 
3470
        break;
 
3471
      case FS_MEDIA_TYPE_VIDEO:
 
3472
        if (priv->video_tee != NULL)
 
3473
          {
 
3474
            pad = gst_element_get_request_pad (priv->video_tee, "src%d");
 
3475
            if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
 
3476
              {
 
3477
                g_warning ("Could not link video source input pipeline");
 
3478
                break;
 
3479
              }
 
3480
            gst_object_unref (pad);
 
3481
          }
 
3482
 
 
3483
        retval = TRUE;
 
3484
        break;
 
3485
      default:
 
3486
        g_assert_not_reached ();
 
3487
    }
 
3488
 
 
3489
  gst_object_unref (sink);
 
3490
  return retval;
 
3491
}
 
3492
 
 
3493
static void
 
3494
empathy_call_window_remove_video_input (EmpathyCallWindow *self)
 
3495
{
 
3496
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
3497
  GstElement *preview;
 
3498
 
 
3499
  disable_camera (self);
 
3500
 
 
3501
  DEBUG ("remove video input");
 
3502
  preview = priv->video_preview_sink;
 
3503
 
 
3504
  gst_element_set_state (priv->video_input, GST_STATE_NULL);
 
3505
  gst_element_set_state (priv->video_tee, GST_STATE_NULL);
 
3506
  gst_element_set_state (preview, GST_STATE_NULL);
 
3507
 
 
3508
  gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
 
3509
    preview, NULL);
 
3510
 
 
3511
  g_object_unref (priv->video_input);
 
3512
  priv->video_input = NULL;
 
3513
  g_object_unref (priv->video_tee);
 
3514
  priv->video_tee = NULL;
 
3515
  clutter_actor_destroy (priv->video_preview);
 
3516
  priv->video_preview = NULL;
 
3517
 
 
3518
  gtk_widget_set_sensitive (priv->camera_button, FALSE);
 
3519
}
 
3520
 
 
3521
static void
 
3522
start_call (EmpathyCallWindow *self)
 
3523
{
 
3524
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
3525
 
 
3526
  priv->call_started = TRUE;
 
3527
  empathy_call_handler_start_call (priv->handler,
 
3528
      gtk_get_current_event_time ());
 
3529
 
 
3530
  if (empathy_call_handler_has_initial_video (priv->handler))
 
3531
    {
 
3532
      TpCallChannel *call;
 
3533
      TpSendingState s;
 
3534
 
 
3535
      g_object_get (priv->handler, "call-channel", &call, NULL);
 
3536
      /* If the call channel isn't set yet we're requesting it, if we're
 
3537
       * requesting it with initial video it should be PENDING_SEND when we get
 
3538
       * it */
 
3539
      if (call == NULL)
 
3540
        s = TP_SENDING_STATE_PENDING_SEND;
 
3541
      else
 
3542
        s = empathy_call_channel_get_video_state (call);
 
3543
 
 
3544
      if (s == TP_SENDING_STATE_PENDING_SEND ||
 
3545
          s == TP_SENDING_STATE_SENDING)
 
3546
        {
 
3547
          /* Enable 'send video' buttons and display the preview */
 
3548
          gtk_toggle_tool_button_set_active (
 
3549
            GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), TRUE);
 
3550
        }
 
3551
      else
 
3552
        {
 
3553
          gtk_toggle_tool_button_set_active (
 
3554
            GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
 
3555
 
 
3556
          if (priv->video_preview == NULL)
 
3557
            {
 
3558
              create_video_preview (self);
 
3559
              add_video_preview_to_pipeline (self);
 
3560
            }
 
3561
        }
 
3562
 
 
3563
      if (call != NULL)
 
3564
        g_object_unref (call);
 
3565
    }
 
3566
}
 
3567
 
 
3568
static gboolean
 
3569
empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
 
3570
  gpointer user_data)
 
3571
{
 
3572
  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
 
3573
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
3574
  GstState newstate, pending;
 
3575
 
 
3576
  empathy_call_handler_bus_message (priv->handler, bus, message);
 
3577
 
 
3578
  switch (GST_MESSAGE_TYPE (message))
 
3579
    {
 
3580
      case GST_MESSAGE_STATE_CHANGED:
 
3581
        if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
 
3582
          {
 
3583
            gst_message_parse_state_changed (message, NULL, &newstate, NULL);
 
3584
          }
 
3585
        if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
 
3586
            !priv->call_started)
 
3587
          {
 
3588
            gst_message_parse_state_changed (message, NULL, &newstate, NULL);
 
3589
            if (newstate == GST_STATE_PAUSED)
 
3590
              {
 
3591
                gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
 
3592
                priv->pipeline_playing = TRUE;
 
3593
 
 
3594
                if (priv->start_call_when_playing)
 
3595
                  start_call (self);
 
3596
              }
 
3597
          }
 
3598
        if (priv->video_preview_sink != NULL &&
 
3599
            GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_preview_sink))
 
3600
          {
 
3601
            gst_message_parse_state_changed (message, NULL, &newstate,
 
3602
                &pending);
 
3603
 
 
3604
            if (newstate == GST_STATE_PLAYING &&
 
3605
                pending == GST_STATE_VOID_PENDING)
 
3606
              empathy_call_window_stop_camera_spinning (self);
 
3607
          }
 
3608
        break;
 
3609
      case GST_MESSAGE_ERROR:
 
3610
        {
 
3611
          GError *error = NULL;
 
3612
          GstElement *gst_error;
 
3613
          gchar *debug;
 
3614
          gchar *name;
 
3615
 
 
3616
          gst_message_parse_error (message, &error, &debug);
 
3617
          gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
 
3618
 
 
3619
          g_message ("Element error: %s -- %s\n", error->message, debug);
 
3620
 
 
3621
          name = gst_element_get_name (gst_error);
 
3622
          if (g_str_has_prefix (name, VIDEO_INPUT_ERROR_PREFIX))
 
3623
            {
 
3624
              /* Remove the video input and continue */
 
3625
              if (priv->video_input != NULL)
 
3626
                empathy_call_window_remove_video_input (self);
 
3627
              gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
 
3628
            }
 
3629
          else
 
3630
            {
 
3631
              empathy_call_window_disconnected (self, TRUE);
 
3632
            }
 
3633
          g_free (name);
 
3634
          g_error_free (error);
 
3635
          g_free (debug);
 
3636
        }
 
3637
      case GST_MESSAGE_UNKNOWN:
 
3638
      case GST_MESSAGE_EOS:
 
3639
      case GST_MESSAGE_WARNING:
 
3640
      case GST_MESSAGE_INFO:
 
3641
      case GST_MESSAGE_TAG:
 
3642
      case GST_MESSAGE_BUFFERING:
 
3643
      case GST_MESSAGE_STATE_DIRTY:
 
3644
      case GST_MESSAGE_STEP_DONE:
 
3645
      case GST_MESSAGE_CLOCK_PROVIDE:
 
3646
      case GST_MESSAGE_CLOCK_LOST:
 
3647
      case GST_MESSAGE_NEW_CLOCK:
 
3648
      case GST_MESSAGE_STRUCTURE_CHANGE:
 
3649
      case GST_MESSAGE_STREAM_STATUS:
 
3650
      case GST_MESSAGE_APPLICATION:
 
3651
      case GST_MESSAGE_ELEMENT:
 
3652
      case GST_MESSAGE_SEGMENT_START:
 
3653
      case GST_MESSAGE_SEGMENT_DONE:
 
3654
      case GST_MESSAGE_DURATION:
 
3655
      case GST_MESSAGE_ANY:
 
3656
      default:
 
3657
        break;
 
3658
    }
 
3659
 
 
3660
  return TRUE;
 
3661
}
 
3662
 
 
3663
static void
 
3664
empathy_call_window_members_changed_cb (TpCallChannel *call,
 
3665
    GHashTable *updates,
 
3666
    GPtrArray *removed,
 
3667
    TpCallStateReason *reason,
 
3668
    EmpathyCallWindow *self)
 
3669
{
 
3670
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
3671
  GHashTableIter iter;
 
3672
  gpointer key, value;
 
3673
  gboolean held = FALSE;
 
3674
 
 
3675
  g_hash_table_iter_init (&iter, updates);
 
3676
  while (g_hash_table_iter_next (&iter, &key, &value))
 
3677
    {
 
3678
      if (GPOINTER_TO_INT (value) & TP_CALL_MEMBER_FLAG_HELD)
 
3679
        {
 
3680
          /* This assumes this is a 1-1 call, otherwise one participant
 
3681
           * putting the call on hold wouldn't mean the call is on hold
 
3682
           * for everyone. */
 
3683
          held = TRUE;
 
3684
          break;
 
3685
        }
 
3686
    }
 
3687
 
 
3688
  if (held)
 
3689
    priv->call_state = HELD;
 
3690
  else if (priv->call_state == HELD)
 
3691
    priv->call_state = CONNECTED;
 
3692
}
 
3693
 
 
3694
static void
 
3695
call_handler_notify_call_cb (EmpathyCallHandler *handler,
 
3696
    GParamSpec *spec,
 
3697
    EmpathyCallWindow *self)
 
3698
{
 
3699
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
3700
  TpCallChannel *call;
 
3701
 
 
3702
  g_object_get (priv->handler, "call-channel", &call, NULL);
 
3703
  if (call == NULL)
 
3704
    return;
 
3705
 
 
3706
/* FIXME
 
3707
  tp_g_signal_connect_object (call, "audio-stream-error",
 
3708
      G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
 
3709
  tp_g_signal_connect_object (call, "video-stream-error",
 
3710
      G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
 
3711
*/
 
3712
 
 
3713
  tp_g_signal_connect_object (call, "members-changed",
 
3714
      G_CALLBACK (empathy_call_window_members_changed_cb), self, 0);
 
3715
 
 
3716
  g_object_unref (call);
 
3717
}
 
3718
 
 
3719
static void
 
3720
empathy_call_window_connect_handler (EmpathyCallWindow *self)
 
3721
{
 
3722
  EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
3723
  TpCallChannel *call;
 
3724
 
 
3725
  g_signal_connect (priv->handler, "state-changed",
 
3726
    G_CALLBACK (empathy_call_window_state_changed_cb), self);
 
3727
  g_signal_connect (priv->handler, "conference-added",
 
3728
    G_CALLBACK (empathy_call_window_conference_added_cb), self);
 
3729
  g_signal_connect (priv->handler, "conference-removed",
 
3730
    G_CALLBACK (empathy_call_window_conference_removed_cb), self);
 
3731
  g_signal_connect (priv->handler, "closed",
 
3732
    G_CALLBACK (empathy_call_window_channel_closed_cb), self);
 
3733
  g_signal_connect (priv->handler, "src-pad-added",
 
3734
    G_CALLBACK (empathy_call_window_src_added_cb), self);
 
3735
  g_signal_connect (priv->handler, "content-added",
 
3736
    G_CALLBACK (empathy_call_window_content_added_cb), self);
 
3737
  g_signal_connect (priv->handler, "content-removed",
 
3738
    G_CALLBACK (empathy_call_window_content_removed_cb), self);
 
3739
 
 
3740
  /* We connect to ::call-channel unconditionally since we'll
 
3741
   * get new channels if we hangup and redial or if we reuse the
 
3742
   * call window. */
 
3743
  g_signal_connect (priv->handler, "notify::call-channel",
 
3744
    G_CALLBACK (call_handler_notify_call_cb), self);
 
3745
 
 
3746
  g_signal_connect (priv->handler, "framerate-changed",
 
3747
    G_CALLBACK (empathy_call_window_framerate_changed_cb), self);
 
3748
  g_signal_connect (priv->handler, "resolution-changed",
 
3749
    G_CALLBACK (empathy_call_window_resolution_changed_cb), self);
 
3750
 
 
3751
  g_object_get (priv->handler, "call-channel", &call, NULL);
 
3752
  if (call != NULL)
 
3753
    {
 
3754
      /* We won't get notify::call-channel for this channel, so
 
3755
       * directly call the callback. */
 
3756
      call_handler_notify_call_cb (priv->handler, NULL, self);
 
3757
      g_object_unref (call);
 
3758
    }
 
3759
}
 
3760
 
 
3761
static void
 
3762
empathy_call_window_realized_cb (GtkWidget *widget,
 
3763
    EmpathyCallWindow *self)
 
3764
{
 
3765
  gint width;
 
3766
 
 
3767
  /* Make the hangup button twice as wide */
 
3768
  width = gtk_widget_get_allocated_width (self->priv->hangup_button);
 
3769
  gtk_widget_set_size_request (self->priv->hangup_button, width * 2, -1);
 
3770
 
 
3771
  empathy_call_window_connect_handler (self);
 
3772
 
 
3773
  gst_element_set_state (self->priv->pipeline, GST_STATE_PAUSED);
 
3774
}
 
3775
 
 
3776
static gboolean
 
3777
empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
 
3778
  EmpathyCallWindow *window)
 
3779
{
 
3780
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
3781
 
 
3782
  if (priv->pipeline != NULL)
 
3783
    {
 
3784
      if (priv->bus_message_source_id != 0)
 
3785
        {
 
3786
          g_source_remove (priv->bus_message_source_id);
 
3787
          priv->bus_message_source_id = 0;
 
3788
        }
 
3789
 
 
3790
      gst_element_set_state (priv->pipeline, GST_STATE_NULL);
 
3791
    }
 
3792
 
 
3793
  if (priv->call_state == CONNECTING)
 
3794
    empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
 
3795
 
 
3796
  return FALSE;
 
3797
}
 
3798
 
 
3799
static void
 
3800
show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
 
3801
{
 
3802
  GtkWidget *menu;
 
3803
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
3804
 
 
3805
  menu = gtk_ui_manager_get_widget (priv->ui_manager,
 
3806
            "/menubar1");
 
3807
 
 
3808
  if (set_fullscreen)
 
3809
    {
 
3810
      gtk_widget_hide (priv->dtmf_panel);
 
3811
      gtk_widget_hide (menu);
 
3812
      gtk_widget_hide (priv->toolbar);
 
3813
    }
 
3814
  else
 
3815
    {
 
3816
      if (priv->dialpad_was_visible_before_fs)
 
3817
        gtk_widget_show (priv->dtmf_panel);
 
3818
 
 
3819
      gtk_widget_show (menu);
 
3820
      gtk_widget_show (priv->toolbar);
 
3821
 
 
3822
      gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
 
3823
          priv->original_height_before_fs);
 
3824
    }
 
3825
}
 
3826
 
 
3827
static void
 
3828
show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
 
3829
{
 
3830
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
3831
 
 
3832
  gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
 
3833
      set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
 
3834
  gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
 
3835
      set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
 
3836
 
 
3837
  if (priv->video_output != NULL)
 
3838
    {
 
3839
#if 0
 
3840
      gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
 
3841
          priv->video_output, TRUE, TRUE,
 
3842
          set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
 
3843
          GTK_PACK_START);
 
3844
#endif
 
3845
    }
 
3846
}
 
3847
 
 
3848
static gboolean
 
3849
empathy_call_window_state_event_cb (GtkWidget *widget,
 
3850
  GdkEventWindowState *event, EmpathyCallWindow *window)
 
3851
{
 
3852
  if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
 
3853
    {
 
3854
      EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
3855
      gboolean set_fullscreen = event->new_window_state &
 
3856
        GDK_WINDOW_STATE_FULLSCREEN;
 
3857
 
 
3858
      if (set_fullscreen)
 
3859
        {
 
3860
          gboolean dialpad_was_visible;
 
3861
          GtkAllocation allocation;
 
3862
          gint original_width, original_height;
 
3863
 
 
3864
          gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
 
3865
          original_width = allocation.width;
 
3866
          original_height = allocation.height;
 
3867
 
 
3868
          g_object_get (priv->dtmf_panel,
 
3869
              "visible", &dialpad_was_visible,
 
3870
              NULL);
 
3871
 
 
3872
          priv->dialpad_was_visible_before_fs = dialpad_was_visible;
 
3873
          priv->original_width_before_fs = original_width;
 
3874
          priv->original_height_before_fs = original_height;
 
3875
 
 
3876
          if (priv->video_output_motion_handler_id == 0 &&
 
3877
                priv->video_output != NULL)
 
3878
            {
 
3879
              priv->video_output_motion_handler_id = g_signal_connect (
 
3880
                  G_OBJECT (priv->video_container), "motion-notify-event",
 
3881
                  G_CALLBACK (empathy_call_window_video_output_motion_notify),
 
3882
                  window);
 
3883
            }
 
3884
        }
 
3885
      else
 
3886
        {
 
3887
          disconnect_video_output_motion_handler (window);
 
3888
        }
 
3889
 
 
3890
      empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
 
3891
          set_fullscreen);
 
3892
      show_controls (window, set_fullscreen);
 
3893
      show_borders (window, set_fullscreen);
 
3894
      gtk_action_set_stock_id (priv->menu_fullscreen,
 
3895
          (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
 
3896
      priv->is_fullscreen = set_fullscreen;
 
3897
  }
 
3898
 
 
3899
  return FALSE;
 
3900
}
 
3901
 
 
3902
static void
 
3903
empathy_call_window_show_dialpad (EmpathyCallWindow *window,
 
3904
    gboolean active)
 
3905
{
 
3906
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
3907
  int w, h, dialpad_width;
 
3908
  GtkAllocation allocation;
 
3909
 
 
3910
  gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
 
3911
  w = allocation.width;
 
3912
  h = allocation.height;
 
3913
 
 
3914
  gtk_widget_get_preferred_width (priv->dtmf_panel, &dialpad_width, NULL);
 
3915
 
 
3916
  if (active)
 
3917
    {
 
3918
      gtk_widget_show (priv->dtmf_panel);
 
3919
      w += dialpad_width;
 
3920
    }
 
3921
  else
 
3922
    {
 
3923
      w -= dialpad_width;
 
3924
      gtk_widget_hide (priv->dtmf_panel);
 
3925
    }
 
3926
 
 
3927
  if (w > 0 && h > 0)
 
3928
    gtk_window_resize (GTK_WINDOW (window), w, h);
 
3929
}
 
3930
 
 
3931
static void
 
3932
empathy_call_window_set_send_video (EmpathyCallWindow *window,
 
3933
  CameraState state)
 
3934
{
 
3935
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
3936
  TpCallChannel *call;
 
3937
 
 
3938
  priv->sending_video = (state == CAMERA_STATE_ON);
 
3939
 
 
3940
  if (state == CAMERA_STATE_ON)
 
3941
    {
 
3942
      /* When we start sending video, we want to show the video preview by
 
3943
         default. */
 
3944
      display_video_preview (window, TRUE);
 
3945
    }
 
3946
  else
 
3947
    {
 
3948
      display_video_preview (window, FALSE);
 
3949
    }
 
3950
 
 
3951
  if (priv->call_state != CONNECTED)
 
3952
    return;
 
3953
 
 
3954
  g_object_get (priv->handler, "call-channel", &call, NULL);
 
3955
  DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
 
3956
  empathy_call_channel_send_video (call, priv->sending_video);
 
3957
  g_object_unref (call);
 
3958
}
 
3959
 
 
3960
static void
 
3961
empathy_call_window_hangup_cb (gpointer object,
 
3962
    EmpathyCallWindow *self)
 
3963
{
 
3964
  /* stopping the call will put it the ENDED state and
 
3965
   * from state_changed_cb we'll reconfigure the window
 
3966
   */
 
3967
  empathy_call_handler_stop_call (self->priv->handler);
 
3968
}
 
3969
 
 
3970
static void
 
3971
empathy_call_window_restart_call (EmpathyCallWindow *window)
 
3972
{
 
3973
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
3974
 
 
3975
  /* Remove error info bars */
 
3976
  gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
 
3977
      (GtkCallback) gtk_widget_destroy, NULL);
 
3978
 
 
3979
  create_video_output_widget (window);
 
3980
  priv->outgoing = TRUE;
 
3981
  empathy_call_window_set_state_connecting (window);
 
3982
 
 
3983
  if (priv->pipeline_playing)
 
3984
    start_call (window);
 
3985
  else
 
3986
    /* call will be started when the pipeline is ready */
 
3987
    priv->start_call_when_playing = TRUE;
 
3988
 
 
3989
  empathy_call_window_setup_avatars (window, priv->handler);
 
3990
 
 
3991
  empathy_call_window_show_hangup_button (window, TRUE);
 
3992
}
 
3993
 
 
3994
static void
 
3995
empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
 
3996
    EmpathyCallWindow *window)
 
3997
{
 
3998
  gboolean active;
 
3999
 
 
4000
  active = gtk_toggle_tool_button_get_active (button);
 
4001
 
 
4002
  empathy_call_window_show_dialpad (window, active);
 
4003
}
 
4004
 
 
4005
static void
 
4006
empathy_call_window_fullscreen_cb (gpointer object,
 
4007
                                   EmpathyCallWindow *window)
 
4008
{
 
4009
  empathy_call_window_fullscreen_toggle (window);
 
4010
}
 
4011
 
 
4012
static void
 
4013
empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
 
4014
{
 
4015
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
4016
 
 
4017
  if (priv->is_fullscreen)
 
4018
    gtk_window_unfullscreen (GTK_WINDOW (window));
 
4019
  else
 
4020
    gtk_window_fullscreen (GTK_WINDOW (window));
 
4021
}
 
4022
 
 
4023
static gboolean
 
4024
empathy_call_window_video_button_press_cb (GtkWidget *video_preview,
 
4025
  GdkEventButton *event, EmpathyCallWindow *window)
 
4026
{
 
4027
  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
 
4028
    {
 
4029
      empathy_call_window_video_menu_popup (window, event->button);
 
4030
      return TRUE;
 
4031
    }
 
4032
 
 
4033
  return FALSE;
 
4034
}
 
4035
 
 
4036
static gboolean
 
4037
empathy_call_window_key_press_cb (GtkWidget *video_output,
 
4038
  GdkEventKey *event, EmpathyCallWindow *window)
 
4039
{
 
4040
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
4041
 
 
4042
  if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
 
4043
    {
 
4044
      /* Since we are in fullscreen mode, toggling will bring us back to
 
4045
         normal mode. */
 
4046
      empathy_call_window_fullscreen_toggle (window);
 
4047
      return TRUE;
 
4048
    }
 
4049
 
 
4050
  return FALSE;
 
4051
}
 
4052
 
 
4053
static gboolean
 
4054
empathy_call_window_video_output_motion_notify (GtkWidget *widget,
 
4055
    GdkEventMotion *event, EmpathyCallWindow *window)
 
4056
{
 
4057
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
4058
 
 
4059
  if (priv->is_fullscreen)
 
4060
    {
 
4061
      empathy_call_window_fullscreen_show_popup (priv->fullscreen);
 
4062
 
 
4063
      /* Show the bottom toolbar */
 
4064
      empathy_call_window_motion_notify_cb (NULL, NULL, window);
 
4065
      return TRUE;
 
4066
    }
 
4067
  return FALSE;
 
4068
}
 
4069
 
 
4070
static void
 
4071
empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
 
4072
  guint button)
 
4073
{
 
4074
  GtkWidget *menu;
 
4075
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
4076
 
 
4077
  menu = gtk_ui_manager_get_widget (priv->ui_manager,
 
4078
            "/video-popup");
 
4079
  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
 
4080
      button, gtk_get_current_event_time ());
 
4081
  gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
 
4082
}
 
4083
 
 
4084
static void
 
4085
empathy_call_window_status_message (EmpathyCallWindow *self,
 
4086
  gchar *message)
 
4087
{
 
4088
  gtk_label_set_label (GTK_LABEL (self->priv->status_label), message);
 
4089
}
 
4090
 
 
4091
GtkUIManager *
 
4092
empathy_call_window_get_ui_manager (EmpathyCallWindow *window)
 
4093
{
 
4094
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
4095
 
 
4096
  return priv->ui_manager;
 
4097
}
 
4098
 
 
4099
EmpathyGstAudioSrc *
 
4100
empathy_call_window_get_audio_src (EmpathyCallWindow *window)
 
4101
{
 
4102
  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
4103
 
 
4104
  return (EmpathyGstAudioSrc *) priv->audio_input;
 
4105
}
 
4106
 
 
4107
EmpathyGstVideoSrc *
 
4108
empathy_call_window_get_video_src (EmpathyCallWindow *self)
 
4109
{
 
4110
  return EMPATHY_GST_VIDEO_SRC (self->priv->video_input);
 
4111
}
 
4112
 
 
4113
void
 
4114
empathy_call_window_change_webcam (EmpathyCallWindow *self,
 
4115
    const gchar *device)
 
4116
{
 
4117
  EmpathyGstVideoSrc *video;
 
4118
  gboolean running;
 
4119
 
 
4120
  /* Restart the camera only if it's already running */
 
4121
  running = (self->priv->video_preview != NULL);
 
4122
  video = empathy_call_window_get_video_src (self);
 
4123
 
 
4124
  if (running)
 
4125
    empathy_call_window_play_camera (self, FALSE);
 
4126
 
 
4127
  empathy_video_src_change_device (video, device);
 
4128
 
 
4129
  if (running)
 
4130
    empathy_call_window_play_camera (self, TRUE);
 
4131
}