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