x(v)imagesink: make it more clean that "synchronous" props are not for avsync
[platform/upstream/gstreamer.git] / sys / ximage / ximagesink.c
1 /* GStreamer
2  * Copyright (C) <2005> Julien Moutte <julien@moutte.net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /**
21  * SECTION:element-ximagesink
22  *
23  * XImageSink renders video frames to a drawable (XWindow) on a local or remote
24  * display. This element can receive a Window ID from the application through
25  * the XOverlay interface and will then render video frames in this drawable.
26  * If no Window ID was provided by the application, the element will create its
27  * own internal window and render into it.
28  *
29  * <refsect2>
30  * <title>Scaling</title>
31  * <para>
32  * As standard XImage rendering to a drawable is not scaled, XImageSink will use
33  * reverse caps negotiation to try to get scaled video frames for the drawable.
34  * This is accomplished by asking the peer pad if it accepts some different caps
35  * which in most cases implies that there is a scaling element in the pipeline,
36  * or that an element generating the video frames can generate them with a 
37  * different geometry. This mechanism is handled during buffer allocations, for
38  * each allocation request the video sink will check the drawable geometry, look
39  * at the #GstXImageSink:force-aspect-ratio property, calculate the geometry of
40  * desired video frames and then check that the peer pad accept those new caps.
41  * If it does it will then allocate a buffer in video memory with this new
42  * geometry and return it with the new caps.
43  * </para>
44  * </refsect2>
45  * <refsect2>
46  * <title>Events</title>
47  * <para>
48  * XImageSink creates a thread to handle events coming from the drawable. There
49  * are several kind of events that can be grouped in 2 big categories: input 
50  * events and window state related events. Input events will be translated to
51  * navigation events and pushed upstream for other elements to react on them.
52  * This includes events such as pointer moves, key press/release, clicks etc...
53  * Other events are used to handle the drawable appearance even when the data
54  * is not flowing (GST_STATE_PAUSED). That means that even when the element is
55  * paused, it will receive expose events from the drawable and draw the latest
56  * frame with correct borders/aspect-ratio.
57  * </para>
58  * </refsect2>
59  * <refsect2>
60  * <title>Pixel aspect ratio</title>
61  * <para>
62  * When changing state to GST_STATE_READY, XImageSink will open a connection to
63  * the display specified in the #GstXImageSink:display property or the default
64  * display if nothing specified. Once this connection is open it will inspect 
65  * the display configuration including the physical display geometry and 
66  * then calculate the pixel aspect ratio. When caps negotiation will occur, the
67  * video sink will set the calculated pixel aspect ratio on the caps to make 
68  * sure that incoming video frames will have the correct pixel aspect ratio for
69  * this display. Sometimes the calculated pixel aspect ratio can be wrong, it is
70  * then possible to enforce a specific pixel aspect ratio using the
71  * #GstXImageSink:pixel-aspect-ratio property.
72  * </para>
73  * </refsect2>
74  * <refsect2>
75  * <title>Examples</title>
76  * |[
77  * gst-launch -v videotestsrc ! queue ! ximagesink
78  * ]| A pipeline to test reverse negotiation. When the test video signal appears
79  * you can resize the window and see that scaled buffers of the desired size are
80  * going to arrive with a short delay. This illustrates how buffers of desired
81  * size are allocated along the way. If you take away the queue, scaling will
82  * happen almost immediately.
83  * |[
84  * gst-launch -v videotestsrc ! navigationtest ! ffmpegcolorspace ! ximagesink
85  * ]| A pipeline to test navigation events.
86  * While moving the mouse pointer over the test signal you will see a black box
87  * following the mouse pointer. If you press the mouse button somewhere on the 
88  * video and release it somewhere else a green box will appear where you pressed
89  * the button and a red one where you released it. (The navigationtest element
90  * is part of gst-plugins-good.)
91  * |[
92  * gst-launch -v videotestsrc ! video/x-raw-rgb, pixel-aspect-ratio=(fraction)4/3 ! videoscale ! ximagesink
93  * ]| This is faking a 4/3 pixel aspect ratio caps on video frames produced by
94  * videotestsrc, in most cases the pixel aspect ratio of the display will be
95  * 1/1. This means that videoscale will have to do the scaling to convert 
96  * incoming frames to a size that will match the display pixel aspect ratio
97  * (from 320x240 to 320x180 in this case). Note that you might have to escape 
98  * some characters for your shell like '\(fraction\)'.
99  * </refsect2>
100  */
101
102 #ifdef HAVE_CONFIG_H
103 #include "config.h"
104 #endif
105
106 /* Our interfaces */
107 #include <gst/interfaces/navigation.h>
108 #include <gst/interfaces/xoverlay.h>
109
110 /* Object header */
111 #include "ximagesink.h"
112
113 /* Debugging category */
114 #include <gst/gstinfo.h>
115
116 GST_DEBUG_CATEGORY_EXTERN (gst_debug_ximagesink);
117 #define GST_CAT_DEFAULT gst_debug_ximagesink
118
119 typedef struct
120 {
121   unsigned long flags;
122   unsigned long functions;
123   unsigned long decorations;
124   long input_mode;
125   unsigned long status;
126 }
127 MotifWmHints, MwmHints;
128
129 #define MWM_HINTS_DECORATIONS   (1L << 1)
130
131 static void gst_ximagesink_reset (GstXImageSink * ximagesink);
132 static void gst_ximagesink_ximage_destroy (GstXImageSink * ximagesink,
133     GstXImageBuffer * ximage);
134 static void gst_ximagesink_xwindow_update_geometry (GstXImageSink * ximagesink);
135 static void gst_ximagesink_expose (GstXOverlay * overlay);
136
137 static GstStaticPadTemplate gst_ximagesink_sink_template_factory =
138 GST_STATIC_PAD_TEMPLATE ("sink",
139     GST_PAD_SINK,
140     GST_PAD_ALWAYS,
141     GST_STATIC_CAPS ("video/x-raw-rgb, "
142         "framerate = (fraction) [ 0, MAX ], "
143         "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
144     );
145
146 enum
147 {
148   PROP_0,
149   PROP_DISPLAY,
150   PROP_SYNCHRONOUS,
151   PROP_PIXEL_ASPECT_RATIO,
152   PROP_FORCE_ASPECT_RATIO,
153   PROP_HANDLE_EVENTS,
154   PROP_HANDLE_EXPOSE,
155   PROP_WINDOW_WIDTH,
156   PROP_WINDOW_HEIGHT
157 };
158
159 static GstVideoSinkClass *parent_class = NULL;
160
161 /* ============================================================= */
162 /*                                                               */
163 /*                       Private Methods                         */
164 /*                                                               */
165 /* ============================================================= */
166
167 /* ximage buffers */
168
169 static GstBufferClass *ximage_buffer_parent_class = NULL;
170
171 #define GST_TYPE_XIMAGE_BUFFER (gst_ximage_buffer_get_type())
172
173 #define GST_IS_XIMAGE_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_XIMAGE_BUFFER))
174 #define GST_XIMAGE_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_XIMAGE_BUFFER, GstXImageBuffer))
175 #define GST_XIMAGE_BUFFER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_XIMAGE_BUFFER, GstXImageBufferClass))
176
177 /* So some words about GstMiniObject, this is pretty messy...
178    GstMiniObject does not use the standard finalizing of GObjects, you are 
179    supposed to call gst_buffer_unref that's going to call gst_mini_objec_unref
180    which will handle its own refcount system and call gst_mini_object_free.
181    gst_mini_object_free will call the class finalize method which is not the 
182    one from GObject, after calling this finalize method it will free the object
183    instance for you if the refcount is still 0 so you should not chain up */
184 static void
185 gst_ximage_buffer_finalize (GstXImageBuffer * ximage)
186 {
187   GstXImageSink *ximagesink = NULL;
188   gboolean recycled = FALSE;
189   gboolean running;
190
191   g_return_if_fail (ximage != NULL);
192
193   ximagesink = ximage->ximagesink;
194   if (G_UNLIKELY (ximagesink == NULL)) {
195     GST_WARNING_OBJECT (ximagesink, "no sink found");
196     goto beach;
197   }
198
199   GST_OBJECT_LOCK (ximagesink);
200   running = ximagesink->running;
201   GST_OBJECT_UNLOCK (ximagesink);
202
203   if (running == FALSE) {
204     /* If the sink is shutting down, need to clear the image */
205     GST_DEBUG_OBJECT (ximagesink,
206         "destroy image %p because the sink is shutting down", ximage);
207     gst_ximagesink_ximage_destroy (ximagesink, ximage);
208   } else if ((ximage->width != GST_VIDEO_SINK_WIDTH (ximagesink)) ||
209       (ximage->height != GST_VIDEO_SINK_HEIGHT (ximagesink))) {
210     /* If our geometry changed we can't reuse that image. */
211     GST_DEBUG_OBJECT (ximagesink,
212         "destroy image %p as its size changed %dx%d vs current %dx%d",
213         ximage, ximage->width, ximage->height,
214         GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
215     gst_ximagesink_ximage_destroy (ximagesink, ximage);
216   } else {
217     /* In that case we can reuse the image and add it to our image pool. */
218     GST_LOG_OBJECT (ximagesink, "recycling image %p in pool", ximage);
219     /* need to increment the refcount again to recycle */
220     gst_buffer_ref (GST_BUFFER_CAST (ximage));
221     g_mutex_lock (ximagesink->pool_lock);
222     ximagesink->buffer_pool = g_slist_prepend (ximagesink->buffer_pool, ximage);
223     g_mutex_unlock (ximagesink->pool_lock);
224     recycled = TRUE;
225   }
226
227   if (!recycled)
228     GST_MINI_OBJECT_CLASS (ximage_buffer_parent_class)->finalize
229         (GST_MINI_OBJECT (ximage));
230
231 beach:
232   return;
233 }
234
235 static void
236 gst_ximage_buffer_free (GstXImageBuffer * ximage)
237 {
238   /* make sure it is not recycled */
239   ximage->width = -1;
240   ximage->height = -1;
241   gst_buffer_unref (GST_BUFFER_CAST (ximage));
242 }
243
244 static void
245 gst_ximage_buffer_init (GstXImageBuffer * ximage_buffer, gpointer g_class)
246 {
247 #ifdef HAVE_XSHM
248   ximage_buffer->SHMInfo.shmaddr = ((void *) -1);
249   ximage_buffer->SHMInfo.shmid = -1;
250 #endif
251 }
252
253 static void
254 gst_ximage_buffer_class_init (gpointer g_class, gpointer class_data)
255 {
256   GstMiniObjectClass *mini_object_class = GST_MINI_OBJECT_CLASS (g_class);
257
258   ximage_buffer_parent_class = g_type_class_peek_parent (g_class);
259
260   mini_object_class->finalize = (GstMiniObjectFinalizeFunction)
261       gst_ximage_buffer_finalize;
262 }
263
264 static GType
265 gst_ximage_buffer_get_type (void)
266 {
267   static GType _gst_ximage_buffer_type;
268
269   if (G_UNLIKELY (_gst_ximage_buffer_type == 0)) {
270     static const GTypeInfo ximage_buffer_info = {
271       sizeof (GstBufferClass),
272       NULL,
273       NULL,
274       gst_ximage_buffer_class_init,
275       NULL,
276       NULL,
277       sizeof (GstXImageBuffer),
278       0,
279       (GInstanceInitFunc) gst_ximage_buffer_init,
280       NULL
281     };
282     _gst_ximage_buffer_type = g_type_register_static (GST_TYPE_BUFFER,
283         "GstXImageBuffer", &ximage_buffer_info, 0);
284   }
285   return _gst_ximage_buffer_type;
286 }
287
288 /* X11 stuff */
289
290 static gboolean error_caught = FALSE;
291
292 static int
293 gst_ximagesink_handle_xerror (Display * display, XErrorEvent * xevent)
294 {
295   char error_msg[1024];
296
297   XGetErrorText (display, xevent->error_code, error_msg, 1024);
298   GST_DEBUG ("ximagesink triggered an XError. error: %s", error_msg);
299   error_caught = TRUE;
300   return 0;
301 }
302
303 #ifdef HAVE_XSHM                /* Check that XShm calls actually work */
304
305 static gboolean
306 gst_ximagesink_check_xshm_calls (GstXImageSink * ximagesink,
307     GstXContext * xcontext)
308 {
309   XImage *ximage;
310   XShmSegmentInfo SHMInfo;
311   size_t size;
312   int (*handler) (Display *, XErrorEvent *);
313   gboolean result = FALSE;
314   gboolean did_attach = FALSE;
315
316   g_return_val_if_fail (xcontext != NULL, FALSE);
317
318   /* Sync to ensure any older errors are already processed */
319   XSync (xcontext->disp, FALSE);
320
321   /* Set defaults so we don't free these later unnecessarily */
322   SHMInfo.shmaddr = ((void *) -1);
323   SHMInfo.shmid = -1;
324
325   /* Setting an error handler to catch failure */
326   error_caught = FALSE;
327   handler = XSetErrorHandler (gst_ximagesink_handle_xerror);
328
329   /* Trying to create a 1x1 ximage */
330   GST_DEBUG ("XShmCreateImage of 1x1");
331
332   ximage = XShmCreateImage (xcontext->disp, xcontext->visual,
333       xcontext->depth, ZPixmap, NULL, &SHMInfo, 1, 1);
334
335   /* Might cause an error, sync to ensure it is noticed */
336   XSync (xcontext->disp, FALSE);
337   if (!ximage || error_caught) {
338     GST_WARNING ("could not XShmCreateImage a 1x1 image");
339     goto beach;
340   }
341   size = ximage->height * ximage->bytes_per_line;
342
343   SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777);
344   if (SHMInfo.shmid == -1) {
345     GST_WARNING ("could not get shared memory of %" G_GSIZE_FORMAT " bytes",
346         size);
347     goto beach;
348   }
349
350   SHMInfo.shmaddr = shmat (SHMInfo.shmid, NULL, 0);
351   if (SHMInfo.shmaddr == ((void *) -1)) {
352     GST_WARNING ("Failed to shmat: %s", g_strerror (errno));
353     /* Clean up shm seg */
354     shmctl (SHMInfo.shmid, IPC_RMID, NULL);
355     goto beach;
356   }
357
358   ximage->data = SHMInfo.shmaddr;
359   SHMInfo.readOnly = FALSE;
360
361   if (XShmAttach (xcontext->disp, &SHMInfo) == 0) {
362     GST_WARNING ("Failed to XShmAttach");
363     /* Clean up shm seg */
364     shmctl (SHMInfo.shmid, IPC_RMID, NULL);
365     goto beach;
366   }
367
368   /* Sync to ensure we see any errors we caused */
369   XSync (xcontext->disp, FALSE);
370
371   /* Delete the shared memory segment as soon as everyone is attached. 
372    * This way, it will be deleted as soon as we detach later, and not
373    * leaked if we crash. */
374   shmctl (SHMInfo.shmid, IPC_RMID, NULL);
375
376   if (!error_caught) {
377     did_attach = TRUE;
378     /* store whether we succeeded in result */
379     result = TRUE;
380   }
381
382 beach:
383   /* Sync to ensure we swallow any errors we caused and reset error_caught */
384   XSync (xcontext->disp, FALSE);
385   error_caught = FALSE;
386   XSetErrorHandler (handler);
387
388   if (did_attach) {
389     XShmDetach (xcontext->disp, &SHMInfo);
390     XSync (xcontext->disp, FALSE);
391   }
392   if (SHMInfo.shmaddr != ((void *) -1))
393     shmdt (SHMInfo.shmaddr);
394   if (ximage)
395     XDestroyImage (ximage);
396   return result;
397 }
398 #endif /* HAVE_XSHM */
399
400 /* This function handles GstXImageBuffer creation depending on XShm availability */
401 static GstXImageBuffer *
402 gst_ximagesink_ximage_new (GstXImageSink * ximagesink, GstCaps * caps)
403 {
404   GstXImageBuffer *ximage = NULL;
405   GstStructure *structure = NULL;
406   gboolean succeeded = FALSE;
407   int (*handler) (Display *, XErrorEvent *);
408
409   g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
410
411   ximage = (GstXImageBuffer *) gst_mini_object_new (GST_TYPE_XIMAGE_BUFFER);
412
413   structure = gst_caps_get_structure (caps, 0);
414
415   if (!gst_structure_get_int (structure, "width", &ximage->width) ||
416       !gst_structure_get_int (structure, "height", &ximage->height)) {
417     GST_WARNING ("failed getting geometry from caps %" GST_PTR_FORMAT, caps);
418   }
419
420   GST_DEBUG_OBJECT (ximagesink, "creating image %p (%dx%d)", ximage,
421       ximage->width, ximage->height);
422
423   g_mutex_lock (ximagesink->x_lock);
424
425   /* Setting an error handler to catch failure */
426   error_caught = FALSE;
427   handler = XSetErrorHandler (gst_ximagesink_handle_xerror);
428
429 #ifdef HAVE_XSHM
430   if (ximagesink->xcontext->use_xshm) {
431     ximage->ximage = XShmCreateImage (ximagesink->xcontext->disp,
432         ximagesink->xcontext->visual,
433         ximagesink->xcontext->depth,
434         ZPixmap, NULL, &ximage->SHMInfo, ximage->width, ximage->height);
435     if (!ximage->ximage || error_caught) {
436       g_mutex_unlock (ximagesink->x_lock);
437
438       /* Reset error flag */
439       error_caught = FALSE;
440
441       /* Push a warning */
442       GST_ELEMENT_WARNING (ximagesink, RESOURCE, WRITE,
443           ("Failed to create output image buffer of %dx%d pixels",
444               ximage->width, ximage->height),
445           ("could not XShmCreateImage a %dx%d image",
446               ximage->width, ximage->height));
447
448       /* Retry without XShm */
449       ximagesink->xcontext->use_xshm = FALSE;
450
451       /* Hold X mutex again to try without XShm */
452       g_mutex_lock (ximagesink->x_lock);
453       goto no_xshm;
454     }
455
456     /* we have to use the returned bytes_per_line for our shm size */
457     ximage->size = ximage->ximage->bytes_per_line * ximage->ximage->height;
458     GST_LOG_OBJECT (ximagesink,
459         "XShm image size is %" G_GSIZE_FORMAT ", width %d, stride %d",
460         ximage->size, ximage->width, ximage->ximage->bytes_per_line);
461
462     ximage->SHMInfo.shmid = shmget (IPC_PRIVATE, ximage->size,
463         IPC_CREAT | 0777);
464     if (ximage->SHMInfo.shmid == -1) {
465       g_mutex_unlock (ximagesink->x_lock);
466       GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
467           ("Failed to create output image buffer of %dx%d pixels",
468               ximage->width, ximage->height),
469           ("could not get shared memory of %" G_GSIZE_FORMAT " bytes",
470               ximage->size));
471       goto beach;
472     }
473
474     ximage->SHMInfo.shmaddr = shmat (ximage->SHMInfo.shmid, NULL, 0);
475     if (ximage->SHMInfo.shmaddr == ((void *) -1)) {
476       g_mutex_unlock (ximagesink->x_lock);
477       GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
478           ("Failed to create output image buffer of %dx%d pixels",
479               ximage->width, ximage->height),
480           ("Failed to shmat: %s", g_strerror (errno)));
481       /* Clean up the shared memory segment */
482       shmctl (ximage->SHMInfo.shmid, IPC_RMID, NULL);
483       goto beach;
484     }
485
486     ximage->ximage->data = ximage->SHMInfo.shmaddr;
487     ximage->SHMInfo.readOnly = FALSE;
488
489     if (XShmAttach (ximagesink->xcontext->disp, &ximage->SHMInfo) == 0) {
490       /* Clean up shm seg */
491       shmctl (ximage->SHMInfo.shmid, IPC_RMID, NULL);
492
493       g_mutex_unlock (ximagesink->x_lock);
494       GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
495           ("Failed to create output image buffer of %dx%d pixels",
496               ximage->width, ximage->height), ("Failed to XShmAttach"));
497       goto beach;
498     }
499
500     XSync (ximagesink->xcontext->disp, FALSE);
501
502     /* Now that everyone has attached, we can delete the shared memory segment.
503      * This way, it will be deleted as soon as we detach later, and not
504      * leaked if we crash. */
505     shmctl (ximage->SHMInfo.shmid, IPC_RMID, NULL);
506
507   } else
508   no_xshm:
509 #endif /* HAVE_XSHM */
510   {
511     guint allocsize;
512
513     ximage->ximage = XCreateImage (ximagesink->xcontext->disp,
514         ximagesink->xcontext->visual,
515         ximagesink->xcontext->depth,
516         ZPixmap, 0, NULL,
517         ximage->width, ximage->height, ximagesink->xcontext->bpp, 0);
518     if (!ximage->ximage || error_caught) {
519       g_mutex_unlock (ximagesink->x_lock);
520       /* Reset error handler */
521       error_caught = FALSE;
522       XSetErrorHandler (handler);
523       /* Push an error */
524       GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
525           ("Failed to create output image buffer of %dx%d pixels",
526               ximage->width, ximage->height),
527           ("could not XCreateImage a %dx%d image",
528               ximage->width, ximage->height));
529       goto beach;
530     }
531
532     /* upstream will assume that rowstrides are multiples of 4, but this
533      * doesn't always seem to be the case with XCreateImage() */
534     if ((ximage->ximage->bytes_per_line % 4) != 0) {
535       GST_WARNING_OBJECT (ximagesink, "returned stride not a multiple of 4 as "
536           "usually assumed");
537     }
538
539     /* we have to use the returned bytes_per_line for our image size */
540     ximage->size = ximage->ximage->bytes_per_line * ximage->ximage->height;
541
542     /* alloc a bit more for unexpected strides to avoid crashes upstream.
543      * FIXME: if we get an unrounded stride, the image will be displayed
544      * distorted, since all upstream elements assume a rounded stride */
545     allocsize =
546         GST_ROUND_UP_4 (ximage->ximage->bytes_per_line) *
547         ximage->ximage->height;
548     ximage->ximage->data = g_malloc (allocsize);
549     GST_LOG_OBJECT (ximagesink,
550         "non-XShm image size is %" G_GSIZE_FORMAT " (alloced: %u), width %d, "
551         "stride %d", ximage->size, allocsize, ximage->width,
552         ximage->ximage->bytes_per_line);
553
554     XSync (ximagesink->xcontext->disp, FALSE);
555   }
556
557   /* Reset error handler */
558   error_caught = FALSE;
559   XSetErrorHandler (handler);
560
561   succeeded = TRUE;
562
563   GST_BUFFER_DATA (ximage) = (guchar *) ximage->ximage->data;
564   GST_BUFFER_SIZE (ximage) = ximage->size;
565
566   /* Keep a ref to our sink */
567   ximage->ximagesink = gst_object_ref (ximagesink);
568
569   g_mutex_unlock (ximagesink->x_lock);
570 beach:
571   if (!succeeded) {
572     gst_ximage_buffer_free (ximage);
573     ximage = NULL;
574   }
575
576   return ximage;
577 }
578
579 /* This function destroys a GstXImageBuffer handling XShm availability */
580 static void
581 gst_ximagesink_ximage_destroy (GstXImageSink * ximagesink,
582     GstXImageBuffer * ximage)
583 {
584   g_return_if_fail (ximage != NULL);
585   g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
586
587   /* Hold the object lock to ensure the XContext doesn't disappear */
588   GST_OBJECT_LOCK (ximagesink);
589
590   /* If the destroyed image is the current one we destroy our reference too */
591   if (ximagesink->cur_image == ximage) {
592     ximagesink->cur_image = NULL;
593   }
594
595   /* We might have some buffers destroyed after changing state to NULL */
596   if (!ximagesink->xcontext) {
597     GST_DEBUG_OBJECT (ximagesink, "Destroying XImage after XContext");
598 #ifdef HAVE_XSHM
599     if (ximage->SHMInfo.shmaddr != ((void *) -1)) {
600       shmdt (ximage->SHMInfo.shmaddr);
601     }
602 #endif
603     goto beach;
604   }
605
606   g_mutex_lock (ximagesink->x_lock);
607
608 #ifdef HAVE_XSHM
609   if (ximagesink->xcontext->use_xshm) {
610     if (ximage->SHMInfo.shmaddr != ((void *) -1)) {
611       XShmDetach (ximagesink->xcontext->disp, &ximage->SHMInfo);
612       XSync (ximagesink->xcontext->disp, 0);
613       shmdt (ximage->SHMInfo.shmaddr);
614     }
615     if (ximage->ximage)
616       XDestroyImage (ximage->ximage);
617
618   } else
619 #endif /* HAVE_XSHM */
620   {
621     if (ximage->ximage) {
622       XDestroyImage (ximage->ximage);
623     }
624   }
625
626   XSync (ximagesink->xcontext->disp, FALSE);
627
628   g_mutex_unlock (ximagesink->x_lock);
629
630 beach:
631   GST_OBJECT_UNLOCK (ximagesink);
632
633   if (ximage->ximagesink) {
634     /* Release the ref to our sink */
635     ximage->ximagesink = NULL;
636     gst_object_unref (ximagesink);
637   }
638
639   return;
640 }
641
642 /* We are called with the x_lock taken */
643 static void
644 gst_ximagesink_xwindow_draw_borders (GstXImageSink * ximagesink,
645     GstXWindow * xwindow, GstVideoRectangle rect)
646 {
647   g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
648   g_return_if_fail (xwindow != NULL);
649
650   XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
651       ximagesink->xcontext->black);
652
653   /* Left border */
654   if (rect.x > 0) {
655     XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
656         0, 0, rect.x, xwindow->height);
657   }
658
659   /* Right border */
660   if ((rect.x + rect.w) < xwindow->width) {
661     XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
662         rect.x + rect.w, 0, xwindow->width, xwindow->height);
663   }
664
665   /* Top border */
666   if (rect.y > 0) {
667     XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
668         0, 0, xwindow->width, rect.y);
669   }
670
671   /* Bottom border */
672   if ((rect.y + rect.h) < xwindow->height) {
673     XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
674         0, rect.y + rect.h, xwindow->width, xwindow->height);
675   }
676 }
677
678 /* This function puts a GstXImageBuffer on a GstXImageSink's window */
679 static gboolean
680 gst_ximagesink_ximage_put (GstXImageSink * ximagesink, GstXImageBuffer * ximage)
681 {
682   GstVideoRectangle src, dst, result;
683   gboolean draw_border = FALSE;
684
685   g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), FALSE);
686
687   /* We take the flow_lock. If expose is in there we don't want to run
688      concurrently from the data flow thread */
689   g_mutex_lock (ximagesink->flow_lock);
690
691   if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
692     g_mutex_unlock (ximagesink->flow_lock);
693     return FALSE;
694   }
695
696   /* Draw borders when displaying the first frame. After this
697      draw borders only on expose event or caps change (ximagesink->draw_border = TRUE). */
698   if (!ximagesink->cur_image || ximagesink->draw_border) {
699     draw_border = TRUE;
700   }
701
702   /* Store a reference to the last image we put, lose the previous one */
703   if (ximage && ximagesink->cur_image != ximage) {
704     if (ximagesink->cur_image) {
705       GST_LOG_OBJECT (ximagesink, "unreffing %p", ximagesink->cur_image);
706       gst_buffer_unref (GST_BUFFER_CAST (ximagesink->cur_image));
707     }
708     GST_LOG_OBJECT (ximagesink, "reffing %p as our current image", ximage);
709     ximagesink->cur_image =
710         GST_XIMAGE_BUFFER (gst_buffer_ref (GST_BUFFER_CAST (ximage)));
711   }
712
713   /* Expose sends a NULL image, we take the latest frame */
714   if (!ximage) {
715     draw_border = TRUE;
716     if (ximagesink->cur_image) {
717       ximage = ximagesink->cur_image;
718     } else {
719       g_mutex_unlock (ximagesink->flow_lock);
720       return TRUE;
721     }
722   }
723
724   src.w = ximage->width;
725   src.h = ximage->height;
726   dst.w = ximagesink->xwindow->width;
727   dst.h = ximagesink->xwindow->height;
728
729   gst_video_sink_center_rect (src, dst, &result, FALSE);
730
731   g_mutex_lock (ximagesink->x_lock);
732
733   if (draw_border) {
734     gst_ximagesink_xwindow_draw_borders (ximagesink, ximagesink->xwindow,
735         result);
736     ximagesink->draw_border = FALSE;
737   }
738 #ifdef HAVE_XSHM
739   if (ximagesink->xcontext->use_xshm) {
740     GST_LOG_OBJECT (ximagesink,
741         "XShmPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
742         ximage, 0, 0, result.x, result.y, result.w, result.h,
743         ximagesink->xwindow->width, ximagesink->xwindow->height);
744     XShmPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
745         ximagesink->xwindow->gc, ximage->ximage, 0, 0, result.x, result.y,
746         result.w, result.h, FALSE);
747   } else
748 #endif /* HAVE_XSHM */
749   {
750     GST_LOG_OBJECT (ximagesink,
751         "XPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
752         ximage, 0, 0, result.x, result.y, result.w, result.h,
753         ximagesink->xwindow->width, ximagesink->xwindow->height);
754     XPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
755         ximagesink->xwindow->gc, ximage->ximage, 0, 0, result.x, result.y,
756         result.w, result.h);
757   }
758
759   XSync (ximagesink->xcontext->disp, FALSE);
760
761   g_mutex_unlock (ximagesink->x_lock);
762
763   g_mutex_unlock (ximagesink->flow_lock);
764
765   return TRUE;
766 }
767
768 static gboolean
769 gst_ximagesink_xwindow_decorate (GstXImageSink * ximagesink,
770     GstXWindow * window)
771 {
772   Atom hints_atom = None;
773   MotifWmHints *hints;
774
775   g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), FALSE);
776   g_return_val_if_fail (window != NULL, FALSE);
777
778   g_mutex_lock (ximagesink->x_lock);
779
780   hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS", 1);
781   if (hints_atom == None) {
782     g_mutex_unlock (ximagesink->x_lock);
783     return FALSE;
784   }
785
786   hints = g_malloc0 (sizeof (MotifWmHints));
787
788   hints->flags |= MWM_HINTS_DECORATIONS;
789   hints->decorations = 1 << 0;
790
791   XChangeProperty (ximagesink->xcontext->disp, window->win,
792       hints_atom, hints_atom, 32, PropModeReplace,
793       (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
794
795   XSync (ximagesink->xcontext->disp, FALSE);
796
797   g_mutex_unlock (ximagesink->x_lock);
798
799   g_free (hints);
800
801   return TRUE;
802 }
803
804 static void
805 gst_ximagesink_xwindow_set_title (GstXImageSink * ximagesink,
806     GstXWindow * xwindow, const gchar * media_title)
807 {
808   if (media_title) {
809     g_free (ximagesink->media_title);
810     ximagesink->media_title = g_strdup (media_title);
811   }
812   if (xwindow) {
813     /* we have a window */
814     if (xwindow->internal) {
815       XTextProperty xproperty;
816       const gchar *app_name;
817       const gchar *title = NULL;
818       gchar *title_mem = NULL;
819
820       /* set application name as a title */
821       app_name = g_get_application_name ();
822
823       if (app_name && ximagesink->media_title) {
824         title = title_mem = g_strconcat (ximagesink->media_title, " : ",
825             app_name, NULL);
826       } else if (app_name) {
827         title = app_name;
828       } else if (ximagesink->media_title) {
829         title = ximagesink->media_title;
830       }
831
832       if (title) {
833         if ((XStringListToTextProperty (((char **) &title), 1,
834                     &xproperty)) != 0) {
835           XSetWMName (ximagesink->xcontext->disp, xwindow->win, &xproperty);
836           XFree (xproperty.value);
837         }
838
839         g_free (title_mem);
840       }
841     }
842   }
843 }
844
845 /* This function handles a GstXWindow creation */
846 static GstXWindow *
847 gst_ximagesink_xwindow_new (GstXImageSink * ximagesink, gint width, gint height)
848 {
849   GstXWindow *xwindow = NULL;
850   XGCValues values;
851
852   g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
853
854   xwindow = g_new0 (GstXWindow, 1);
855
856   xwindow->width = width;
857   xwindow->height = height;
858   xwindow->internal = TRUE;
859
860   g_mutex_lock (ximagesink->x_lock);
861
862   xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp,
863       ximagesink->xcontext->root,
864       0, 0, xwindow->width, xwindow->height, 0, 0, ximagesink->xcontext->black);
865
866   /* We have to do that to prevent X from redrawing the background on 
867      ConfigureNotify. This takes away flickering of video when resizing. */
868   XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None);
869
870   /* set application name as a title */
871   gst_ximagesink_xwindow_set_title (ximagesink, xwindow, NULL);
872
873   if (ximagesink->handle_events) {
874     Atom wm_delete;
875
876     XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
877         StructureNotifyMask | PointerMotionMask | KeyPressMask |
878         KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
879
880     /* Tell the window manager we'd like delete client messages instead of
881      * being killed */
882     wm_delete = XInternAtom (ximagesink->xcontext->disp,
883         "WM_DELETE_WINDOW", False);
884     (void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win,
885         &wm_delete, 1);
886   }
887
888   xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
889       0, &values);
890
891   XMapRaised (ximagesink->xcontext->disp, xwindow->win);
892
893   XSync (ximagesink->xcontext->disp, FALSE);
894
895   g_mutex_unlock (ximagesink->x_lock);
896
897   gst_ximagesink_xwindow_decorate (ximagesink, xwindow);
898
899   gst_x_overlay_got_window_handle (GST_X_OVERLAY (ximagesink), xwindow->win);
900
901   return xwindow;
902 }
903
904 /* This function destroys a GstXWindow */
905 static void
906 gst_ximagesink_xwindow_destroy (GstXImageSink * ximagesink,
907     GstXWindow * xwindow)
908 {
909   g_return_if_fail (xwindow != NULL);
910   g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
911
912   g_mutex_lock (ximagesink->x_lock);
913
914   /* If we did not create that window we just free the GC and let it live */
915   if (xwindow->internal)
916     XDestroyWindow (ximagesink->xcontext->disp, xwindow->win);
917   else
918     XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0);
919
920   XFreeGC (ximagesink->xcontext->disp, xwindow->gc);
921
922   XSync (ximagesink->xcontext->disp, FALSE);
923
924   g_mutex_unlock (ximagesink->x_lock);
925
926   g_free (xwindow);
927 }
928
929 static void
930 gst_ximagesink_xwindow_update_geometry (GstXImageSink * ximagesink)
931 {
932   XWindowAttributes attr;
933
934   g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
935
936   /* Update the window geometry */
937   g_mutex_lock (ximagesink->x_lock);
938   if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
939     g_mutex_unlock (ximagesink->x_lock);
940     return;
941   }
942
943   XGetWindowAttributes (ximagesink->xcontext->disp,
944       ximagesink->xwindow->win, &attr);
945
946   ximagesink->xwindow->width = attr.width;
947   ximagesink->xwindow->height = attr.height;
948
949   g_mutex_unlock (ximagesink->x_lock);
950 }
951
952 static void
953 gst_ximagesink_xwindow_clear (GstXImageSink * ximagesink, GstXWindow * xwindow)
954 {
955   g_return_if_fail (xwindow != NULL);
956   g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
957
958   g_mutex_lock (ximagesink->x_lock);
959
960   XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
961       ximagesink->xcontext->black);
962
963   XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
964       0, 0, xwindow->width, xwindow->height);
965
966   XSync (ximagesink->xcontext->disp, FALSE);
967
968   g_mutex_unlock (ximagesink->x_lock);
969 }
970
971 /* This function handles XEvents that might be in the queue. It generates
972    GstEvent that will be sent upstream in the pipeline to handle interactivity
973    and navigation.*/
974 static void
975 gst_ximagesink_handle_xevents (GstXImageSink * ximagesink)
976 {
977   XEvent e;
978   guint pointer_x = 0, pointer_y = 0;
979   gboolean pointer_moved = FALSE;
980   gboolean exposed = FALSE, configured = FALSE;
981
982   g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
983
984   /* Then we get all pointer motion events, only the last position is
985      interesting. */
986   g_mutex_lock (ximagesink->flow_lock);
987   g_mutex_lock (ximagesink->x_lock);
988   while (XCheckWindowEvent (ximagesink->xcontext->disp,
989           ximagesink->xwindow->win, PointerMotionMask, &e)) {
990     g_mutex_unlock (ximagesink->x_lock);
991     g_mutex_unlock (ximagesink->flow_lock);
992
993     switch (e.type) {
994       case MotionNotify:
995         pointer_x = e.xmotion.x;
996         pointer_y = e.xmotion.y;
997         pointer_moved = TRUE;
998         break;
999       default:
1000         break;
1001     }
1002     g_mutex_lock (ximagesink->flow_lock);
1003     g_mutex_lock (ximagesink->x_lock);
1004   }
1005
1006   if (pointer_moved) {
1007     g_mutex_unlock (ximagesink->x_lock);
1008     g_mutex_unlock (ximagesink->flow_lock);
1009
1010     GST_DEBUG ("ximagesink pointer moved over window at %d,%d",
1011         pointer_x, pointer_y);
1012     gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
1013         "mouse-move", 0, pointer_x, pointer_y);
1014
1015     g_mutex_lock (ximagesink->flow_lock);
1016     g_mutex_lock (ximagesink->x_lock);
1017   }
1018
1019   /* We get all remaining events on our window to throw them upstream */
1020   while (XCheckWindowEvent (ximagesink->xcontext->disp,
1021           ximagesink->xwindow->win,
1022           KeyPressMask | KeyReleaseMask |
1023           ButtonPressMask | ButtonReleaseMask, &e)) {
1024     KeySym keysym;
1025
1026     /* We lock only for the X function call */
1027     g_mutex_unlock (ximagesink->x_lock);
1028     g_mutex_unlock (ximagesink->flow_lock);
1029
1030     switch (e.type) {
1031       case ButtonPress:
1032         /* Mouse button pressed/released over our window. We send upstream
1033            events for interactivity/navigation */
1034         GST_DEBUG ("ximagesink button %d pressed over window at %d,%d",
1035             e.xbutton.button, e.xbutton.x, e.xbutton.x);
1036         gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
1037             "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
1038         break;
1039       case ButtonRelease:
1040         GST_DEBUG ("ximagesink button %d release over window at %d,%d",
1041             e.xbutton.button, e.xbutton.x, e.xbutton.x);
1042         gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
1043             "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
1044         break;
1045       case KeyPress:
1046       case KeyRelease:
1047         /* Key pressed/released over our window. We send upstream
1048            events for interactivity/navigation */
1049         GST_DEBUG ("ximagesink key %d pressed over window at %d,%d",
1050             e.xkey.keycode, e.xkey.x, e.xkey.x);
1051         g_mutex_lock (ximagesink->x_lock);
1052         keysym = XKeycodeToKeysym (ximagesink->xcontext->disp,
1053             e.xkey.keycode, 0);
1054         g_mutex_unlock (ximagesink->x_lock);
1055         if (keysym != NoSymbol) {
1056           char *key_str = NULL;
1057
1058           g_mutex_lock (ximagesink->x_lock);
1059           key_str = XKeysymToString (keysym);
1060           g_mutex_unlock (ximagesink->x_lock);
1061           gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
1062               e.type == KeyPress ? "key-press" : "key-release", key_str);
1063
1064         } else {
1065           gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
1066               e.type == KeyPress ? "key-press" : "key-release", "unknown");
1067         }
1068         break;
1069       default:
1070         GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)",
1071             e.type);
1072     }
1073     g_mutex_lock (ximagesink->flow_lock);
1074     g_mutex_lock (ximagesink->x_lock);
1075   }
1076
1077   while (XCheckWindowEvent (ximagesink->xcontext->disp,
1078           ximagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) {
1079     switch (e.type) {
1080       case Expose:
1081         exposed = TRUE;
1082         break;
1083       case ConfigureNotify:
1084         g_mutex_unlock (ximagesink->x_lock);
1085         gst_ximagesink_xwindow_update_geometry (ximagesink);
1086         g_mutex_lock (ximagesink->x_lock);
1087         configured = TRUE;
1088         break;
1089       default:
1090         break;
1091     }
1092   }
1093
1094   if (ximagesink->handle_expose && (exposed || configured)) {
1095     g_mutex_unlock (ximagesink->x_lock);
1096     g_mutex_unlock (ximagesink->flow_lock);
1097
1098     gst_ximagesink_expose (GST_X_OVERLAY (ximagesink));
1099
1100     g_mutex_lock (ximagesink->flow_lock);
1101     g_mutex_lock (ximagesink->x_lock);
1102   }
1103
1104   /* Handle Display events */
1105   while (XPending (ximagesink->xcontext->disp)) {
1106     XNextEvent (ximagesink->xcontext->disp, &e);
1107
1108     switch (e.type) {
1109       case ClientMessage:{
1110         Atom wm_delete;
1111
1112         wm_delete = XInternAtom (ximagesink->xcontext->disp,
1113             "WM_DELETE_WINDOW", False);
1114         if (wm_delete == (Atom) e.xclient.data.l[0]) {
1115           /* Handle window deletion by posting an error on the bus */
1116           GST_ELEMENT_ERROR (ximagesink, RESOURCE, NOT_FOUND,
1117               ("Output window was closed"), (NULL));
1118
1119           g_mutex_unlock (ximagesink->x_lock);
1120           gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1121           ximagesink->xwindow = NULL;
1122           g_mutex_lock (ximagesink->x_lock);
1123         }
1124         break;
1125       }
1126       default:
1127         break;
1128     }
1129   }
1130
1131   g_mutex_unlock (ximagesink->x_lock);
1132   g_mutex_unlock (ximagesink->flow_lock);
1133 }
1134
1135 static gpointer
1136 gst_ximagesink_event_thread (GstXImageSink * ximagesink)
1137 {
1138   g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
1139
1140   GST_OBJECT_LOCK (ximagesink);
1141   while (ximagesink->running) {
1142     GST_OBJECT_UNLOCK (ximagesink);
1143
1144     if (ximagesink->xwindow) {
1145       gst_ximagesink_handle_xevents (ximagesink);
1146     }
1147     /* FIXME: do we want to align this with the framerate or anything else? */
1148     g_usleep (G_USEC_PER_SEC / 20);
1149
1150     GST_OBJECT_LOCK (ximagesink);
1151   }
1152   GST_OBJECT_UNLOCK (ximagesink);
1153
1154   return NULL;
1155 }
1156
1157 static void
1158 gst_ximagesink_manage_event_thread (GstXImageSink * ximagesink)
1159 {
1160   GThread *thread = NULL;
1161
1162   /* don't start the thread too early */
1163   if (ximagesink->xcontext == NULL) {
1164     return;
1165   }
1166
1167   GST_OBJECT_LOCK (ximagesink);
1168   if (ximagesink->handle_expose || ximagesink->handle_events) {
1169     if (!ximagesink->event_thread) {
1170       /* Setup our event listening thread */
1171       GST_DEBUG_OBJECT (ximagesink, "run xevent thread, expose %d, events %d",
1172           ximagesink->handle_expose, ximagesink->handle_events);
1173       ximagesink->running = TRUE;
1174       ximagesink->event_thread = g_thread_create (
1175           (GThreadFunc) gst_ximagesink_event_thread, ximagesink, TRUE, NULL);
1176     }
1177   } else {
1178     if (ximagesink->event_thread) {
1179       GST_DEBUG_OBJECT (ximagesink, "stop xevent thread, expose %d, events %d",
1180           ximagesink->handle_expose, ximagesink->handle_events);
1181       ximagesink->running = FALSE;
1182       /* grab thread and mark it as NULL */
1183       thread = ximagesink->event_thread;
1184       ximagesink->event_thread = NULL;
1185     }
1186   }
1187   GST_OBJECT_UNLOCK (ximagesink);
1188
1189   /* Wait for our event thread to finish */
1190   if (thread)
1191     g_thread_join (thread);
1192
1193 }
1194
1195
1196 /* This function calculates the pixel aspect ratio based on the properties
1197  * in the xcontext structure and stores it there. */
1198 static void
1199 gst_ximagesink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
1200 {
1201   static const gint par[][2] = {
1202     {1, 1},                     /* regular screen */
1203     {16, 15},                   /* PAL TV */
1204     {11, 10},                   /* 525 line Rec.601 video */
1205     {54, 59},                   /* 625 line Rec.601 video */
1206     {64, 45},                   /* 1280x1024 on 16:9 display */
1207     {5, 3},                     /* 1280x1024 on 4:3 display */
1208     {4, 3}                      /*  800x600 on 16:9 display */
1209   };
1210   gint i;
1211   gint index;
1212   gdouble ratio;
1213   gdouble delta;
1214
1215 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
1216
1217   /* first calculate the "real" ratio based on the X values;
1218    * which is the "physical" w/h divided by the w/h in pixels of the display */
1219   ratio = (gdouble) (xcontext->widthmm * xcontext->height)
1220       / (xcontext->heightmm * xcontext->width);
1221
1222   /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
1223    * override here */
1224   if (xcontext->width == 720 && xcontext->height == 576) {
1225     ratio = 4.0 * 576 / (3.0 * 720);
1226   }
1227   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
1228
1229   /* now find the one from par[][2] with the lowest delta to the real one */
1230   delta = DELTA (0);
1231   index = 0;
1232
1233   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
1234     gdouble this_delta = DELTA (i);
1235
1236     if (this_delta < delta) {
1237       index = i;
1238       delta = this_delta;
1239     }
1240   }
1241
1242   GST_DEBUG ("Decided on index %d (%d/%d)", index,
1243       par[index][0], par[index][1]);
1244
1245   g_free (xcontext->par);
1246   xcontext->par = g_new0 (GValue, 1);
1247   g_value_init (xcontext->par, GST_TYPE_FRACTION);
1248   gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
1249   GST_DEBUG ("set xcontext PAR to %d/%d",
1250       gst_value_get_fraction_numerator (xcontext->par),
1251       gst_value_get_fraction_denominator (xcontext->par));
1252 }
1253
1254 /* This function gets the X Display and global info about it. Everything is
1255    stored in our object and will be cleaned when the object is disposed. Note
1256    here that caps for supported format are generated without any window or
1257    image creation */
1258 static GstXContext *
1259 gst_ximagesink_xcontext_get (GstXImageSink * ximagesink)
1260 {
1261   GstXContext *xcontext = NULL;
1262   XPixmapFormatValues *px_formats = NULL;
1263   gint nb_formats = 0, i;
1264
1265   g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
1266
1267   xcontext = g_new0 (GstXContext, 1);
1268
1269   g_mutex_lock (ximagesink->x_lock);
1270
1271   xcontext->disp = XOpenDisplay (ximagesink->display_name);
1272
1273   if (!xcontext->disp) {
1274     g_mutex_unlock (ximagesink->x_lock);
1275     g_free (xcontext);
1276     GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1277         ("Could not initialise X output"), ("Could not open display"));
1278     return NULL;
1279   }
1280
1281   xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
1282   xcontext->screen_num = DefaultScreen (xcontext->disp);
1283   xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
1284   xcontext->root = DefaultRootWindow (xcontext->disp);
1285   xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
1286   xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
1287   xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
1288
1289   xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
1290   xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
1291   xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
1292   xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
1293
1294   GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm",
1295       xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
1296
1297   gst_ximagesink_calculate_pixel_aspect_ratio (xcontext);
1298
1299   /* We get supported pixmap formats at supported depth */
1300   px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
1301
1302   if (!px_formats) {
1303     XCloseDisplay (xcontext->disp);
1304     g_mutex_unlock (ximagesink->x_lock);
1305     g_free (xcontext->par);
1306     g_free (xcontext);
1307     GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1308         ("Could not get supported pixmap formats"), (NULL));
1309     return NULL;
1310   }
1311
1312   /* We get bpp value corresponding to our running depth */
1313   for (i = 0; i < nb_formats; i++) {
1314     if (px_formats[i].depth == xcontext->depth)
1315       xcontext->bpp = px_formats[i].bits_per_pixel;
1316   }
1317
1318   XFree (px_formats);
1319
1320   xcontext->endianness =
1321       (ImageByteOrder (xcontext->disp) ==
1322       LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
1323
1324   /* Search for XShm extension support */
1325 #ifdef HAVE_XSHM
1326   if (XShmQueryExtension (xcontext->disp) &&
1327       gst_ximagesink_check_xshm_calls (ximagesink, xcontext)) {
1328     xcontext->use_xshm = TRUE;
1329     GST_DEBUG ("ximagesink is using XShm extension");
1330   } else
1331 #endif
1332   {
1333     xcontext->use_xshm = FALSE;
1334     GST_DEBUG ("ximagesink is not using XShm extension");
1335   }
1336
1337   /* our caps system handles 24/32bpp RGB as big-endian. */
1338   if ((xcontext->bpp == 24 || xcontext->bpp == 32) &&
1339       xcontext->endianness == G_LITTLE_ENDIAN) {
1340     xcontext->endianness = G_BIG_ENDIAN;
1341     xcontext->visual->red_mask = GUINT32_TO_BE (xcontext->visual->red_mask);
1342     xcontext->visual->green_mask = GUINT32_TO_BE (xcontext->visual->green_mask);
1343     xcontext->visual->blue_mask = GUINT32_TO_BE (xcontext->visual->blue_mask);
1344     if (xcontext->bpp == 24) {
1345       xcontext->visual->red_mask >>= 8;
1346       xcontext->visual->green_mask >>= 8;
1347       xcontext->visual->blue_mask >>= 8;
1348     }
1349   }
1350
1351   /* update object's par with calculated one if not set yet */
1352   if (!ximagesink->par) {
1353     ximagesink->par = g_new0 (GValue, 1);
1354     gst_value_init_and_copy (ximagesink->par, xcontext->par);
1355     GST_DEBUG_OBJECT (ximagesink, "set calculated PAR on object's PAR");
1356   }
1357   xcontext->caps = gst_caps_new_simple ("video/x-raw-rgb",
1358       "bpp", G_TYPE_INT, xcontext->bpp,
1359       "depth", G_TYPE_INT, xcontext->depth,
1360       "endianness", G_TYPE_INT, xcontext->endianness,
1361       "red_mask", G_TYPE_INT, xcontext->visual->red_mask,
1362       "green_mask", G_TYPE_INT, xcontext->visual->green_mask,
1363       "blue_mask", G_TYPE_INT, xcontext->visual->blue_mask,
1364       "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
1365       "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
1366       "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
1367   if (ximagesink->par) {
1368     int nom, den;
1369
1370     nom = gst_value_get_fraction_numerator (ximagesink->par);
1371     den = gst_value_get_fraction_denominator (ximagesink->par);
1372     gst_caps_set_simple (xcontext->caps, "pixel-aspect-ratio",
1373         GST_TYPE_FRACTION, nom, den, NULL);
1374   }
1375
1376   g_mutex_unlock (ximagesink->x_lock);
1377
1378   return xcontext;
1379 }
1380
1381 /* This function cleans the X context. Closing the Display and unrefing the
1382    caps for supported formats. */
1383 static void
1384 gst_ximagesink_xcontext_clear (GstXImageSink * ximagesink)
1385 {
1386   GstXContext *xcontext;
1387
1388   g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
1389
1390   GST_OBJECT_LOCK (ximagesink);
1391   if (ximagesink->xcontext == NULL) {
1392     GST_OBJECT_UNLOCK (ximagesink);
1393     return;
1394   }
1395
1396   /* Take the xcontext reference and NULL it while we
1397    * clean it up, so that any buffer-alloced buffers 
1398    * arriving after this will be freed correctly */
1399   xcontext = ximagesink->xcontext;
1400   ximagesink->xcontext = NULL;
1401
1402   GST_OBJECT_UNLOCK (ximagesink);
1403
1404   gst_caps_unref (xcontext->caps);
1405   g_free (xcontext->par);
1406   g_free (ximagesink->par);
1407   ximagesink->par = NULL;
1408
1409   if (xcontext->last_caps)
1410     gst_caps_replace (&xcontext->last_caps, NULL);
1411
1412   g_mutex_lock (ximagesink->x_lock);
1413
1414   XCloseDisplay (xcontext->disp);
1415
1416   g_mutex_unlock (ximagesink->x_lock);
1417
1418   g_free (xcontext);
1419 }
1420
1421 static void
1422 gst_ximagesink_bufferpool_clear (GstXImageSink * ximagesink)
1423 {
1424
1425   g_mutex_lock (ximagesink->pool_lock);
1426
1427   while (ximagesink->buffer_pool) {
1428     GstXImageBuffer *ximage = ximagesink->buffer_pool->data;
1429
1430     ximagesink->buffer_pool = g_slist_delete_link (ximagesink->buffer_pool,
1431         ximagesink->buffer_pool);
1432     gst_ximage_buffer_free (ximage);
1433   }
1434
1435   g_mutex_unlock (ximagesink->pool_lock);
1436 }
1437
1438 /* Element stuff */
1439
1440 static GstCaps *
1441 gst_ximagesink_getcaps (GstBaseSink * bsink)
1442 {
1443   GstXImageSink *ximagesink;
1444   GstCaps *caps;
1445   int i;
1446
1447   ximagesink = GST_XIMAGESINK (bsink);
1448
1449   if (ximagesink->xcontext)
1450     return gst_caps_ref (ximagesink->xcontext->caps);
1451
1452   /* get a template copy and add the pixel aspect ratio */
1453   caps =
1454       gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SINK
1455           (ximagesink)->sinkpad));
1456   for (i = 0; i < gst_caps_get_size (caps); ++i) {
1457     GstStructure *structure = gst_caps_get_structure (caps, i);
1458
1459     if (ximagesink->par) {
1460       int nom, den;
1461
1462       nom = gst_value_get_fraction_numerator (ximagesink->par);
1463       den = gst_value_get_fraction_denominator (ximagesink->par);
1464       gst_structure_set (structure, "pixel-aspect-ratio",
1465           GST_TYPE_FRACTION, nom, den, NULL);
1466     }
1467   }
1468
1469   return caps;
1470 }
1471
1472 static gboolean
1473 gst_ximagesink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1474 {
1475   GstXImageSink *ximagesink;
1476   gboolean ret = TRUE;
1477   GstStructure *structure;
1478   const GValue *par;
1479   gint new_width, new_height;
1480   const GValue *fps;
1481
1482   ximagesink = GST_XIMAGESINK (bsink);
1483
1484   if (!ximagesink->xcontext)
1485     return FALSE;
1486
1487   GST_DEBUG_OBJECT (ximagesink,
1488       "sinkconnect possible caps %" GST_PTR_FORMAT " with given caps %"
1489       GST_PTR_FORMAT, ximagesink->xcontext->caps, caps);
1490
1491   /* We intersect those caps with our template to make sure they are correct */
1492   if (!gst_caps_can_intersect (ximagesink->xcontext->caps, caps))
1493     goto incompatible_caps;
1494
1495   structure = gst_caps_get_structure (caps, 0);
1496
1497   ret &= gst_structure_get_int (structure, "width", &new_width);
1498   ret &= gst_structure_get_int (structure, "height", &new_height);
1499   fps = gst_structure_get_value (structure, "framerate");
1500   ret &= (fps != NULL);
1501   if (!ret)
1502     return FALSE;
1503
1504   /* if the caps contain pixel-aspect-ratio, they have to match ours,
1505    * otherwise linking should fail */
1506   par = gst_structure_get_value (structure, "pixel-aspect-ratio");
1507   if (par) {
1508     if (ximagesink->par) {
1509       if (gst_value_compare (par, ximagesink->par) != GST_VALUE_EQUAL) {
1510         goto wrong_aspect;
1511       }
1512     } else if (ximagesink->xcontext->par) {
1513       if (gst_value_compare (par, ximagesink->xcontext->par) != GST_VALUE_EQUAL) {
1514         goto wrong_aspect;
1515       }
1516     }
1517   }
1518
1519   GST_VIDEO_SINK_WIDTH (ximagesink) = new_width;
1520   GST_VIDEO_SINK_HEIGHT (ximagesink) = new_height;
1521   ximagesink->fps_n = gst_value_get_fraction_numerator (fps);
1522   ximagesink->fps_d = gst_value_get_fraction_denominator (fps);
1523
1524   /* Notify application to set xwindow id now */
1525   g_mutex_lock (ximagesink->flow_lock);
1526   if (!ximagesink->xwindow) {
1527     g_mutex_unlock (ximagesink->flow_lock);
1528     gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (ximagesink));
1529   } else {
1530     g_mutex_unlock (ximagesink->flow_lock);
1531   }
1532
1533   /* Creating our window and our image */
1534   if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 ||
1535       GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0) {
1536     GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL),
1537         ("Invalid image size."));
1538     return FALSE;
1539   }
1540
1541   g_mutex_lock (ximagesink->flow_lock);
1542   if (!ximagesink->xwindow) {
1543     ximagesink->xwindow = gst_ximagesink_xwindow_new (ximagesink,
1544         GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1545   }
1546   /* Remember to draw borders for next frame */
1547   ximagesink->draw_border = TRUE;
1548   g_mutex_unlock (ximagesink->flow_lock);
1549
1550   /* If our ximage has changed we destroy it, next chain iteration will create
1551      a new one */
1552   if ((ximagesink->ximage) &&
1553       ((GST_VIDEO_SINK_WIDTH (ximagesink) != ximagesink->ximage->width) ||
1554           (GST_VIDEO_SINK_HEIGHT (ximagesink) != ximagesink->ximage->height))) {
1555     GST_DEBUG_OBJECT (ximagesink, "our image is not usable anymore, unref %p",
1556         ximagesink->ximage);
1557     gst_buffer_unref (GST_BUFFER_CAST (ximagesink->ximage));
1558     ximagesink->ximage = NULL;
1559   }
1560
1561   return TRUE;
1562
1563   /* ERRORS */
1564 incompatible_caps:
1565   {
1566     GST_ERROR_OBJECT (ximagesink, "caps incompatible");
1567     return FALSE;
1568   }
1569 wrong_aspect:
1570   {
1571     GST_INFO_OBJECT (ximagesink, "pixel aspect ratio does not match");
1572     return FALSE;
1573   }
1574 }
1575
1576 static GstStateChangeReturn
1577 gst_ximagesink_change_state (GstElement * element, GstStateChange transition)
1578 {
1579   GstXImageSink *ximagesink;
1580   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1581   GstXContext *xcontext = NULL;
1582
1583   ximagesink = GST_XIMAGESINK (element);
1584
1585   switch (transition) {
1586     case GST_STATE_CHANGE_NULL_TO_READY:
1587
1588       /* Initializing the XContext */
1589       if (ximagesink->xcontext == NULL) {
1590         xcontext = gst_ximagesink_xcontext_get (ximagesink);
1591         if (xcontext == NULL) {
1592           ret = GST_STATE_CHANGE_FAILURE;
1593           goto beach;
1594         }
1595         GST_OBJECT_LOCK (ximagesink);
1596         if (xcontext)
1597           ximagesink->xcontext = xcontext;
1598         GST_OBJECT_UNLOCK (ximagesink);
1599       }
1600
1601       /* call XSynchronize with the current value of synchronous */
1602       GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1603           ximagesink->synchronous ? "TRUE" : "FALSE");
1604       g_mutex_lock (ximagesink->x_lock);
1605       XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1606       g_mutex_unlock (ximagesink->x_lock);
1607       gst_ximagesink_manage_event_thread (ximagesink);
1608       break;
1609     case GST_STATE_CHANGE_READY_TO_PAUSED:
1610       g_mutex_lock (ximagesink->flow_lock);
1611       if (ximagesink->xwindow)
1612         gst_ximagesink_xwindow_clear (ximagesink, ximagesink->xwindow);
1613       g_mutex_unlock (ximagesink->flow_lock);
1614       break;
1615     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1616       break;
1617     default:
1618       break;
1619   }
1620
1621   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1622
1623   switch (transition) {
1624     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1625       break;
1626     case GST_STATE_CHANGE_PAUSED_TO_READY:
1627       ximagesink->fps_n = 0;
1628       ximagesink->fps_d = 1;
1629       GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
1630       GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
1631       break;
1632     case GST_STATE_CHANGE_READY_TO_NULL:
1633       gst_ximagesink_reset (ximagesink);
1634       break;
1635     default:
1636       break;
1637   }
1638
1639 beach:
1640   return ret;
1641 }
1642
1643 static void
1644 gst_ximagesink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1645     GstClockTime * start, GstClockTime * end)
1646 {
1647   GstXImageSink *ximagesink;
1648
1649   ximagesink = GST_XIMAGESINK (bsink);
1650
1651   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1652     *start = GST_BUFFER_TIMESTAMP (buf);
1653     if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1654       *end = *start + GST_BUFFER_DURATION (buf);
1655     } else {
1656       if (ximagesink->fps_n > 0) {
1657         *end = *start +
1658             gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d,
1659             ximagesink->fps_n);
1660       }
1661     }
1662   }
1663 }
1664
1665 static GstFlowReturn
1666 gst_ximagesink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1667 {
1668   GstXImageSink *ximagesink;
1669
1670   g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
1671
1672   ximagesink = GST_XIMAGESINK (vsink);
1673
1674   /* This shouldn't really happen because state changes will fail
1675    * if the xcontext can't be allocated */
1676   if (!ximagesink->xcontext)
1677     return GST_FLOW_ERROR;
1678
1679   /* If this buffer has been allocated using our buffer management we simply
1680      put the ximage which is in the PRIVATE pointer */
1681   if (GST_IS_XIMAGE_BUFFER (buf)) {
1682     GST_LOG_OBJECT (ximagesink, "buffer from our pool, writing directly");
1683     if (!gst_ximagesink_ximage_put (ximagesink, GST_XIMAGE_BUFFER (buf)))
1684       goto no_window;
1685   } else {
1686     /* Else we have to copy the data into our private image, */
1687     /* if we have one... */
1688     GST_LOG_OBJECT (ximagesink, "normal buffer, copying from it");
1689     if (!ximagesink->ximage) {
1690       GST_DEBUG_OBJECT (ximagesink, "creating our ximage");
1691       ximagesink->ximage = gst_ximagesink_ximage_new (ximagesink,
1692           GST_BUFFER_CAPS (buf));
1693       if (!ximagesink->ximage)
1694         /* The create method should have posted an informative error */
1695         goto no_ximage;
1696
1697       if (ximagesink->ximage->size < GST_BUFFER_SIZE (buf)) {
1698         GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1699             ("Failed to create output image buffer of %dx%d pixels",
1700                 ximagesink->ximage->width, ximagesink->ximage->height),
1701             ("XServer allocated buffer size did not match input buffer"));
1702
1703         gst_ximagesink_ximage_destroy (ximagesink, ximagesink->ximage);
1704         ximagesink->ximage = NULL;
1705         goto no_ximage;
1706       }
1707     }
1708     memcpy (GST_BUFFER_DATA (ximagesink->ximage), GST_BUFFER_DATA (buf),
1709         MIN (GST_BUFFER_SIZE (buf), ximagesink->ximage->size));
1710     if (!gst_ximagesink_ximage_put (ximagesink, ximagesink->ximage))
1711       goto no_window;
1712   }
1713
1714   return GST_FLOW_OK;
1715
1716   /* ERRORS */
1717 no_ximage:
1718   {
1719     /* No image available. That's very bad ! */
1720     GST_WARNING_OBJECT (ximagesink, "could not create image");
1721     return GST_FLOW_ERROR;
1722   }
1723 no_window:
1724   {
1725     /* No Window available to put our image into */
1726     GST_WARNING_OBJECT (ximagesink, "could not output image - no window");
1727     return GST_FLOW_ERROR;
1728   }
1729 }
1730
1731
1732 static gboolean
1733 gst_ximagesink_event (GstBaseSink * sink, GstEvent * event)
1734 {
1735   GstXImageSink *ximagesink = GST_XIMAGESINK (sink);
1736
1737   switch (GST_EVENT_TYPE (event)) {
1738     case GST_EVENT_TAG:{
1739       GstTagList *l;
1740       gchar *title = NULL;
1741
1742       gst_event_parse_tag (event, &l);
1743       gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1744
1745       if (title) {
1746         GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title);
1747         gst_ximagesink_xwindow_set_title (ximagesink, ximagesink->xwindow,
1748             title);
1749
1750         g_free (title);
1751       }
1752       break;
1753     }
1754     default:
1755       break;
1756   }
1757   if (GST_BASE_SINK_CLASS (parent_class)->event)
1758     return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1759   else
1760     return TRUE;
1761 }
1762
1763
1764 /* Buffer management
1765  *
1766  * The buffer_alloc function must either return a buffer with given size and
1767  * caps or create a buffer with different caps attached to the buffer. This
1768  * last option is called reverse negotiation, ie, where the sink suggests a
1769  * different format from the upstream peer. 
1770  *
1771  * We try to do reverse negotiation when our geometry changes and we like a
1772  * resized buffer.
1773  */
1774 static GstFlowReturn
1775 gst_ximagesink_buffer_alloc (GstBaseSink * bsink, guint64 offset, guint size,
1776     GstCaps * caps, GstBuffer ** buf)
1777 {
1778   GstXImageSink *ximagesink;
1779   GstXImageBuffer *ximage = NULL;
1780   GstStructure *structure = NULL;
1781   GstFlowReturn ret = GST_FLOW_OK;
1782   GstCaps *alloc_caps;
1783   gboolean alloc_unref = FALSE;
1784   gint width, height;
1785   GstVideoRectangle dst, src, result;
1786   gboolean caps_accepted = FALSE;
1787
1788   ximagesink = GST_XIMAGESINK (bsink);
1789
1790   if (G_UNLIKELY (!caps)) {
1791     GST_WARNING_OBJECT (ximagesink, "have no caps, doing fallback allocation");
1792     *buf = NULL;
1793     ret = GST_FLOW_OK;
1794     goto beach;
1795   }
1796
1797   /* This shouldn't really happen because state changes will fail
1798    * if the xcontext can't be allocated */
1799   if (!ximagesink->xcontext)
1800     return GST_FLOW_ERROR;
1801
1802   GST_LOG_OBJECT (ximagesink,
1803       "a buffer of %d bytes was requested with caps %" GST_PTR_FORMAT
1804       " and offset %" G_GUINT64_FORMAT, size, caps, offset);
1805
1806   /* assume we're going to alloc what was requested, keep track of
1807    * wheter we need to unref or not. When we suggest a new format 
1808    * upstream we will create a new caps that we need to unref. */
1809   alloc_caps = caps;
1810   alloc_unref = FALSE;
1811
1812   /* get struct to see what is requested */
1813   structure = gst_caps_get_structure (caps, 0);
1814   if (!gst_structure_get_int (structure, "width", &width) ||
1815       !gst_structure_get_int (structure, "height", &height)) {
1816     GST_WARNING_OBJECT (ximagesink, "invalid caps for buffer allocation %"
1817         GST_PTR_FORMAT, caps);
1818     ret = GST_FLOW_NOT_NEGOTIATED;
1819     goto beach;
1820   }
1821
1822   src.w = width;
1823   src.h = height;
1824
1825   /* We take the flow_lock because the window might go away */
1826   g_mutex_lock (ximagesink->flow_lock);
1827   if (!ximagesink->xwindow) {
1828     g_mutex_unlock (ximagesink->flow_lock);
1829     goto alloc;
1830   }
1831
1832   /* What is our geometry */
1833   dst.w = ximagesink->xwindow->width;
1834   dst.h = ximagesink->xwindow->height;
1835
1836   g_mutex_unlock (ximagesink->flow_lock);
1837
1838   if (ximagesink->keep_aspect) {
1839     GST_LOG_OBJECT (ximagesink, "enforcing aspect ratio in reverse caps "
1840         "negotiation");
1841     gst_video_sink_center_rect (src, dst, &result, TRUE);
1842   } else {
1843     GST_LOG_OBJECT (ximagesink, "trying to resize to window geometry "
1844         "ignoring aspect ratio");
1845     result.x = result.y = 0;
1846     result.w = dst.w;
1847     result.h = dst.h;
1848   }
1849
1850   /* We would like another geometry */
1851   if (width != result.w || height != result.h) {
1852     int nom, den;
1853     GstCaps *desired_caps;
1854     GstStructure *desired_struct;
1855
1856     /* make a copy of the incomming caps to create the new
1857      * suggestion. We can't use make_writable because we might
1858      * then destroy the original caps which we still need when the
1859      * peer does not accept the suggestion. */
1860     desired_caps = gst_caps_copy (caps);
1861     desired_struct = gst_caps_get_structure (desired_caps, 0);
1862
1863     GST_DEBUG ("we would love to receive a %dx%d video", result.w, result.h);
1864     gst_structure_set (desired_struct, "width", G_TYPE_INT, result.w, NULL);
1865     gst_structure_set (desired_struct, "height", G_TYPE_INT, result.h, NULL);
1866
1867     /* PAR property overrides the X calculated one */
1868     if (ximagesink->par) {
1869       nom = gst_value_get_fraction_numerator (ximagesink->par);
1870       den = gst_value_get_fraction_denominator (ximagesink->par);
1871       gst_structure_set (desired_struct, "pixel-aspect-ratio",
1872           GST_TYPE_FRACTION, nom, den, NULL);
1873     } else if (ximagesink->xcontext->par) {
1874       nom = gst_value_get_fraction_numerator (ximagesink->xcontext->par);
1875       den = gst_value_get_fraction_denominator (ximagesink->xcontext->par);
1876       gst_structure_set (desired_struct, "pixel-aspect-ratio",
1877           GST_TYPE_FRACTION, nom, den, NULL);
1878     }
1879
1880
1881     /* see if peer accepts our new suggestion, if there is no peer, this 
1882      * function returns true. */
1883     if (!ximagesink->xcontext->last_caps ||
1884         !gst_caps_is_equal (desired_caps, ximagesink->xcontext->last_caps)) {
1885       caps_accepted =
1886           gst_pad_peer_accept_caps (GST_VIDEO_SINK_PAD (ximagesink),
1887           desired_caps);
1888
1889       /* Suggestion failed, prevent future attempts for the same caps
1890        * to fail as well. */
1891       if (!caps_accepted)
1892         gst_caps_replace (&ximagesink->xcontext->last_caps, desired_caps);
1893     }
1894
1895     if (caps_accepted) {
1896       /* we will not alloc a buffer of the new suggested caps. Make sure
1897        * we also unref this new caps after we set it on the buffer. */
1898       alloc_caps = desired_caps;
1899       alloc_unref = TRUE;
1900       width = result.w;
1901       height = result.h;
1902       GST_DEBUG ("peer pad accepts our desired caps %" GST_PTR_FORMAT,
1903           desired_caps);
1904     } else {
1905       GST_DEBUG ("peer pad does not accept our desired caps %" GST_PTR_FORMAT,
1906           desired_caps);
1907       /* we alloc a buffer with the original incomming caps already in the
1908        * width and height variables */
1909       gst_caps_unref (desired_caps);
1910     }
1911   }
1912
1913 alloc:
1914   /* Inspect our buffer pool */
1915   g_mutex_lock (ximagesink->pool_lock);
1916   while (ximagesink->buffer_pool) {
1917     ximage = (GstXImageBuffer *) ximagesink->buffer_pool->data;
1918
1919     if (ximage) {
1920       /* Removing from the pool */
1921       ximagesink->buffer_pool = g_slist_delete_link (ximagesink->buffer_pool,
1922           ximagesink->buffer_pool);
1923
1924       /* If the ximage is invalid for our need, destroy */
1925       if ((ximage->width != width) || (ximage->height != height)) {
1926         gst_ximage_buffer_free (ximage);
1927         ximage = NULL;
1928       } else {
1929         /* We found a suitable ximage */
1930         break;
1931       }
1932     }
1933   }
1934   g_mutex_unlock (ximagesink->pool_lock);
1935
1936   /* We haven't found anything, creating a new one */
1937   if (!ximage) {
1938     ximage = gst_ximagesink_ximage_new (ximagesink, alloc_caps);
1939   }
1940   /* Now we should have a ximage, set appropriate caps on it */
1941   if (ximage) {
1942     /* Make sure the buffer is cleared of any previously used flags */
1943     GST_MINI_OBJECT_CAST (ximage)->flags = 0;
1944     gst_buffer_set_caps (GST_BUFFER_CAST (ximage), alloc_caps);
1945   }
1946
1947   /* could be our new reffed suggestion or the original unreffed caps */
1948   if (alloc_unref)
1949     gst_caps_unref (alloc_caps);
1950
1951   *buf = GST_BUFFER_CAST (ximage);
1952
1953 beach:
1954   return ret;
1955 }
1956
1957 /* Interfaces stuff */
1958
1959 static gboolean
1960 gst_ximagesink_interface_supported (GstImplementsInterface * iface, GType type)
1961 {
1962   if (type == GST_TYPE_NAVIGATION || type == GST_TYPE_X_OVERLAY)
1963     return TRUE;
1964   else
1965     return FALSE;
1966 }
1967
1968 static void
1969 gst_ximagesink_interface_init (GstImplementsInterfaceClass * klass)
1970 {
1971   klass->supported = gst_ximagesink_interface_supported;
1972 }
1973
1974 static void
1975 gst_ximagesink_navigation_send_event (GstNavigation * navigation,
1976     GstStructure * structure)
1977 {
1978   GstXImageSink *ximagesink = GST_XIMAGESINK (navigation);
1979   GstEvent *event;
1980   gint x_offset, y_offset;
1981   gdouble x, y;
1982   GstPad *pad = NULL;
1983
1984   event = gst_event_new_navigation (structure);
1985
1986   /* We are not converting the pointer coordinates as there's no hardware
1987      scaling done here. The only possible scaling is done by videoscale and
1988      videoscale will have to catch those events and tranform the coordinates
1989      to match the applied scaling. So here we just add the offset if the image
1990      is centered in the window.  */
1991
1992   /* We take the flow_lock while we look at the window */
1993   g_mutex_lock (ximagesink->flow_lock);
1994
1995   if (!ximagesink->xwindow) {
1996     g_mutex_unlock (ximagesink->flow_lock);
1997     return;
1998   }
1999
2000   x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
2001   y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
2002
2003   g_mutex_unlock (ximagesink->flow_lock);
2004
2005   if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) {
2006     x -= x_offset / 2;
2007     gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
2008   }
2009   if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
2010     y -= y_offset / 2;
2011     gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
2012   }
2013
2014   pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (ximagesink));
2015
2016   if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
2017     gst_pad_send_event (pad, event);
2018
2019     gst_object_unref (pad);
2020   }
2021 }
2022
2023 static void
2024 gst_ximagesink_navigation_init (GstNavigationInterface * iface)
2025 {
2026   iface->send_event = gst_ximagesink_navigation_send_event;
2027 }
2028
2029 static void
2030 gst_ximagesink_set_window_handle (GstXOverlay * overlay, guintptr id)
2031 {
2032   XID xwindow_id = id;
2033   GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
2034   GstXWindow *xwindow = NULL;
2035   XWindowAttributes attr;
2036
2037   /* We acquire the stream lock while setting this window in the element.
2038      We are basically cleaning tons of stuff replacing the old window, putting
2039      images while we do that would surely crash */
2040   g_mutex_lock (ximagesink->flow_lock);
2041
2042   /* If we already use that window return */
2043   if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) {
2044     g_mutex_unlock (ximagesink->flow_lock);
2045     return;
2046   }
2047
2048   /* If the element has not initialized the X11 context try to do so */
2049   if (!ximagesink->xcontext &&
2050       !(ximagesink->xcontext = gst_ximagesink_xcontext_get (ximagesink))) {
2051     g_mutex_unlock (ximagesink->flow_lock);
2052     /* we have thrown a GST_ELEMENT_ERROR now */
2053     return;
2054   }
2055
2056   /* If a window is there already we destroy it */
2057   if (ximagesink->xwindow) {
2058     gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
2059     ximagesink->xwindow = NULL;
2060   }
2061
2062   /* If the xid is 0 we go back to an internal window */
2063   if (xwindow_id == 0) {
2064     /* If no width/height caps nego did not happen window will be created
2065        during caps nego then */
2066     if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
2067       xwindow = gst_ximagesink_xwindow_new (ximagesink,
2068           GST_VIDEO_SINK_WIDTH (ximagesink),
2069           GST_VIDEO_SINK_HEIGHT (ximagesink));
2070     }
2071   } else {
2072     xwindow = g_new0 (GstXWindow, 1);
2073
2074     xwindow->win = xwindow_id;
2075
2076     /* We get window geometry, set the event we want to receive,
2077        and create a GC */
2078     g_mutex_lock (ximagesink->x_lock);
2079     XGetWindowAttributes (ximagesink->xcontext->disp, xwindow->win, &attr);
2080     xwindow->width = attr.width;
2081     xwindow->height = attr.height;
2082     xwindow->internal = FALSE;
2083     if (ximagesink->handle_events) {
2084       XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
2085           StructureNotifyMask | PointerMotionMask | KeyPressMask |
2086           KeyReleaseMask);
2087     }
2088
2089     xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
2090     g_mutex_unlock (ximagesink->x_lock);
2091   }
2092
2093   if (xwindow)
2094     ximagesink->xwindow = xwindow;
2095
2096   g_mutex_unlock (ximagesink->flow_lock);
2097 }
2098
2099 static void
2100 gst_ximagesink_expose (GstXOverlay * overlay)
2101 {
2102   GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
2103
2104   gst_ximagesink_xwindow_update_geometry (ximagesink);
2105   gst_ximagesink_ximage_put (ximagesink, NULL);
2106 }
2107
2108 static void
2109 gst_ximagesink_set_event_handling (GstXOverlay * overlay,
2110     gboolean handle_events)
2111 {
2112   GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
2113
2114   ximagesink->handle_events = handle_events;
2115
2116   g_mutex_lock (ximagesink->flow_lock);
2117
2118   if (G_UNLIKELY (!ximagesink->xwindow)) {
2119     g_mutex_unlock (ximagesink->flow_lock);
2120     return;
2121   }
2122
2123   g_mutex_lock (ximagesink->x_lock);
2124
2125   if (handle_events) {
2126     if (ximagesink->xwindow->internal) {
2127       XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
2128           ExposureMask | StructureNotifyMask | PointerMotionMask |
2129           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
2130     } else {
2131       XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
2132           ExposureMask | StructureNotifyMask | PointerMotionMask |
2133           KeyPressMask | KeyReleaseMask);
2134     }
2135   } else {
2136     XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0);
2137   }
2138
2139   g_mutex_unlock (ximagesink->x_lock);
2140
2141   g_mutex_unlock (ximagesink->flow_lock);
2142 }
2143
2144 static void
2145 gst_ximagesink_xoverlay_init (GstXOverlayClass * iface)
2146 {
2147   iface->set_window_handle = gst_ximagesink_set_window_handle;
2148   iface->expose = gst_ximagesink_expose;
2149   iface->handle_events = gst_ximagesink_set_event_handling;
2150 }
2151
2152 /* =========================================== */
2153 /*                                             */
2154 /*              Init & Class init              */
2155 /*                                             */
2156 /* =========================================== */
2157
2158 static void
2159 gst_ximagesink_set_property (GObject * object, guint prop_id,
2160     const GValue * value, GParamSpec * pspec)
2161 {
2162   GstXImageSink *ximagesink;
2163
2164   g_return_if_fail (GST_IS_XIMAGESINK (object));
2165
2166   ximagesink = GST_XIMAGESINK (object);
2167
2168   switch (prop_id) {
2169     case PROP_DISPLAY:
2170       ximagesink->display_name = g_strdup (g_value_get_string (value));
2171       break;
2172     case PROP_SYNCHRONOUS:
2173       ximagesink->synchronous = g_value_get_boolean (value);
2174       if (ximagesink->xcontext) {
2175         GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
2176             ximagesink->synchronous ? "TRUE" : "FALSE");
2177         g_mutex_lock (ximagesink->x_lock);
2178         XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
2179         g_mutex_unlock (ximagesink->x_lock);
2180       }
2181       break;
2182     case PROP_FORCE_ASPECT_RATIO:
2183       ximagesink->keep_aspect = g_value_get_boolean (value);
2184       break;
2185     case PROP_PIXEL_ASPECT_RATIO:
2186     {
2187       GValue *tmp;
2188
2189       tmp = g_new0 (GValue, 1);
2190       g_value_init (tmp, GST_TYPE_FRACTION);
2191
2192       if (!g_value_transform (value, tmp)) {
2193         GST_WARNING_OBJECT (ximagesink,
2194             "Could not transform string to aspect ratio");
2195         g_free (tmp);
2196       } else {
2197         GST_DEBUG_OBJECT (ximagesink, "set PAR to %d/%d",
2198             gst_value_get_fraction_numerator (tmp),
2199             gst_value_get_fraction_denominator (tmp));
2200         g_free (ximagesink->par);
2201         ximagesink->par = tmp;
2202       }
2203     }
2204       break;
2205     case PROP_HANDLE_EVENTS:
2206       gst_ximagesink_set_event_handling (GST_X_OVERLAY (ximagesink),
2207           g_value_get_boolean (value));
2208       gst_ximagesink_manage_event_thread (ximagesink);
2209       break;
2210     case PROP_HANDLE_EXPOSE:
2211       ximagesink->handle_expose = g_value_get_boolean (value);
2212       gst_ximagesink_manage_event_thread (ximagesink);
2213       break;
2214     default:
2215       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2216       break;
2217   }
2218 }
2219
2220 static void
2221 gst_ximagesink_get_property (GObject * object, guint prop_id,
2222     GValue * value, GParamSpec * pspec)
2223 {
2224   GstXImageSink *ximagesink;
2225
2226   g_return_if_fail (GST_IS_XIMAGESINK (object));
2227
2228   ximagesink = GST_XIMAGESINK (object);
2229
2230   switch (prop_id) {
2231     case PROP_DISPLAY:
2232       g_value_set_string (value, ximagesink->display_name);
2233       break;
2234     case PROP_SYNCHRONOUS:
2235       g_value_set_boolean (value, ximagesink->synchronous);
2236       break;
2237     case PROP_FORCE_ASPECT_RATIO:
2238       g_value_set_boolean (value, ximagesink->keep_aspect);
2239       break;
2240     case PROP_PIXEL_ASPECT_RATIO:
2241       if (ximagesink->par)
2242         g_value_transform (ximagesink->par, value);
2243       break;
2244     case PROP_HANDLE_EVENTS:
2245       g_value_set_boolean (value, ximagesink->handle_events);
2246       break;
2247     case PROP_HANDLE_EXPOSE:
2248       g_value_set_boolean (value, ximagesink->handle_expose);
2249       break;
2250     case PROP_WINDOW_WIDTH:
2251       if (ximagesink->xwindow)
2252         g_value_set_uint64 (value, ximagesink->xwindow->width);
2253       else
2254         g_value_set_uint64 (value, 0);
2255       break;
2256     case PROP_WINDOW_HEIGHT:
2257       if (ximagesink->xwindow)
2258         g_value_set_uint64 (value, ximagesink->xwindow->height);
2259       else
2260         g_value_set_uint64 (value, 0);
2261       break;
2262     default:
2263       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2264       break;
2265   }
2266 }
2267
2268 static void
2269 gst_ximagesink_reset (GstXImageSink * ximagesink)
2270 {
2271   GThread *thread;
2272
2273   GST_OBJECT_LOCK (ximagesink);
2274   ximagesink->running = FALSE;
2275   /* grab thread and mark it as NULL */
2276   thread = ximagesink->event_thread;
2277   ximagesink->event_thread = NULL;
2278   GST_OBJECT_UNLOCK (ximagesink);
2279
2280   /* Wait for our event thread to finish before we clean up our stuff. */
2281   if (thread)
2282     g_thread_join (thread);
2283
2284   if (ximagesink->ximage) {
2285     gst_buffer_unref (GST_BUFFER_CAST (ximagesink->ximage));
2286     ximagesink->ximage = NULL;
2287   }
2288   if (ximagesink->cur_image) {
2289     gst_buffer_unref (GST_BUFFER_CAST (ximagesink->cur_image));
2290     ximagesink->cur_image = NULL;
2291   }
2292
2293   gst_ximagesink_bufferpool_clear (ximagesink);
2294
2295   g_mutex_lock (ximagesink->flow_lock);
2296   if (ximagesink->xwindow) {
2297     gst_ximagesink_xwindow_clear (ximagesink, ximagesink->xwindow);
2298     gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
2299     ximagesink->xwindow = NULL;
2300   }
2301   g_mutex_unlock (ximagesink->flow_lock);
2302
2303   gst_ximagesink_xcontext_clear (ximagesink);
2304 }
2305
2306 static void
2307 gst_ximagesink_finalize (GObject * object)
2308 {
2309   GstXImageSink *ximagesink;
2310
2311   ximagesink = GST_XIMAGESINK (object);
2312
2313   gst_ximagesink_reset (ximagesink);
2314
2315   if (ximagesink->display_name) {
2316     g_free (ximagesink->display_name);
2317     ximagesink->display_name = NULL;
2318   }
2319   if (ximagesink->par) {
2320     g_free (ximagesink->par);
2321     ximagesink->par = NULL;
2322   }
2323   if (ximagesink->x_lock) {
2324     g_mutex_free (ximagesink->x_lock);
2325     ximagesink->x_lock = NULL;
2326   }
2327   if (ximagesink->flow_lock) {
2328     g_mutex_free (ximagesink->flow_lock);
2329     ximagesink->flow_lock = NULL;
2330   }
2331   if (ximagesink->pool_lock) {
2332     g_mutex_free (ximagesink->pool_lock);
2333     ximagesink->pool_lock = NULL;
2334   }
2335
2336   g_free (ximagesink->media_title);
2337
2338   G_OBJECT_CLASS (parent_class)->finalize (object);
2339 }
2340
2341 static void
2342 gst_ximagesink_init (GstXImageSink * ximagesink)
2343 {
2344   ximagesink->display_name = NULL;
2345   ximagesink->xcontext = NULL;
2346   ximagesink->xwindow = NULL;
2347   ximagesink->ximage = NULL;
2348   ximagesink->cur_image = NULL;
2349
2350   ximagesink->event_thread = NULL;
2351   ximagesink->running = FALSE;
2352
2353   ximagesink->fps_n = 0;
2354   ximagesink->fps_d = 1;
2355
2356   ximagesink->x_lock = g_mutex_new ();
2357   ximagesink->flow_lock = g_mutex_new ();
2358
2359   ximagesink->par = NULL;
2360
2361   ximagesink->pool_lock = g_mutex_new ();
2362   ximagesink->buffer_pool = NULL;
2363
2364   ximagesink->synchronous = FALSE;
2365   ximagesink->keep_aspect = FALSE;
2366   ximagesink->handle_events = TRUE;
2367   ximagesink->handle_expose = TRUE;
2368 }
2369
2370 static void
2371 gst_ximagesink_base_init (gpointer g_class)
2372 {
2373   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
2374
2375   gst_element_class_set_details_simple (element_class,
2376       "Video sink", "Sink/Video",
2377       "A standard X based videosink", "Julien Moutte <julien@moutte.net>");
2378
2379   gst_element_class_add_pad_template (element_class,
2380       gst_static_pad_template_get (&gst_ximagesink_sink_template_factory));
2381 }
2382
2383 static void
2384 gst_ximagesink_class_init (GstXImageSinkClass * klass)
2385 {
2386   GObjectClass *gobject_class;
2387   GstElementClass *gstelement_class;
2388   GstBaseSinkClass *gstbasesink_class;
2389   GstVideoSinkClass *videosink_class;
2390
2391   gobject_class = (GObjectClass *) klass;
2392   gstelement_class = (GstElementClass *) klass;
2393   gstbasesink_class = (GstBaseSinkClass *) klass;
2394   videosink_class = (GstVideoSinkClass *) klass;
2395
2396   parent_class = g_type_class_peek_parent (klass);
2397
2398   gobject_class->finalize = gst_ximagesink_finalize;
2399   gobject_class->set_property = gst_ximagesink_set_property;
2400   gobject_class->get_property = gst_ximagesink_get_property;
2401
2402   g_object_class_install_property (gobject_class, PROP_DISPLAY,
2403       g_param_spec_string ("display", "Display", "X Display name",
2404           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2405   g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
2406       g_param_spec_boolean ("synchronous", "Synchronous",
2407           "When enabled, runs the X display in synchronous mode. "
2408           "(unrelated to A/V sync, used only for debugging)", FALSE,
2409           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2410   g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
2411       g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
2412           "When enabled, reverse caps negotiation (scaling) will respect "
2413           "original aspect ratio", FALSE,
2414           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2415   g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
2416       g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
2417           "The pixel aspect ratio of the device", "1/1",
2418           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2419   g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
2420       g_param_spec_boolean ("handle-events", "Handle XEvents",
2421           "When enabled, XEvents will be selected and handled", TRUE,
2422           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2423   g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
2424       g_param_spec_boolean ("handle-expose", "Handle expose",
2425           "When enabled, "
2426           "the current frame will always be drawn in response to X Expose "
2427           "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2428
2429   /**
2430    * GstXImageSink:window-width
2431    *
2432    * Actual width of the video window.
2433    *
2434    * Since: 0.10.32
2435    */
2436   g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
2437       g_param_spec_uint64 ("window-width", "window-width",
2438           "Width of the window", 0, G_MAXUINT64, 0,
2439           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2440
2441   /**
2442    * GstXImageSink:window-height
2443    *
2444    * Actual height of the video window.
2445    *
2446    * Since: 0.10.32
2447    */
2448   g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
2449       g_param_spec_uint64 ("window-height", "window-height",
2450           "Height of the window", 0, G_MAXUINT64, 0,
2451           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2452
2453   gstelement_class->change_state = gst_ximagesink_change_state;
2454
2455   gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_getcaps);
2456   gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_setcaps);
2457   gstbasesink_class->buffer_alloc =
2458       GST_DEBUG_FUNCPTR (gst_ximagesink_buffer_alloc);
2459   gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_ximagesink_get_times);
2460   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_ximagesink_event);
2461
2462   videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_ximagesink_show_frame);
2463 }
2464
2465 /* ============================================================= */
2466 /*                                                               */
2467 /*                       Public Methods                          */
2468 /*                                                               */
2469 /* ============================================================= */
2470
2471 /* =========================================== */
2472 /*                                             */
2473 /*          Object typing & Creation           */
2474 /*                                             */
2475 /* =========================================== */
2476
2477 GType
2478 gst_ximagesink_get_type (void)
2479 {
2480   static GType ximagesink_type = 0;
2481
2482   if (!ximagesink_type) {
2483     static const GTypeInfo ximagesink_info = {
2484       sizeof (GstXImageSinkClass),
2485       gst_ximagesink_base_init,
2486       NULL,
2487       (GClassInitFunc) gst_ximagesink_class_init,
2488       NULL,
2489       NULL,
2490       sizeof (GstXImageSink), 0, (GInstanceInitFunc) gst_ximagesink_init,
2491     };
2492     static const GInterfaceInfo iface_info = {
2493       (GInterfaceInitFunc) gst_ximagesink_interface_init, NULL, NULL,
2494     };
2495     static const GInterfaceInfo navigation_info = {
2496       (GInterfaceInitFunc) gst_ximagesink_navigation_init, NULL, NULL,
2497     };
2498     static const GInterfaceInfo overlay_info = {
2499       (GInterfaceInitFunc) gst_ximagesink_xoverlay_init, NULL, NULL,
2500     };
2501
2502     ximagesink_type = g_type_register_static (GST_TYPE_VIDEO_SINK,
2503         "GstXImageSink", &ximagesink_info, 0);
2504
2505     g_type_add_interface_static (ximagesink_type, GST_TYPE_IMPLEMENTS_INTERFACE,
2506         &iface_info);
2507     g_type_add_interface_static (ximagesink_type, GST_TYPE_NAVIGATION,
2508         &navigation_info);
2509     g_type_add_interface_static (ximagesink_type, GST_TYPE_X_OVERLAY,
2510         &overlay_info);
2511
2512     /* register type and create class in a more safe place instead of at
2513      * runtime since the type registration and class creation is not
2514      * threadsafe. */
2515     g_type_class_ref (gst_ximage_buffer_get_type ());
2516   }
2517
2518   return ximagesink_type;
2519 }