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