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