]> git.0d.be Git - empathy.git/blob - src/empathy-video-widget.c
Move should_create_salut_account to local-xmpp-assistant-widget
[empathy.git] / src / empathy-video-widget.c
1 /*
2  * empathy-gst-gtk-widget.c - Source for EmpathyVideoWidget
3  * Copyright (C) 2008 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 <gdk/gdkx.h>
27 #include <gst/interfaces/xoverlay.h>
28 #include <gst/farsight/fs-element-added-notifier.h>
29
30 #include "empathy-video-widget.h"
31
32 G_DEFINE_TYPE(EmpathyVideoWidget, empathy_video_widget,
33   GTK_TYPE_DRAWING_AREA)
34
35 static void empathy_video_widget_element_added_cb (
36   FsElementAddedNotifier *notifier, GstBin *bin, GstElement *element,
37   EmpathyVideoWidget *self);
38
39 static void empathy_video_widget_sync_message_cb (
40   GstBus *bus, GstMessage *message, EmpathyVideoWidget *self);
41
42 /* signal enum */
43 #if 0
44 enum
45 {
46     LAST_SIGNAL
47 };
48
49 static guint signals[LAST_SIGNAL] = {0};
50 #endif
51
52 enum {
53   PROP_GST_ELEMENT = 1,
54   PROP_GST_BUS,
55   PROP_MIN_WIDTH,
56   PROP_MIN_HEIGHT,
57   PROP_SYNC,
58   PROP_ASYNC,
59   PROP_FLIP_VIDEO,
60 };
61
62 /* private structure */
63 typedef struct _EmpathyVideoWidgetPriv EmpathyVideoWidgetPriv;
64
65 struct _EmpathyVideoWidgetPriv
66 {
67   gboolean dispose_has_run;
68   GstBus *bus;
69   GstElement *videosink;
70   GstPad *sink_pad;
71   GstElement *overlay;
72   GstElement *flip;
73   FsElementAddedNotifier *notifier;
74   gint min_width;
75   gint min_height;
76   gboolean sync;
77   gboolean async;
78   gboolean flip_video;
79   guintptr xid;
80
81   GMutex *lock;
82 };
83
84 #define GET_PRIV(o)     (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
85   EMPATHY_TYPE_VIDEO_WIDGET, EmpathyVideoWidgetPriv))
86
87 static void
88 empathy_video_widget_init (EmpathyVideoWidget *obj)
89 {
90   EmpathyVideoWidgetPriv *priv = GET_PRIV (obj);
91   GdkRGBA black;
92
93   priv->lock = g_mutex_new ();
94
95   priv->notifier = fs_element_added_notifier_new ();
96   g_signal_connect (priv->notifier, "element-added",
97     G_CALLBACK (empathy_video_widget_element_added_cb),
98     obj);
99
100   if (gdk_rgba_parse (&black, "Black"))
101     gtk_widget_override_background_color (GTK_WIDGET (obj), 0, &black);
102
103   gtk_widget_set_double_buffered (GTK_WIDGET (obj), FALSE);
104 }
105
106 static void
107 empathy_video_widget_realized (GtkWidget *widget, gpointer user_data)
108 {
109   EmpathyVideoWidgetPriv *priv = GET_PRIV (widget);
110
111   /* requesting the XID forces the GdkWindow to be native in GTK+ 2.18
112    * onwards, requesting the native window in a thread causes a BadWindowID,
113    * so we need to request it now. We could call gdk_window_ensure_native(),
114    * but that would mean we require GTK+ 2.18, so instead we call this */
115   priv->xid = GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (widget)));
116 }
117
118 static void
119 empathy_video_widget_constructed (GObject *object)
120 {
121   EmpathyVideoWidgetPriv *priv = GET_PRIV (object);
122   GstElement *colorspace, *videoscale, *sink;
123   GstPad *pad;
124
125   g_signal_connect (object, "realize",
126       G_CALLBACK (empathy_video_widget_realized), NULL);
127
128   priv->videosink = gst_bin_new (NULL);
129
130   gst_object_ref (priv->videosink);
131   gst_object_sink (priv->videosink);
132
133   priv->sink_pad = gst_element_get_static_pad (priv->videosink, "sink");
134
135   sink = gst_element_factory_make ("gconfvideosink", NULL);
136   g_assert (sink != NULL);
137
138   videoscale = gst_element_factory_make ("videoscale", NULL);
139   g_assert (videoscale != NULL);
140
141   g_object_set (videoscale, "qos", FALSE, NULL);
142
143   colorspace = gst_element_factory_make ("ffmpegcolorspace", NULL);
144   g_assert (colorspace != NULL);
145
146   g_object_set (colorspace, "qos", FALSE, NULL);
147
148   priv->flip = gst_element_factory_make ("videoflip", NULL);
149   g_assert (priv->flip != NULL);
150
151   gst_bin_add_many (GST_BIN (priv->videosink), colorspace, videoscale,
152     priv->flip, sink, NULL);
153
154   if (!gst_element_link (colorspace, videoscale))
155     g_error ("Failed to link ffmpegcolorspace and videoscale");
156
157   if (!gst_element_link (videoscale, priv->flip))
158     g_error ("Failed to link videoscale and videoflip");
159
160   if (!gst_element_link (priv->flip, sink))
161     g_error ("Failed to link videoflip and gconfvideosink");
162
163   pad = gst_element_get_static_pad (colorspace, "sink");
164   g_assert (pad != NULL);
165
166   priv->sink_pad = gst_ghost_pad_new ("sink", pad);
167   if (!gst_element_add_pad  (priv->videosink, priv->sink_pad))
168     g_error ("Couldn't add sink ghostpad to the bin");
169
170   gst_object_unref (pad);
171
172   fs_element_added_notifier_add (priv->notifier, GST_BIN (priv->videosink));
173   gst_bus_enable_sync_message_emission (priv->bus);
174
175   g_signal_connect (priv->bus, "sync-message",
176     G_CALLBACK (empathy_video_widget_sync_message_cb), object);
177
178   gtk_widget_set_size_request (GTK_WIDGET (object), priv->min_width,
179     priv->min_height);
180 }
181
182 static void empathy_video_widget_dispose (GObject *object);
183 static void empathy_video_widget_finalize (GObject *object);
184
185
186 static gboolean empathy_video_widget_draw (GtkWidget *widget,
187   cairo_t *cr);
188
189 static void
190 empathy_video_widget_element_set_sink_properties (EmpathyVideoWidget *self);
191
192 static void
193 empathy_video_widget_set_property (GObject *object,
194   guint property_id, const GValue *value, GParamSpec *pspec)
195 {
196   EmpathyVideoWidgetPriv *priv = GET_PRIV (object);
197
198   switch (property_id)
199     {
200       case PROP_GST_BUS:
201         priv->bus = g_value_dup_object (value);
202         break;
203       case PROP_MIN_WIDTH:
204         priv->min_width = g_value_get_int (value);
205         break;
206       case PROP_MIN_HEIGHT:
207         priv->min_height = g_value_get_int (value);
208         break;
209       case PROP_SYNC:
210         priv->sync = g_value_get_boolean (value);
211         empathy_video_widget_element_set_sink_properties (
212           EMPATHY_VIDEO_WIDGET (object));
213         break;
214       case PROP_ASYNC:
215         priv->async = g_value_get_boolean (value);
216         empathy_video_widget_element_set_sink_properties (
217           EMPATHY_VIDEO_WIDGET (object));
218         break;
219       case PROP_FLIP_VIDEO:
220         priv->flip_video = g_value_get_boolean (value);
221         gst_util_set_object_arg (G_OBJECT (priv->flip), "method",
222             priv->flip_video ? "horizontal-flip" : "none");
223         break;
224       default:
225         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
226     }
227 }
228
229 static void
230 empathy_video_widget_get_property (GObject *object,
231   guint property_id, GValue *value, GParamSpec *pspec)
232 {
233   EmpathyVideoWidgetPriv *priv = GET_PRIV (object);
234
235   switch (property_id)
236     {
237       case PROP_GST_ELEMENT:
238         g_value_set_object (value, priv->videosink);
239         break;
240       case PROP_GST_BUS:
241         g_value_set_object (value, priv->bus);
242         break;
243       case PROP_MIN_WIDTH:
244         g_value_set_int (value, priv->min_width);
245         break;
246       case PROP_MIN_HEIGHT:
247         g_value_set_int (value, priv->min_height);
248         break;
249       case PROP_SYNC:
250         g_value_set_boolean (value, priv->sync);
251         break;
252       case PROP_ASYNC:
253         g_value_set_boolean (value, priv->async);
254         break;
255       case PROP_FLIP_VIDEO:
256         g_value_set_boolean (value, priv->flip_video);
257         break;
258       default:
259         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
260     }
261 }
262
263
264 static void
265 empathy_video_widget_class_init (
266   EmpathyVideoWidgetClass *empathy_video_widget_class)
267 {
268   GObjectClass *object_class = G_OBJECT_CLASS (empathy_video_widget_class);
269   GtkWidgetClass *widget_class =
270     GTK_WIDGET_CLASS (empathy_video_widget_class);
271   GParamSpec *param_spec;
272
273   g_type_class_add_private (empathy_video_widget_class,
274     sizeof (EmpathyVideoWidgetPriv));
275
276   object_class->dispose = empathy_video_widget_dispose;
277   object_class->finalize = empathy_video_widget_finalize;
278   object_class->constructed = empathy_video_widget_constructed;
279
280   object_class->set_property = empathy_video_widget_set_property;
281   object_class->get_property = empathy_video_widget_get_property;
282
283   widget_class->draw = empathy_video_widget_draw;
284
285   param_spec = g_param_spec_object ("gst-element",
286     "gst-element", "The underlaying gstreamer element",
287     GST_TYPE_ELEMENT,
288     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
289   g_object_class_install_property (object_class, PROP_GST_ELEMENT, param_spec);
290
291   param_spec = g_param_spec_object ("gst-bus",
292     "gst-bus",
293     "The toplevel bus from the pipeline in which this bin will be added",
294     GST_TYPE_BUS,
295     G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
296   g_object_class_install_property (object_class, PROP_GST_BUS, param_spec);
297
298   param_spec = g_param_spec_int ("min-width",
299     "min-width",
300     "Minimal width of the widget",
301     0, G_MAXINT, EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH,
302     G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
303   g_object_class_install_property (object_class, PROP_MIN_WIDTH, param_spec);
304
305   param_spec = g_param_spec_int ("min-height",
306     "min-height",
307     "Minimal height of the widget",
308     0, G_MAXINT, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT,
309     G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
310   g_object_class_install_property (object_class, PROP_MIN_HEIGHT, param_spec);
311
312   param_spec = g_param_spec_boolean ("sync",
313     "sync",
314     "Whether the underlying sink should be sync or not",
315     TRUE,
316     G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
317   g_object_class_install_property (object_class, PROP_SYNC, param_spec);
318
319   param_spec = g_param_spec_boolean ("async",
320     "async",
321     "Whether the underlying sink should be async or not",
322     TRUE,
323     G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
324   g_object_class_install_property (object_class, PROP_ASYNC, param_spec);
325
326   param_spec = g_param_spec_boolean ("flip-video",
327     "flip video",
328     "Whether the video should be flipped horizontally or not",
329     FALSE,
330     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
331   g_object_class_install_property (object_class, PROP_FLIP_VIDEO, param_spec);
332 }
333
334 void
335 empathy_video_widget_dispose (GObject *object)
336 {
337   EmpathyVideoWidget *self = EMPATHY_VIDEO_WIDGET (object);
338   EmpathyVideoWidgetPriv *priv = GET_PRIV (self);
339
340   if (priv->dispose_has_run)
341     return;
342
343   priv->dispose_has_run = TRUE;
344
345   g_signal_handlers_disconnect_by_func (priv->bus,
346     empathy_video_widget_sync_message_cb, object);
347
348   if (priv->bus != NULL)
349     g_object_unref (priv->bus);
350
351   priv->bus = NULL;
352
353   if (priv->videosink != NULL)
354     g_object_unref (priv->videosink);
355
356   priv->videosink = NULL;
357
358
359   /* release any references held by the object here */
360
361   if (G_OBJECT_CLASS (empathy_video_widget_parent_class)->dispose)
362     G_OBJECT_CLASS (empathy_video_widget_parent_class)->dispose (object);
363 }
364
365 void
366 empathy_video_widget_finalize (GObject *object)
367 {
368   EmpathyVideoWidget *self = EMPATHY_VIDEO_WIDGET (object);
369   EmpathyVideoWidgetPriv *priv = GET_PRIV (self);
370
371   /* free any data held directly by the object here */
372   g_mutex_free (priv->lock);
373
374   G_OBJECT_CLASS (empathy_video_widget_parent_class)->finalize (object);
375 }
376
377
378 static void
379 empathy_video_widget_element_set_sink_properties_unlocked (
380   EmpathyVideoWidget *self)
381 {
382   EmpathyVideoWidgetPriv *priv = GET_PRIV (self);
383
384   if (priv->overlay == NULL)
385     return;
386
387   if (g_object_class_find_property (G_OBJECT_GET_CLASS (priv->overlay),
388       "force-aspect-ratio"))
389     g_object_set (G_OBJECT (priv->overlay), "force-aspect-ratio", TRUE, NULL);
390
391   if (g_object_class_find_property (
392       G_OBJECT_GET_CLASS (priv->overlay), "sync"))
393     g_object_set (G_OBJECT (priv->overlay), "sync", priv->sync, NULL);
394
395   if (g_object_class_find_property (G_OBJECT_GET_CLASS (priv->overlay),
396       "async"))
397     g_object_set (G_OBJECT (priv->overlay), "async", priv->async, NULL);
398 }
399
400 static void
401 empathy_video_widget_element_set_sink_properties (EmpathyVideoWidget *self)
402 {
403   EmpathyVideoWidgetPriv *priv = GET_PRIV (self);
404
405   g_mutex_lock (priv->lock);
406   empathy_video_widget_element_set_sink_properties_unlocked (self);
407   g_mutex_unlock (priv->lock);
408 }
409
410 static void
411 empathy_video_widget_element_added_cb (FsElementAddedNotifier *notifier,
412   GstBin *bin, GstElement *element, EmpathyVideoWidget *self)
413 {
414   EmpathyVideoWidgetPriv *priv = GET_PRIV (self);
415
416   /* We assume the overlay is the sink */
417   g_mutex_lock (priv->lock);
418   if (priv->overlay == NULL && GST_IS_X_OVERLAY (element))
419     {
420       priv->overlay = element;
421       g_object_add_weak_pointer (G_OBJECT (element),
422         (gpointer) &priv->overlay);
423       empathy_video_widget_element_set_sink_properties_unlocked (self);
424       gst_x_overlay_expose (GST_X_OVERLAY (priv->overlay));
425     }
426   g_mutex_unlock (priv->lock);
427 }
428
429 static void
430 empathy_video_widget_sync_message_cb (GstBus *bus, GstMessage *message,
431   EmpathyVideoWidget *self)
432 {
433   EmpathyVideoWidgetPriv *priv = GET_PRIV (self);
434   const GstStructure *s;
435
436   if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
437     return;
438
439   if (GST_MESSAGE_SRC (message) != (GstObject *) priv->overlay)
440     return;
441
442   s = gst_message_get_structure (message);
443
444   if (gst_structure_has_name (s, "prepare-xwindow-id"))
445     {
446       g_assert (priv->xid != 0);
447       gst_x_overlay_set_window_handle (GST_X_OVERLAY (priv->overlay),
448           priv->xid);
449     }
450 }
451
452 static gboolean
453 empathy_video_widget_draw (GtkWidget *widget,
454     cairo_t *cr)
455 {
456   EmpathyVideoWidget *self = EMPATHY_VIDEO_WIDGET (widget);
457   EmpathyVideoWidgetPriv *priv = GET_PRIV (self);
458   GtkAllocation allocation;
459
460   if (priv->overlay == NULL)
461     {
462       gtk_widget_get_allocation (widget, &allocation);
463
464       gtk_render_frame (gtk_widget_get_style_context (widget), cr,
465           0, 0,
466           gtk_widget_get_allocated_width (widget),
467           gtk_widget_get_allocated_height (widget));
468       return TRUE;
469     }
470
471   gst_x_overlay_set_xwindow_id (GST_X_OVERLAY (priv->overlay),
472     GDK_WINDOW_XID (gtk_widget_get_window (widget)));
473
474   gst_x_overlay_expose (GST_X_OVERLAY (priv->overlay));
475
476   return TRUE;
477 }
478
479 GtkWidget *
480 empathy_video_widget_new_with_size (GstBus *bus, gint width, gint height)
481 {
482   g_return_val_if_fail (bus != NULL, NULL);
483
484   return GTK_WIDGET (g_object_new (EMPATHY_TYPE_VIDEO_WIDGET,
485     "gst-bus", bus,
486     "min-width", width,
487     "min-height", height,
488     NULL));
489 }
490
491 GtkWidget *
492 empathy_video_widget_new (GstBus *bus)
493 {
494   g_return_val_if_fail (bus != NULL, NULL);
495
496   return GTK_WIDGET (g_object_new (EMPATHY_TYPE_VIDEO_WIDGET,
497     "gst-bus", bus,
498     NULL));
499 }
500
501 GstPad *
502 empathy_video_widget_get_sink (EmpathyVideoWidget *widget)
503 {
504   EmpathyVideoWidgetPriv *priv = GET_PRIV (widget);
505
506   return priv->sink_pad;
507 }
508
509 GstElement *
510 empathy_video_widget_get_element (EmpathyVideoWidget *widget)
511 {
512   EmpathyVideoWidgetPriv *priv = GET_PRIV (widget);
513
514   return priv->videosink;
515 }