36e678d19dc44fde16626d56bcd5b9f3fbfc851c
[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., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /**
21  * SECTION:element-ximagesink
22  * @title: ximagesink
23  *
24  * XImageSink renders video frames to a drawable (XWindow) on a local or remote
25  * display. This element can receive a Window ID from the application through
26  * the #GstVideoOverlay interface and will then render video frames in this
27  * drawable. If no Window ID was provided by the application, the element will
28  * create its own internal window and render into it.
29  *
30  * ## Scaling
31  *
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  *
44  * ## Events
45  *
46  * XImageSink creates a thread to handle events coming from the drawable. There
47  * are several kind of events that can be grouped in 2 big categories: input
48  * events and window state related events. Input events will be translated to
49  * navigation events and pushed upstream for other elements to react on them.
50  * This includes events such as pointer moves, key press/release, clicks etc...
51  * Other events are used to handle the drawable appearance even when the data
52  * is not flowing (GST_STATE_PAUSED). That means that even when the element is
53  * paused, it will receive expose events from the drawable and draw the latest
54  * frame with correct borders/aspect-ratio.
55  *
56  * ## Pixel aspect ratio
57  *
58  * When changing state to GST_STATE_READY, XImageSink will open a connection to
59  * the display specified in the #GstXImageSink:display property or the default
60  * display if nothing specified. Once this connection is open it will inspect
61  * the display configuration including the physical display geometry and
62  * then calculate the pixel aspect ratio. When caps negotiation will occur, the
63  * video sink will set the calculated pixel aspect ratio on the caps to make
64  * sure that incoming video frames will have the correct pixel aspect ratio for
65  * this display. Sometimes the calculated pixel aspect ratio can be wrong, it is
66  * then possible to enforce a specific pixel aspect ratio using the
67  * #GstXImageSink:pixel-aspect-ratio property.
68  *
69  * ## Examples
70  * |[
71  * gst-launch-1.0 -v videotestsrc ! queue ! ximagesink
72  * ]|
73  *  A pipeline to test reverse negotiation. When the test video signal appears
74  * you can resize the window and see that scaled buffers of the desired size are
75  * going to arrive with a short delay. This illustrates how buffers of desired
76  * size are allocated along the way. If you take away the queue, scaling will
77  * happen almost immediately.
78  * |[
79  * gst-launch-1.0 -v videotestsrc ! navigationtest ! videoconvert ! ximagesink
80  * ]|
81  *  A pipeline to test navigation events.
82  * While moving the mouse pointer over the test signal you will see a black box
83  * following the mouse pointer. If you press the mouse button somewhere on the
84  * video and release it somewhere else a green box will appear where you pressed
85  * the button and a red one where you released it. (The navigationtest element
86  * is part of gst-plugins-good.)
87  * |[
88  * gst-launch-1.0 -v videotestsrc ! video/x-raw, pixel-aspect-ratio=(fraction)4/3 ! videoscale ! ximagesink
89  * ]|
90  *  This is faking a 4/3 pixel aspect ratio caps on video frames produced by
91  * videotestsrc, in most cases the pixel aspect ratio of the display will be
92  * 1/1. This means that videoscale will have to do the scaling to convert
93  * incoming frames to a size that will match the display pixel aspect ratio
94  * (from 320x240 to 320x180 in this case). Note that you might have to escape
95  * some characters for your shell like '\(fraction\)'.
96  *
97  */
98
99 #ifdef HAVE_CONFIG_H
100 #include "config.h"
101 #endif
102
103 /* Our interfaces */
104 #include <gst/video/navigation.h>
105 #include <gst/video/videooverlay.h>
106
107 #include <gst/video/gstvideometa.h>
108
109 /* Object header */
110 #include "ximagesink.h"
111
112 /* Debugging category */
113 #include <gst/gstinfo.h>
114
115 /* for XkbKeycodeToKeysym */
116 #include <X11/XKBlib.h>
117
118 GST_DEBUG_CATEGORY_EXTERN (gst_debug_x_image_sink);
119 GST_DEBUG_CATEGORY_EXTERN (CAT_PERFORMANCE);
120 #define GST_CAT_DEFAULT gst_debug_x_image_sink
121
122 typedef struct
123 {
124   unsigned long flags;
125   unsigned long functions;
126   unsigned long decorations;
127   long input_mode;
128   unsigned long status;
129 }
130 MotifWmHints, MwmHints;
131
132 #define MWM_HINTS_DECORATIONS   (1L << 1)
133
134 static void gst_x_image_sink_reset (GstXImageSink * ximagesink);
135 static void gst_x_image_sink_xwindow_update_geometry (GstXImageSink *
136     ximagesink);
137 static void gst_x_image_sink_expose (GstVideoOverlay * overlay);
138
139 static GstStaticPadTemplate gst_x_image_sink_sink_template_factory =
140 GST_STATIC_PAD_TEMPLATE ("sink",
141     GST_PAD_SINK,
142     GST_PAD_ALWAYS,
143     GST_STATIC_CAPS ("video/x-raw, "
144         "framerate = (fraction) [ 0, MAX ], "
145         "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
146     );
147
148 enum
149 {
150   PROP_0,
151   PROP_DISPLAY,
152   PROP_SYNCHRONOUS,
153   PROP_PIXEL_ASPECT_RATIO,
154   PROP_FORCE_ASPECT_RATIO,
155   PROP_HANDLE_EVENTS,
156   PROP_HANDLE_EXPOSE,
157   PROP_WINDOW_WIDTH,
158   PROP_WINDOW_HEIGHT
159 };
160
161 /* ============================================================= */
162 /*                                                               */
163 /*                       Public Methods                          */
164 /*                                                               */
165 /* ============================================================= */
166
167 /* =========================================== */
168 /*                                             */
169 /*          Object typing & Creation           */
170 /*                                             */
171 /* =========================================== */
172 static void gst_x_image_sink_navigation_init (GstNavigationInterface * iface);
173 static void gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface *
174     iface);
175 #define gst_x_image_sink_parent_class parent_class
176 G_DEFINE_TYPE_WITH_CODE (GstXImageSink, gst_x_image_sink, GST_TYPE_VIDEO_SINK,
177     G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
178         gst_x_image_sink_navigation_init);
179     G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
180         gst_x_image_sink_video_overlay_init));
181
182 /* ============================================================= */
183 /*                                                               */
184 /*                       Private Methods                         */
185 /*                                                               */
186 /* ============================================================= */
187
188 /* X11 stuff */
189
190 /* We are called with the x_lock taken */
191 static void
192 gst_x_image_sink_xwindow_draw_borders (GstXImageSink * ximagesink,
193     GstXWindow * xwindow, GstVideoRectangle rect)
194 {
195   g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
196   g_return_if_fail (xwindow != NULL);
197
198   XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
199       ximagesink->xcontext->black);
200
201   /* Left border */
202   if (rect.x > 0) {
203     XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
204         0, 0, rect.x, xwindow->height);
205   }
206
207   /* Right border */
208   if ((rect.x + rect.w) < xwindow->width) {
209     XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
210         rect.x + rect.w, 0, xwindow->width, xwindow->height);
211   }
212
213   /* Top border */
214   if (rect.y > 0) {
215     XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
216         0, 0, xwindow->width, rect.y);
217   }
218
219   /* Bottom border */
220   if ((rect.y + rect.h) < xwindow->height) {
221     XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
222         0, rect.y + rect.h, xwindow->width, xwindow->height);
223   }
224 }
225
226 /* This function puts a GstXImageBuffer on a GstXImageSink's window */
227 static gboolean
228 gst_x_image_sink_ximage_put (GstXImageSink * ximagesink, GstBuffer * ximage)
229 {
230   GstXImageMemory *mem;
231   GstVideoCropMeta *crop;
232   GstVideoRectangle src = { 0, };
233   GstVideoRectangle dst = { 0, };
234   GstVideoRectangle result;
235   gboolean draw_border = FALSE;
236
237   /* We take the flow_lock. If expose is in there we don't want to run
238      concurrently from the data flow thread */
239   g_mutex_lock (&ximagesink->flow_lock);
240
241   if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
242     g_mutex_unlock (&ximagesink->flow_lock);
243     return FALSE;
244   }
245
246   /* Draw borders when displaying the first frame. After this
247      draw borders only on expose event or caps change (ximagesink->draw_border = TRUE). */
248   if (!ximagesink->cur_image || ximagesink->draw_border) {
249     draw_border = TRUE;
250   }
251
252   /* Store a reference to the last image we put, lose the previous one */
253   if (ximage && ximagesink->cur_image != ximage) {
254     if (ximagesink->cur_image) {
255       GST_LOG_OBJECT (ximagesink, "unreffing %p", ximagesink->cur_image);
256       gst_buffer_unref (ximagesink->cur_image);
257     }
258     GST_LOG_OBJECT (ximagesink, "reffing %p as our current image", ximage);
259     ximagesink->cur_image = gst_buffer_ref (ximage);
260   }
261
262   /* Expose sends a NULL image, we take the latest frame */
263   if (!ximage) {
264     draw_border = TRUE;
265     if (ximagesink->cur_image) {
266       ximage = ximagesink->cur_image;
267     } else {
268       g_mutex_unlock (&ximagesink->flow_lock);
269       return TRUE;
270     }
271   }
272
273   mem = (GstXImageMemory *) gst_buffer_peek_memory (ximage, 0);
274   crop = gst_buffer_get_video_crop_meta (ximage);
275
276   if (crop) {
277     src.x = crop->x + mem->x;
278     src.y = crop->y + mem->y;
279     src.w = crop->width;
280     src.h = crop->height;
281     GST_LOG_OBJECT (ximagesink,
282         "crop %dx%d-%dx%d", crop->x, crop->y, crop->width, crop->height);
283   } else {
284     src.x = mem->x;
285     src.y = mem->y;
286     src.w = mem->width;
287     src.h = mem->height;
288   }
289   dst.w = ximagesink->xwindow->width;
290   dst.h = ximagesink->xwindow->height;
291
292   gst_video_sink_center_rect (src, dst, &result, FALSE);
293
294   g_mutex_lock (&ximagesink->x_lock);
295
296   if (draw_border) {
297     gst_x_image_sink_xwindow_draw_borders (ximagesink, ximagesink->xwindow,
298         result);
299     ximagesink->draw_border = FALSE;
300   }
301 #ifdef HAVE_XSHM
302   if (ximagesink->xcontext->use_xshm) {
303     GST_LOG_OBJECT (ximagesink,
304         "XShmPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
305         ximage, 0, 0, result.x, result.y, result.w, result.h,
306         ximagesink->xwindow->width, ximagesink->xwindow->height);
307     XShmPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
308         ximagesink->xwindow->gc, mem->ximage, src.x, src.y, result.x, result.y,
309         result.w, result.h, FALSE);
310   } else
311 #endif /* HAVE_XSHM */
312   {
313     GST_LOG_OBJECT (ximagesink,
314         "XPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
315         ximage, 0, 0, result.x, result.y, result.w, result.h,
316         ximagesink->xwindow->width, ximagesink->xwindow->height);
317     XPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
318         ximagesink->xwindow->gc, mem->ximage, src.x, src.y, result.x, result.y,
319         result.w, result.h);
320   }
321
322   XSync (ximagesink->xcontext->disp, FALSE);
323
324   g_mutex_unlock (&ximagesink->x_lock);
325
326   g_mutex_unlock (&ximagesink->flow_lock);
327
328   return TRUE;
329 }
330
331 static gboolean
332 gst_x_image_sink_xwindow_decorate (GstXImageSink * ximagesink,
333     GstXWindow * window)
334 {
335   Atom hints_atom = None;
336   MotifWmHints *hints;
337
338   g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), FALSE);
339   g_return_val_if_fail (window != NULL, FALSE);
340
341   g_mutex_lock (&ximagesink->x_lock);
342
343   hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS",
344       True);
345   if (hints_atom == None) {
346     g_mutex_unlock (&ximagesink->x_lock);
347     return FALSE;
348   }
349
350   hints = g_malloc0 (sizeof (MotifWmHints));
351
352   hints->flags |= MWM_HINTS_DECORATIONS;
353   hints->decorations = 1 << 0;
354
355   XChangeProperty (ximagesink->xcontext->disp, window->win,
356       hints_atom, hints_atom, 32, PropModeReplace,
357       (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
358
359   XSync (ximagesink->xcontext->disp, FALSE);
360
361   g_mutex_unlock (&ximagesink->x_lock);
362
363   g_free (hints);
364
365   return TRUE;
366 }
367
368 static void
369 gst_x_image_sink_xwindow_set_title (GstXImageSink * ximagesink,
370     GstXWindow * xwindow, const gchar * media_title)
371 {
372   if (media_title) {
373     g_free (ximagesink->media_title);
374     ximagesink->media_title = g_strdup (media_title);
375   }
376   if (xwindow) {
377     /* we have a window */
378     if (xwindow->internal) {
379       XTextProperty xproperty;
380       XClassHint *hint = XAllocClassHint ();
381       const gchar *app_name;
382       const gchar *title = NULL;
383       gchar *title_mem = NULL;
384
385       /* set application name as a title */
386       app_name = g_get_application_name ();
387
388       if (app_name && ximagesink->media_title) {
389         title = title_mem = g_strconcat (ximagesink->media_title, " : ",
390             app_name, NULL);
391       } else if (app_name) {
392         title = app_name;
393       } else if (ximagesink->media_title) {
394         title = ximagesink->media_title;
395       }
396
397       if (title) {
398         if ((XStringListToTextProperty (((char **) &title), 1,
399                     &xproperty)) != 0) {
400           XSetWMName (ximagesink->xcontext->disp, xwindow->win, &xproperty);
401           XFree (xproperty.value);
402         }
403
404         g_free (title_mem);
405       }
406
407       if (hint) {
408         hint->res_name = (char *) app_name;
409         hint->res_class = (char *) "GStreamer";
410         XSetClassHint (ximagesink->xcontext->disp, xwindow->win, hint);
411       }
412       XFree (hint);
413     }
414   }
415 }
416
417 /* This function handles a GstXWindow creation */
418 static GstXWindow *
419 gst_x_image_sink_xwindow_new (GstXImageSink * ximagesink, gint width,
420     gint height)
421 {
422   GstXWindow *xwindow = NULL;
423   XGCValues values;
424
425   g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
426
427   xwindow = g_new0 (GstXWindow, 1);
428
429   xwindow->width = width;
430   xwindow->height = height;
431   xwindow->internal = TRUE;
432
433   g_mutex_lock (&ximagesink->x_lock);
434
435   xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp,
436       ximagesink->xcontext->root,
437       0, 0, width, height, 0, 0, ximagesink->xcontext->black);
438
439   /* We have to do that to prevent X from redrawing the background on 
440      ConfigureNotify. This takes away flickering of video when resizing. */
441   XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None);
442
443   /* set application name as a title */
444   gst_x_image_sink_xwindow_set_title (ximagesink, xwindow, NULL);
445
446   if (ximagesink->handle_events) {
447     Atom wm_delete;
448
449     XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
450         StructureNotifyMask | PointerMotionMask | KeyPressMask |
451         KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
452
453     /* Tell the window manager we'd like delete client messages instead of
454      * being killed */
455     wm_delete = XInternAtom (ximagesink->xcontext->disp,
456         "WM_DELETE_WINDOW", False);
457     (void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win,
458         &wm_delete, 1);
459   }
460
461   xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
462       0, &values);
463
464   XMapRaised (ximagesink->xcontext->disp, xwindow->win);
465
466   XSync (ximagesink->xcontext->disp, FALSE);
467
468   g_mutex_unlock (&ximagesink->x_lock);
469
470   gst_x_image_sink_xwindow_decorate (ximagesink, xwindow);
471
472   gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (ximagesink),
473       xwindow->win);
474
475   return xwindow;
476 }
477
478 /* This function destroys a GstXWindow */
479 static void
480 gst_x_image_sink_xwindow_destroy (GstXImageSink * ximagesink,
481     GstXWindow * xwindow)
482 {
483   g_return_if_fail (xwindow != NULL);
484   g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
485
486   g_mutex_lock (&ximagesink->x_lock);
487
488   /* If we did not create that window we just free the GC and let it live */
489   if (xwindow->internal)
490     XDestroyWindow (ximagesink->xcontext->disp, xwindow->win);
491   else
492     XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0);
493
494   XFreeGC (ximagesink->xcontext->disp, xwindow->gc);
495
496   XSync (ximagesink->xcontext->disp, FALSE);
497
498   g_mutex_unlock (&ximagesink->x_lock);
499
500   g_free (xwindow);
501 }
502
503 static void
504 gst_x_image_sink_xwindow_update_geometry (GstXImageSink * ximagesink)
505 {
506   XWindowAttributes attr;
507   gboolean reconfigure;
508
509   g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
510
511   /* Update the window geometry */
512   g_mutex_lock (&ximagesink->x_lock);
513   if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
514     g_mutex_unlock (&ximagesink->x_lock);
515     return;
516   }
517
518   XGetWindowAttributes (ximagesink->xcontext->disp,
519       ximagesink->xwindow->win, &attr);
520
521   /* Check if we would suggest a different width/height now */
522   reconfigure = (ximagesink->xwindow->width != attr.width)
523       || (ximagesink->xwindow->height != attr.height);
524   ximagesink->xwindow->width = attr.width;
525   ximagesink->xwindow->height = attr.height;
526
527   g_mutex_unlock (&ximagesink->x_lock);
528
529   if (reconfigure)
530     gst_pad_push_event (GST_BASE_SINK (ximagesink)->sinkpad,
531         gst_event_new_reconfigure ());
532 }
533
534 static void
535 gst_x_image_sink_xwindow_clear (GstXImageSink * ximagesink,
536     GstXWindow * xwindow)
537 {
538   g_return_if_fail (xwindow != NULL);
539   g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
540
541   g_mutex_lock (&ximagesink->x_lock);
542
543   XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
544       ximagesink->xcontext->black);
545
546   XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
547       0, 0, xwindow->width, xwindow->height);
548
549   XSync (ximagesink->xcontext->disp, FALSE);
550
551   g_mutex_unlock (&ximagesink->x_lock);
552 }
553
554 /* This function handles XEvents that might be in the queue. It generates
555    GstEvent that will be sent upstream in the pipeline to handle interactivity
556    and navigation.*/
557 static void
558 gst_x_image_sink_handle_xevents (GstXImageSink * ximagesink)
559 {
560   XEvent e;
561   gint pointer_x = 0, pointer_y = 0;
562   gboolean pointer_moved = FALSE;
563   gboolean exposed = FALSE, configured = FALSE;
564
565   g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
566
567   /* Then we get all pointer motion events, only the last position is
568      interesting. */
569   g_mutex_lock (&ximagesink->flow_lock);
570   g_mutex_lock (&ximagesink->x_lock);
571   while (XCheckWindowEvent (ximagesink->xcontext->disp,
572           ximagesink->xwindow->win, PointerMotionMask, &e)) {
573     g_mutex_unlock (&ximagesink->x_lock);
574     g_mutex_unlock (&ximagesink->flow_lock);
575
576     switch (e.type) {
577       case MotionNotify:
578         pointer_x = e.xmotion.x;
579         pointer_y = e.xmotion.y;
580         pointer_moved = TRUE;
581         break;
582       default:
583         break;
584     }
585     g_mutex_lock (&ximagesink->flow_lock);
586     g_mutex_lock (&ximagesink->x_lock);
587   }
588
589   if (pointer_moved) {
590     g_mutex_unlock (&ximagesink->x_lock);
591     g_mutex_unlock (&ximagesink->flow_lock);
592
593     GST_DEBUG ("ximagesink pointer moved over window at %d,%d",
594         pointer_x, pointer_y);
595     gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
596         "mouse-move", 0, pointer_x, pointer_y);
597
598     g_mutex_lock (&ximagesink->flow_lock);
599     g_mutex_lock (&ximagesink->x_lock);
600   }
601
602   /* We get all remaining events on our window to throw them upstream */
603   while (XCheckWindowEvent (ximagesink->xcontext->disp,
604           ximagesink->xwindow->win,
605           KeyPressMask | KeyReleaseMask |
606           ButtonPressMask | ButtonReleaseMask, &e)) {
607     KeySym keysym;
608     const char *key_str = NULL;
609
610     /* We lock only for the X function call */
611     g_mutex_unlock (&ximagesink->x_lock);
612     g_mutex_unlock (&ximagesink->flow_lock);
613
614     switch (e.type) {
615       case ButtonPress:
616         /* Mouse button pressed/released over our window. We send upstream
617            events for interactivity/navigation */
618         GST_DEBUG ("ximagesink button %d pressed over window at %d,%d",
619             e.xbutton.button, e.xbutton.x, e.xbutton.x);
620         gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
621             "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
622         break;
623       case ButtonRelease:
624         GST_DEBUG ("ximagesink button %d release over window at %d,%d",
625             e.xbutton.button, e.xbutton.x, e.xbutton.x);
626         gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
627             "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
628         break;
629       case KeyPress:
630       case KeyRelease:
631         /* Key pressed/released over our window. We send upstream
632            events for interactivity/navigation */
633         g_mutex_lock (&ximagesink->x_lock);
634         if (ximagesink->xcontext->use_xkb) {
635           keysym = XkbKeycodeToKeysym (ximagesink->xcontext->disp,
636               e.xkey.keycode, 0, 0);
637         } else {
638           keysym = XKeycodeToKeysym (ximagesink->xcontext->disp,
639               e.xkey.keycode, 0);
640         }
641         if (keysym != NoSymbol) {
642           key_str = XKeysymToString (keysym);
643         } else {
644           key_str = "unknown";
645         }
646         g_mutex_unlock (&ximagesink->x_lock);
647         GST_DEBUG_OBJECT (ximagesink,
648             "key %d pressed over window at %d,%d (%s)",
649             e.xkey.keycode, e.xkey.x, e.xkey.y, key_str);
650         gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
651             e.type == KeyPress ? "key-press" : "key-release", key_str);
652         break;
653       default:
654         GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)",
655             e.type);
656     }
657     g_mutex_lock (&ximagesink->flow_lock);
658     g_mutex_lock (&ximagesink->x_lock);
659   }
660
661   /* Handle Expose */
662   while (XCheckWindowEvent (ximagesink->xcontext->disp,
663           ximagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) {
664     switch (e.type) {
665       case Expose:
666         exposed = TRUE;
667         break;
668       case ConfigureNotify:
669         g_mutex_unlock (&ximagesink->x_lock);
670         gst_x_image_sink_xwindow_update_geometry (ximagesink);
671         g_mutex_lock (&ximagesink->x_lock);
672         configured = TRUE;
673         break;
674       default:
675         break;
676     }
677   }
678
679   if (ximagesink->handle_expose && (exposed || configured)) {
680     g_mutex_unlock (&ximagesink->x_lock);
681     g_mutex_unlock (&ximagesink->flow_lock);
682
683     gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink));
684
685     g_mutex_lock (&ximagesink->flow_lock);
686     g_mutex_lock (&ximagesink->x_lock);
687   }
688
689   /* Handle Display events */
690   while (XPending (ximagesink->xcontext->disp)) {
691     XNextEvent (ximagesink->xcontext->disp, &e);
692
693     switch (e.type) {
694       case ClientMessage:{
695         Atom wm_delete;
696
697         wm_delete = XInternAtom (ximagesink->xcontext->disp,
698             "WM_DELETE_WINDOW", False);
699         if (wm_delete == (Atom) e.xclient.data.l[0]) {
700           /* Handle window deletion by posting an error on the bus */
701           GST_ELEMENT_ERROR (ximagesink, RESOURCE, NOT_FOUND,
702               ("Output window was closed"), (NULL));
703
704           g_mutex_unlock (&ximagesink->x_lock);
705           gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
706           ximagesink->xwindow = NULL;
707           g_mutex_lock (&ximagesink->x_lock);
708         }
709         break;
710       }
711       default:
712         break;
713     }
714   }
715
716   g_mutex_unlock (&ximagesink->x_lock);
717   g_mutex_unlock (&ximagesink->flow_lock);
718 }
719
720 static gpointer
721 gst_x_image_sink_event_thread (GstXImageSink * ximagesink)
722 {
723   g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
724
725   GST_OBJECT_LOCK (ximagesink);
726   while (ximagesink->running) {
727     GST_OBJECT_UNLOCK (ximagesink);
728
729     if (ximagesink->xwindow) {
730       gst_x_image_sink_handle_xevents (ximagesink);
731     }
732     /* FIXME: do we want to align this with the framerate or anything else? */
733     g_usleep (G_USEC_PER_SEC / 20);
734
735     GST_OBJECT_LOCK (ximagesink);
736   }
737   GST_OBJECT_UNLOCK (ximagesink);
738
739   return NULL;
740 }
741
742 static void
743 gst_x_image_sink_manage_event_thread (GstXImageSink * ximagesink)
744 {
745   GThread *thread = NULL;
746
747   /* don't start the thread too early */
748   if (ximagesink->xcontext == NULL) {
749     return;
750   }
751
752   GST_OBJECT_LOCK (ximagesink);
753   if (ximagesink->handle_expose || ximagesink->handle_events) {
754     if (!ximagesink->event_thread) {
755       /* Setup our event listening thread */
756       GST_DEBUG_OBJECT (ximagesink, "run xevent thread, expose %d, events %d",
757           ximagesink->handle_expose, ximagesink->handle_events);
758       ximagesink->running = TRUE;
759       ximagesink->event_thread = g_thread_try_new ("ximagesink-events",
760           (GThreadFunc) gst_x_image_sink_event_thread, ximagesink, NULL);
761     }
762   } else {
763     if (ximagesink->event_thread) {
764       GST_DEBUG_OBJECT (ximagesink, "stop xevent thread, expose %d, events %d",
765           ximagesink->handle_expose, ximagesink->handle_events);
766       ximagesink->running = FALSE;
767       /* grab thread and mark it as NULL */
768       thread = ximagesink->event_thread;
769       ximagesink->event_thread = NULL;
770     }
771   }
772   GST_OBJECT_UNLOCK (ximagesink);
773
774   /* Wait for our event thread to finish */
775   if (thread)
776     g_thread_join (thread);
777
778 }
779
780
781 /* This function calculates the pixel aspect ratio based on the properties
782  * in the xcontext structure and stores it there. */
783 static void
784 gst_x_image_sink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
785 {
786   static const gint par[][2] = {
787     {1, 1},                     /* regular screen */
788     {16, 15},                   /* PAL TV */
789     {11, 10},                   /* 525 line Rec.601 video */
790     {54, 59},                   /* 625 line Rec.601 video */
791     {64, 45},                   /* 1280x1024 on 16:9 display */
792     {5, 3},                     /* 1280x1024 on 4:3 display */
793     {4, 3}                      /*  800x600 on 16:9 display */
794   };
795   gint i;
796   gint index;
797   gdouble ratio;
798   gdouble delta;
799
800 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
801
802   /* first calculate the "real" ratio based on the X values;
803    * which is the "physical" w/h divided by the w/h in pixels of the display */
804   ratio = (gdouble) (xcontext->widthmm * xcontext->height)
805       / (xcontext->heightmm * xcontext->width);
806
807   /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
808    * override here */
809   if (xcontext->width == 720 && xcontext->height == 576) {
810     ratio = 4.0 * 576 / (3.0 * 720);
811   }
812   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
813
814   /* now find the one from par[][2] with the lowest delta to the real one */
815   delta = DELTA (0);
816   index = 0;
817
818   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
819     gdouble this_delta = DELTA (i);
820
821     if (this_delta < delta) {
822       index = i;
823       delta = this_delta;
824     }
825   }
826
827   GST_DEBUG ("Decided on index %d (%d/%d)", index,
828       par[index][0], par[index][1]);
829
830   g_free (xcontext->par);
831   xcontext->par = g_new0 (GValue, 1);
832   g_value_init (xcontext->par, GST_TYPE_FRACTION);
833   gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
834   GST_DEBUG ("set xcontext PAR to %d/%d",
835       gst_value_get_fraction_numerator (xcontext->par),
836       gst_value_get_fraction_denominator (xcontext->par));
837 }
838
839 /* This function gets the X Display and global info about it. Everything is
840    stored in our object and will be cleaned when the object is disposed. Note
841    here that caps for supported format are generated without any window or
842    image creation */
843 static GstXContext *
844 gst_x_image_sink_xcontext_get (GstXImageSink * ximagesink)
845 {
846   GstXContext *xcontext = NULL;
847   XPixmapFormatValues *px_formats = NULL;
848   gint nb_formats = 0, i;
849   gint endianness;
850   GstVideoFormat vformat;
851   guint32 alpha_mask;
852   int opcode, event, err;
853   int major = XkbMajorVersion;
854   int minor = XkbMinorVersion;
855
856   g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
857
858   xcontext = g_new0 (GstXContext, 1);
859
860   g_mutex_lock (&ximagesink->x_lock);
861
862   xcontext->disp = XOpenDisplay (ximagesink->display_name);
863
864   if (!xcontext->disp) {
865     g_mutex_unlock (&ximagesink->x_lock);
866     g_free (xcontext);
867     GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
868         ("Could not initialise X output"), ("Could not open display"));
869     return NULL;
870   }
871
872   xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
873   xcontext->screen_num = DefaultScreen (xcontext->disp);
874   xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
875   xcontext->root = DefaultRootWindow (xcontext->disp);
876   xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
877   xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
878   xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
879
880   xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
881   xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
882   xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
883   xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
884
885   GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm",
886       xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
887
888   gst_x_image_sink_calculate_pixel_aspect_ratio (xcontext);
889
890   /* We get supported pixmap formats at supported depth */
891   px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
892
893   if (!px_formats) {
894     XCloseDisplay (xcontext->disp);
895     g_mutex_unlock (&ximagesink->x_lock);
896     g_free (xcontext->par);
897     g_free (xcontext);
898     GST_ELEMENT_ERROR (ximagesink, RESOURCE, SETTINGS,
899         ("Could not get supported pixmap formats"), (NULL));
900     return NULL;
901   }
902
903   /* We get bpp value corresponding to our running depth */
904   for (i = 0; i < nb_formats; i++) {
905     if (px_formats[i].depth == xcontext->depth)
906       xcontext->bpp = px_formats[i].bits_per_pixel;
907   }
908
909   XFree (px_formats);
910
911   endianness = (ImageByteOrder (xcontext->disp) ==
912       LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
913
914   /* Search for XShm extension support */
915 #ifdef HAVE_XSHM
916   if (XShmQueryExtension (xcontext->disp) &&
917       gst_x_image_sink_check_xshm_calls (ximagesink, xcontext)) {
918     xcontext->use_xshm = TRUE;
919     GST_DEBUG ("ximagesink is using XShm extension");
920   } else
921 #endif /* HAVE_XSHM */
922   {
923     xcontext->use_xshm = FALSE;
924     GST_DEBUG ("ximagesink is not using XShm extension");
925   }
926   if (XkbQueryExtension (xcontext->disp, &opcode, &event, &err, &major, &minor)) {
927     xcontext->use_xkb = TRUE;
928     GST_DEBUG ("ximagesink is using Xkb extension");
929   } else {
930     xcontext->use_xkb = FALSE;
931     GST_DEBUG ("ximagesink is not using Xkb extension");
932   }
933
934   /* extrapolate alpha mask */
935   if (xcontext->depth == 32) {
936     alpha_mask = ~(xcontext->visual->red_mask
937         | xcontext->visual->green_mask | xcontext->visual->blue_mask);
938   } else {
939     alpha_mask = 0;
940   }
941
942   vformat =
943       gst_video_format_from_masks (xcontext->depth, xcontext->bpp, endianness,
944       xcontext->visual->red_mask, xcontext->visual->green_mask,
945       xcontext->visual->blue_mask, alpha_mask);
946
947   if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
948     goto unknown_format;
949
950   /* update object's par with calculated one if not set yet */
951   if (!ximagesink->par) {
952     ximagesink->par = g_new0 (GValue, 1);
953     gst_value_init_and_copy (ximagesink->par, xcontext->par);
954     GST_DEBUG_OBJECT (ximagesink, "set calculated PAR on object's PAR");
955   }
956   xcontext->caps = gst_caps_new_simple ("video/x-raw",
957       "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
958       "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
959       "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
960       "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
961   if (ximagesink->par) {
962     int nom, den;
963
964     nom = gst_value_get_fraction_numerator (ximagesink->par);
965     den = gst_value_get_fraction_denominator (ximagesink->par);
966     gst_caps_set_simple (xcontext->caps, "pixel-aspect-ratio",
967         GST_TYPE_FRACTION, nom, den, NULL);
968   }
969
970   g_mutex_unlock (&ximagesink->x_lock);
971
972   return xcontext;
973
974   /* ERRORS */
975 unknown_format:
976   {
977     GST_ERROR_OBJECT (ximagesink, "unknown format");
978     return NULL;
979   }
980 }
981
982 /* This function cleans the X context. Closing the Display and unrefing the
983    caps for supported formats. */
984 static void
985 gst_x_image_sink_xcontext_clear (GstXImageSink * ximagesink)
986 {
987   GstXContext *xcontext;
988
989   g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
990
991   GST_OBJECT_LOCK (ximagesink);
992   if (ximagesink->xcontext == NULL) {
993     GST_OBJECT_UNLOCK (ximagesink);
994     return;
995   }
996
997   /* Take the xcontext reference and NULL it while we
998    * clean it up, so that any buffer-alloced buffers 
999    * arriving after this will be freed correctly */
1000   xcontext = ximagesink->xcontext;
1001   ximagesink->xcontext = NULL;
1002
1003   GST_OBJECT_UNLOCK (ximagesink);
1004
1005   gst_caps_unref (xcontext->caps);
1006   g_free (xcontext->par);
1007   g_free (ximagesink->par);
1008   ximagesink->par = NULL;
1009
1010   if (xcontext->last_caps)
1011     gst_caps_replace (&xcontext->last_caps, NULL);
1012
1013   g_mutex_lock (&ximagesink->x_lock);
1014
1015   XCloseDisplay (xcontext->disp);
1016
1017   g_mutex_unlock (&ximagesink->x_lock);
1018
1019   g_free (xcontext);
1020 }
1021
1022 /* Element stuff */
1023
1024 static GstCaps *
1025 gst_x_image_sink_getcaps (GstBaseSink * bsink, GstCaps * filter)
1026 {
1027   GstXImageSink *ximagesink;
1028   GstCaps *caps;
1029   int i;
1030
1031   ximagesink = GST_X_IMAGE_SINK (bsink);
1032
1033   g_mutex_lock (&ximagesink->x_lock);
1034   if (ximagesink->xcontext) {
1035     GstCaps *caps;
1036
1037     caps = gst_caps_ref (ximagesink->xcontext->caps);
1038
1039     if (filter) {
1040       GstCaps *intersection;
1041
1042       intersection =
1043           gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1044       gst_caps_unref (caps);
1045       caps = intersection;
1046     }
1047
1048     if (gst_caps_is_empty (caps)) {
1049       g_mutex_unlock (&ximagesink->x_lock);
1050       return caps;
1051     }
1052
1053     if (ximagesink->xwindow && ximagesink->xwindow->width) {
1054       GstStructure *s0, *s1;
1055
1056       caps = gst_caps_make_writable (caps);
1057
1058       /* There can only be a single structure because the xcontext
1059        * caps only have a single structure */
1060       s0 = gst_caps_get_structure (caps, 0);
1061       s1 = gst_structure_copy (gst_caps_get_structure (caps, 0));
1062
1063       gst_structure_set (s0, "width", G_TYPE_INT, ximagesink->xwindow->width,
1064           "height", G_TYPE_INT, ximagesink->xwindow->height, NULL);
1065       gst_caps_append_structure (caps, s1);
1066
1067       /* This will not change the order but will remove the
1068        * fixed width/height caps again if not possible
1069        * upstream */
1070       if (filter) {
1071         GstCaps *intersection;
1072
1073         intersection =
1074             gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1075         gst_caps_unref (caps);
1076         caps = intersection;
1077       }
1078     }
1079
1080     g_mutex_unlock (&ximagesink->x_lock);
1081     return caps;
1082   }
1083   g_mutex_unlock (&ximagesink->x_lock);
1084
1085   /* get a template copy and add the pixel aspect ratio */
1086   caps = gst_pad_get_pad_template_caps (GST_BASE_SINK (ximagesink)->sinkpad);
1087   if (ximagesink->par) {
1088     caps = gst_caps_make_writable (caps);
1089     for (i = 0; i < gst_caps_get_size (caps); ++i) {
1090       GstStructure *structure = gst_caps_get_structure (caps, i);
1091       int nom, den;
1092
1093       nom = gst_value_get_fraction_numerator (ximagesink->par);
1094       den = gst_value_get_fraction_denominator (ximagesink->par);
1095       gst_structure_set (structure, "pixel-aspect-ratio",
1096           GST_TYPE_FRACTION, nom, den, NULL);
1097     }
1098   }
1099
1100   if (filter) {
1101     GstCaps *intersection;
1102
1103     intersection =
1104         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1105     gst_caps_unref (caps);
1106     caps = intersection;
1107   }
1108
1109   return caps;
1110 }
1111
1112 static GstBufferPool *
1113 gst_x_image_sink_create_pool (GstXImageSink * ximagesink, GstCaps * caps,
1114     gsize size, gint min)
1115 {
1116   static GstAllocationParams params = { 0, 15, 0, 0, };
1117   GstBufferPool *pool;
1118   GstStructure *config;
1119
1120   /* create a new pool for the new configuration */
1121   pool = gst_ximage_buffer_pool_new (ximagesink);
1122
1123   config = gst_buffer_pool_get_config (pool);
1124   gst_buffer_pool_config_set_params (config, caps, size, min, 0);
1125   gst_buffer_pool_config_set_allocator (config, NULL, &params);
1126
1127   if (!gst_buffer_pool_set_config (pool, config))
1128     goto config_failed;
1129
1130   return pool;
1131
1132 config_failed:
1133   {
1134     GST_WARNING_OBJECT (ximagesink, "failed setting config");
1135     gst_object_unref (pool);
1136     return NULL;
1137   }
1138 }
1139
1140 static gboolean
1141 gst_x_image_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1142 {
1143   GstXImageSink *ximagesink;
1144   GstStructure *structure;
1145   GstVideoInfo info;
1146   GstBufferPool *newpool, *oldpool;
1147   const GValue *par;
1148
1149   ximagesink = GST_X_IMAGE_SINK (bsink);
1150
1151   if (!ximagesink->xcontext)
1152     return FALSE;
1153
1154   GST_DEBUG_OBJECT (ximagesink,
1155       "sinkconnect possible caps %" GST_PTR_FORMAT " with given caps %"
1156       GST_PTR_FORMAT, ximagesink->xcontext->caps, caps);
1157
1158   /* We intersect those caps with our template to make sure they are correct */
1159   if (!gst_caps_can_intersect (ximagesink->xcontext->caps, caps))
1160     goto incompatible_caps;
1161
1162   if (!gst_video_info_from_caps (&info, caps))
1163     goto invalid_format;
1164
1165   structure = gst_caps_get_structure (caps, 0);
1166   /* if the caps contain pixel-aspect-ratio, they have to match ours,
1167    * otherwise linking should fail */
1168   par = gst_structure_get_value (structure, "pixel-aspect-ratio");
1169   if (par) {
1170     if (ximagesink->par) {
1171       if (gst_value_compare (par, ximagesink->par) != GST_VALUE_EQUAL) {
1172         goto wrong_aspect;
1173       }
1174     } else if (ximagesink->xcontext->par) {
1175       if (gst_value_compare (par, ximagesink->xcontext->par) != GST_VALUE_EQUAL) {
1176         goto wrong_aspect;
1177       }
1178     }
1179   }
1180
1181   GST_VIDEO_SINK_WIDTH (ximagesink) = info.width;
1182   GST_VIDEO_SINK_HEIGHT (ximagesink) = info.height;
1183   ximagesink->fps_n = info.fps_n;
1184   ximagesink->fps_d = info.fps_d;
1185
1186   /* Notify application to set xwindow id now */
1187   g_mutex_lock (&ximagesink->flow_lock);
1188   if (!ximagesink->xwindow) {
1189     g_mutex_unlock (&ximagesink->flow_lock);
1190     gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (ximagesink));
1191   } else {
1192     g_mutex_unlock (&ximagesink->flow_lock);
1193   }
1194
1195   /* Creating our window and our image */
1196   if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 ||
1197       GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0)
1198     goto invalid_size;
1199
1200   g_mutex_lock (&ximagesink->flow_lock);
1201   if (!ximagesink->xwindow) {
1202     ximagesink->xwindow = gst_x_image_sink_xwindow_new (ximagesink,
1203         GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1204   }
1205
1206   ximagesink->info = info;
1207
1208   /* Remember to draw borders for next frame */
1209   ximagesink->draw_border = TRUE;
1210
1211   /* create a new internal pool for the new configuration */
1212   newpool = gst_x_image_sink_create_pool (ximagesink, caps, info.size, 2);
1213
1214   /* we don't activate the internal pool yet as it may not be needed */
1215   oldpool = ximagesink->pool;
1216   ximagesink->pool = newpool;
1217   g_mutex_unlock (&ximagesink->flow_lock);
1218
1219   /* deactivate and unref the old internal pool */
1220   if (oldpool) {
1221     gst_buffer_pool_set_active (oldpool, FALSE);
1222     gst_object_unref (oldpool);
1223   }
1224
1225   return TRUE;
1226
1227   /* ERRORS */
1228 incompatible_caps:
1229   {
1230     GST_ERROR_OBJECT (ximagesink, "caps incompatible");
1231     return FALSE;
1232   }
1233 invalid_format:
1234   {
1235     GST_ERROR_OBJECT (ximagesink, "caps invalid");
1236     return FALSE;
1237   }
1238 wrong_aspect:
1239   {
1240     GST_INFO_OBJECT (ximagesink, "pixel aspect ratio does not match");
1241     return FALSE;
1242   }
1243 invalid_size:
1244   {
1245     GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL),
1246         ("Invalid image size."));
1247     return FALSE;
1248   }
1249 }
1250
1251 static GstStateChangeReturn
1252 gst_x_image_sink_change_state (GstElement * element, GstStateChange transition)
1253 {
1254   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1255   GstXImageSink *ximagesink;
1256   GstXContext *xcontext = NULL;
1257
1258   ximagesink = GST_X_IMAGE_SINK (element);
1259
1260   switch (transition) {
1261     case GST_STATE_CHANGE_NULL_TO_READY:
1262       /* Initializing the XContext */
1263       if (ximagesink->xcontext == NULL) {
1264         xcontext = gst_x_image_sink_xcontext_get (ximagesink);
1265         if (xcontext == NULL) {
1266           ret = GST_STATE_CHANGE_FAILURE;
1267           goto beach;
1268         }
1269         GST_OBJECT_LOCK (ximagesink);
1270         if (xcontext)
1271           ximagesink->xcontext = xcontext;
1272         GST_OBJECT_UNLOCK (ximagesink);
1273       }
1274
1275       /* call XSynchronize with the current value of synchronous */
1276       GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1277           ximagesink->synchronous ? "TRUE" : "FALSE");
1278       g_mutex_lock (&ximagesink->x_lock);
1279       XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1280       g_mutex_unlock (&ximagesink->x_lock);
1281       gst_x_image_sink_manage_event_thread (ximagesink);
1282       break;
1283     case GST_STATE_CHANGE_READY_TO_PAUSED:
1284       g_mutex_lock (&ximagesink->flow_lock);
1285       if (ximagesink->xwindow)
1286         gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
1287       g_mutex_unlock (&ximagesink->flow_lock);
1288       break;
1289     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1290       break;
1291     default:
1292       break;
1293   }
1294
1295   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1296
1297   switch (transition) {
1298     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1299       break;
1300     case GST_STATE_CHANGE_PAUSED_TO_READY:
1301       ximagesink->fps_n = 0;
1302       ximagesink->fps_d = 1;
1303       GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
1304       GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
1305       g_mutex_lock (&ximagesink->flow_lock);
1306       if (ximagesink->pool)
1307         gst_buffer_pool_set_active (ximagesink->pool, FALSE);
1308       g_mutex_unlock (&ximagesink->flow_lock);
1309       break;
1310     case GST_STATE_CHANGE_READY_TO_NULL:
1311       gst_x_image_sink_reset (ximagesink);
1312       break;
1313     default:
1314       break;
1315   }
1316
1317 beach:
1318   return ret;
1319 }
1320
1321 static void
1322 gst_x_image_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1323     GstClockTime * start, GstClockTime * end)
1324 {
1325   GstXImageSink *ximagesink;
1326
1327   ximagesink = GST_X_IMAGE_SINK (bsink);
1328
1329   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1330     *start = GST_BUFFER_TIMESTAMP (buf);
1331     if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1332       *end = *start + GST_BUFFER_DURATION (buf);
1333     } else {
1334       if (ximagesink->fps_n > 0) {
1335         *end = *start +
1336             gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d,
1337             ximagesink->fps_n);
1338       }
1339     }
1340   }
1341 }
1342
1343 static GstFlowReturn
1344 gst_x_image_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1345 {
1346   GstFlowReturn res;
1347   GstXImageSink *ximagesink;
1348   GstXImageMemory *mem;
1349   GstBuffer *to_put = NULL;
1350
1351   ximagesink = GST_X_IMAGE_SINK (vsink);
1352
1353   if (gst_buffer_n_memory (buf) == 1
1354       && (mem = (GstXImageMemory *) gst_buffer_peek_memory (buf, 0))
1355       && g_strcmp0 (mem->parent.allocator->mem_type, "ximage") == 0
1356       && mem->sink == ximagesink) {
1357     /* If this buffer has been allocated using our buffer management we simply
1358        put the ximage which is in the PRIVATE pointer */
1359     GST_LOG_OBJECT (ximagesink, "buffer from our pool, writing directly");
1360     to_put = buf;
1361     res = GST_FLOW_OK;
1362   } else {
1363     GstVideoFrame src, dest;
1364     GstBufferPoolAcquireParams params = { 0, };
1365
1366     /* Else we have to copy the data into our private image, */
1367     /* if we have one... */
1368     GST_LOG_OBJECT (ximagesink, "buffer not from our pool, copying");
1369
1370     /* an internal pool should have been created in setcaps */
1371     if (G_UNLIKELY (ximagesink->pool == NULL))
1372       goto no_pool;
1373
1374     if (!gst_buffer_pool_set_active (ximagesink->pool, TRUE))
1375       goto activate_failed;
1376
1377     /* take a buffer from our pool, if there is no buffer in the pool something
1378      * is seriously wrong, waiting for the pool here might deadlock when we try
1379      * to go to PAUSED because we never flush the pool. */
1380     params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT;
1381     res = gst_buffer_pool_acquire_buffer (ximagesink->pool, &to_put, &params);
1382     if (res != GST_FLOW_OK)
1383       goto no_buffer;
1384
1385     GST_CAT_LOG_OBJECT (CAT_PERFORMANCE, ximagesink,
1386         "slow copy into bufferpool buffer %p", to_put);
1387
1388     if (!gst_video_frame_map (&src, &ximagesink->info, buf, GST_MAP_READ))
1389       goto invalid_buffer;
1390
1391     if (!gst_video_frame_map (&dest, &ximagesink->info, to_put, GST_MAP_WRITE)) {
1392       gst_video_frame_unmap (&src);
1393       goto invalid_buffer;
1394     }
1395
1396     gst_video_frame_copy (&dest, &src);
1397
1398     gst_video_frame_unmap (&dest);
1399     gst_video_frame_unmap (&src);
1400   }
1401
1402   if (!gst_x_image_sink_ximage_put (ximagesink, to_put))
1403     goto no_window;
1404
1405 done:
1406   if (to_put != buf)
1407     gst_buffer_unref (to_put);
1408
1409   return res;
1410
1411   /* ERRORS */
1412 no_pool:
1413   {
1414     GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1415         ("Internal error: can't allocate images"),
1416         ("We don't have a bufferpool negotiated"));
1417     return GST_FLOW_ERROR;
1418   }
1419 no_buffer:
1420   {
1421     /* No image available. That's very bad ! */
1422     GST_WARNING_OBJECT (ximagesink, "could not create image");
1423     return GST_FLOW_OK;
1424   }
1425 invalid_buffer:
1426   {
1427     /* No Window available to put our image into */
1428     GST_WARNING_OBJECT (ximagesink, "could not map image");
1429     res = GST_FLOW_OK;
1430     goto done;
1431   }
1432 no_window:
1433   {
1434     /* No Window available to put our image into */
1435     GST_WARNING_OBJECT (ximagesink, "could not output image - no window");
1436     res = GST_FLOW_ERROR;
1437     goto done;
1438   }
1439 activate_failed:
1440   {
1441     GST_ERROR_OBJECT (ximagesink, "failed to activate bufferpool.");
1442     res = GST_FLOW_ERROR;
1443     goto done;
1444   }
1445 }
1446
1447 static gboolean
1448 gst_x_image_sink_event (GstBaseSink * sink, GstEvent * event)
1449 {
1450   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (sink);
1451
1452   switch (GST_EVENT_TYPE (event)) {
1453     case GST_EVENT_TAG:{
1454       GstTagList *l;
1455       gchar *title = NULL;
1456
1457       gst_event_parse_tag (event, &l);
1458       gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1459
1460       if (title) {
1461         GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title);
1462         gst_x_image_sink_xwindow_set_title (ximagesink, ximagesink->xwindow,
1463             title);
1464
1465         g_free (title);
1466       }
1467       break;
1468     }
1469     default:
1470       break;
1471   }
1472   return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1473 }
1474
1475 static gboolean
1476 gst_x_image_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1477 {
1478   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (bsink);
1479   GstBufferPool *pool = NULL;
1480   GstCaps *caps;
1481   GstVideoInfo info;
1482   guint size;
1483   gboolean need_pool;
1484
1485   gst_query_parse_allocation (query, &caps, &need_pool);
1486
1487   if (caps == NULL)
1488     goto no_caps;
1489
1490   if (!gst_video_info_from_caps (&info, caps))
1491     goto invalid_caps;
1492
1493   /* the normal size of a frame */
1494   size = info.size;
1495
1496   if (need_pool) {
1497     pool = gst_x_image_sink_create_pool (ximagesink, caps, info.size, 0);
1498
1499     if (pool == NULL)
1500       goto no_pool;
1501   }
1502
1503   /* we need at least 2 buffer because we hold on to the last one */
1504   gst_query_add_allocation_pool (query, pool, size, 2, 0);
1505   if (pool)
1506     gst_object_unref (pool);
1507
1508   /* we also support various metadata */
1509   gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
1510   gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL);
1511
1512   return TRUE;
1513
1514   /* ERRORS */
1515 no_caps:
1516   {
1517     GST_DEBUG_OBJECT (bsink, "no caps specified");
1518     return FALSE;
1519   }
1520 invalid_caps:
1521   {
1522     GST_DEBUG_OBJECT (bsink, "invalid caps specified");
1523     return FALSE;
1524   }
1525 no_pool:
1526   {
1527     /* Already warned in create_pool */
1528     return FALSE;
1529   }
1530 }
1531
1532 /* Interfaces stuff */
1533 static void
1534 gst_x_image_sink_navigation_send_event (GstNavigation * navigation,
1535     GstStructure * structure)
1536 {
1537   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (navigation);
1538   GstEvent *event = NULL;
1539   gint x_offset, y_offset;
1540   gdouble x, y;
1541   gboolean handled = FALSE;
1542
1543   /* We are not converting the pointer coordinates as there's no hardware
1544      scaling done here. The only possible scaling is done by videoscale and
1545      videoscale will have to catch those events and transform the coordinates
1546      to match the applied scaling. So here we just add the offset if the image
1547      is centered in the window.  */
1548
1549   /* We take the flow_lock while we look at the window */
1550   g_mutex_lock (&ximagesink->flow_lock);
1551
1552   if (!ximagesink->xwindow) {
1553     g_mutex_unlock (&ximagesink->flow_lock);
1554     gst_structure_free (structure);
1555     return;
1556   }
1557
1558   x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
1559   y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
1560
1561   g_mutex_unlock (&ximagesink->flow_lock);
1562
1563   if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) {
1564     x -= x_offset / 2;
1565     gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
1566   }
1567   if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
1568     y -= y_offset / 2;
1569     gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
1570   }
1571
1572   event = gst_event_new_navigation (structure);
1573   if (event) {
1574     gst_event_ref (event);
1575     handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (ximagesink), event);
1576
1577     if (!handled)
1578       gst_element_post_message (GST_ELEMENT_CAST (ximagesink),
1579           gst_navigation_message_new_event (GST_OBJECT_CAST (ximagesink),
1580               event));
1581
1582     gst_event_unref (event);
1583   }
1584 }
1585
1586 static void
1587 gst_x_image_sink_navigation_init (GstNavigationInterface * iface)
1588 {
1589   iface->send_event = gst_x_image_sink_navigation_send_event;
1590 }
1591
1592 static void
1593 gst_x_image_sink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
1594 {
1595   XID xwindow_id = id;
1596   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1597   GstXWindow *xwindow = NULL;
1598
1599   /* We acquire the stream lock while setting this window in the element.
1600      We are basically cleaning tons of stuff replacing the old window, putting
1601      images while we do that would surely crash */
1602   g_mutex_lock (&ximagesink->flow_lock);
1603
1604   /* If we already use that window return */
1605   if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) {
1606     g_mutex_unlock (&ximagesink->flow_lock);
1607     return;
1608   }
1609
1610   /* If the element has not initialized the X11 context try to do so */
1611   if (!ximagesink->xcontext &&
1612       !(ximagesink->xcontext = gst_x_image_sink_xcontext_get (ximagesink))) {
1613     g_mutex_unlock (&ximagesink->flow_lock);
1614     /* we have thrown a GST_ELEMENT_ERROR now */
1615     return;
1616   }
1617
1618   /* If a window is there already we destroy it */
1619   if (ximagesink->xwindow) {
1620     gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1621     ximagesink->xwindow = NULL;
1622   }
1623
1624   /* If the xid is 0 we go back to an internal window */
1625   if (xwindow_id == 0) {
1626     /* If no width/height caps nego did not happen window will be created
1627        during caps nego then */
1628     if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
1629       xwindow = gst_x_image_sink_xwindow_new (ximagesink,
1630           GST_VIDEO_SINK_WIDTH (ximagesink),
1631           GST_VIDEO_SINK_HEIGHT (ximagesink));
1632     }
1633   } else {
1634     xwindow = g_new0 (GstXWindow, 1);
1635
1636     xwindow->win = xwindow_id;
1637
1638     /* We set the events we want to receive and create a GC. */
1639     g_mutex_lock (&ximagesink->x_lock);
1640     xwindow->internal = FALSE;
1641     if (ximagesink->handle_events) {
1642       XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
1643           StructureNotifyMask | PointerMotionMask | KeyPressMask |
1644           KeyReleaseMask);
1645     }
1646
1647     xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
1648     g_mutex_unlock (&ximagesink->x_lock);
1649   }
1650
1651   if (xwindow) {
1652     ximagesink->xwindow = xwindow;
1653     /* Update the window geometry, possibly generating a reconfigure event. */
1654     gst_x_image_sink_xwindow_update_geometry (ximagesink);
1655   }
1656
1657   g_mutex_unlock (&ximagesink->flow_lock);
1658 }
1659
1660 static void
1661 gst_x_image_sink_expose (GstVideoOverlay * overlay)
1662 {
1663   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1664
1665   gst_x_image_sink_xwindow_update_geometry (ximagesink);
1666   gst_x_image_sink_ximage_put (ximagesink, NULL);
1667 }
1668
1669 static void
1670 gst_x_image_sink_set_event_handling (GstVideoOverlay * overlay,
1671     gboolean handle_events)
1672 {
1673   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1674
1675   ximagesink->handle_events = handle_events;
1676
1677   g_mutex_lock (&ximagesink->flow_lock);
1678
1679   if (G_UNLIKELY (!ximagesink->xwindow)) {
1680     g_mutex_unlock (&ximagesink->flow_lock);
1681     return;
1682   }
1683
1684   g_mutex_lock (&ximagesink->x_lock);
1685
1686   if (handle_events) {
1687     if (ximagesink->xwindow->internal) {
1688       XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1689           ExposureMask | StructureNotifyMask | PointerMotionMask |
1690           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1691     } else {
1692       XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1693           ExposureMask | StructureNotifyMask | PointerMotionMask |
1694           KeyPressMask | KeyReleaseMask);
1695     }
1696   } else {
1697     XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0);
1698   }
1699
1700   g_mutex_unlock (&ximagesink->x_lock);
1701
1702   g_mutex_unlock (&ximagesink->flow_lock);
1703 }
1704
1705 static void
1706 gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface * iface)
1707 {
1708   iface->set_window_handle = gst_x_image_sink_set_window_handle;
1709   iface->expose = gst_x_image_sink_expose;
1710   iface->handle_events = gst_x_image_sink_set_event_handling;
1711 }
1712
1713 /* =========================================== */
1714 /*                                             */
1715 /*              Init & Class init              */
1716 /*                                             */
1717 /* =========================================== */
1718
1719 static void
1720 gst_x_image_sink_set_property (GObject * object, guint prop_id,
1721     const GValue * value, GParamSpec * pspec)
1722 {
1723   GstXImageSink *ximagesink;
1724
1725   g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
1726
1727   ximagesink = GST_X_IMAGE_SINK (object);
1728
1729   switch (prop_id) {
1730     case PROP_DISPLAY:
1731       ximagesink->display_name = g_strdup (g_value_get_string (value));
1732       break;
1733     case PROP_SYNCHRONOUS:
1734       ximagesink->synchronous = g_value_get_boolean (value);
1735       if (ximagesink->xcontext) {
1736         GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1737             ximagesink->synchronous ? "TRUE" : "FALSE");
1738         g_mutex_lock (&ximagesink->x_lock);
1739         XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1740         g_mutex_unlock (&ximagesink->x_lock);
1741       }
1742       break;
1743     case PROP_FORCE_ASPECT_RATIO:
1744       ximagesink->keep_aspect = g_value_get_boolean (value);
1745       break;
1746     case PROP_PIXEL_ASPECT_RATIO:
1747     {
1748       GValue *tmp;
1749
1750       tmp = g_new0 (GValue, 1);
1751       g_value_init (tmp, GST_TYPE_FRACTION);
1752
1753       if (!g_value_transform (value, tmp)) {
1754         GST_WARNING_OBJECT (ximagesink,
1755             "Could not transform string to aspect ratio");
1756         g_free (tmp);
1757       } else {
1758         GST_DEBUG_OBJECT (ximagesink, "set PAR to %d/%d",
1759             gst_value_get_fraction_numerator (tmp),
1760             gst_value_get_fraction_denominator (tmp));
1761         g_free (ximagesink->par);
1762         ximagesink->par = tmp;
1763       }
1764     }
1765       break;
1766     case PROP_HANDLE_EVENTS:
1767       gst_x_image_sink_set_event_handling (GST_VIDEO_OVERLAY (ximagesink),
1768           g_value_get_boolean (value));
1769       gst_x_image_sink_manage_event_thread (ximagesink);
1770       break;
1771     case PROP_HANDLE_EXPOSE:
1772       ximagesink->handle_expose = g_value_get_boolean (value);
1773       gst_x_image_sink_manage_event_thread (ximagesink);
1774       break;
1775     default:
1776       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1777       break;
1778   }
1779 }
1780
1781 static void
1782 gst_x_image_sink_get_property (GObject * object, guint prop_id,
1783     GValue * value, GParamSpec * pspec)
1784 {
1785   GstXImageSink *ximagesink;
1786
1787   g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
1788
1789   ximagesink = GST_X_IMAGE_SINK (object);
1790
1791   switch (prop_id) {
1792     case PROP_DISPLAY:
1793       g_value_set_string (value, ximagesink->display_name);
1794       break;
1795     case PROP_SYNCHRONOUS:
1796       g_value_set_boolean (value, ximagesink->synchronous);
1797       break;
1798     case PROP_FORCE_ASPECT_RATIO:
1799       g_value_set_boolean (value, ximagesink->keep_aspect);
1800       break;
1801     case PROP_PIXEL_ASPECT_RATIO:
1802       if (ximagesink->par)
1803         g_value_transform (ximagesink->par, value);
1804       break;
1805     case PROP_HANDLE_EVENTS:
1806       g_value_set_boolean (value, ximagesink->handle_events);
1807       break;
1808     case PROP_HANDLE_EXPOSE:
1809       g_value_set_boolean (value, ximagesink->handle_expose);
1810       break;
1811     case PROP_WINDOW_WIDTH:
1812       if (ximagesink->xwindow)
1813         g_value_set_uint64 (value, ximagesink->xwindow->width);
1814       else
1815         g_value_set_uint64 (value, 0);
1816       break;
1817     case PROP_WINDOW_HEIGHT:
1818       if (ximagesink->xwindow)
1819         g_value_set_uint64 (value, ximagesink->xwindow->height);
1820       else
1821         g_value_set_uint64 (value, 0);
1822       break;
1823     default:
1824       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1825       break;
1826   }
1827 }
1828
1829 static void
1830 gst_x_image_sink_reset (GstXImageSink * ximagesink)
1831 {
1832   GThread *thread;
1833
1834   GST_OBJECT_LOCK (ximagesink);
1835   ximagesink->running = FALSE;
1836   /* grab thread and mark it as NULL */
1837   thread = ximagesink->event_thread;
1838   ximagesink->event_thread = NULL;
1839   GST_OBJECT_UNLOCK (ximagesink);
1840
1841   /* Wait for our event thread to finish before we clean up our stuff. */
1842   if (thread)
1843     g_thread_join (thread);
1844
1845   if (ximagesink->cur_image) {
1846     gst_buffer_unref (ximagesink->cur_image);
1847     ximagesink->cur_image = NULL;
1848   }
1849
1850   g_mutex_lock (&ximagesink->flow_lock);
1851
1852   if (ximagesink->pool) {
1853     gst_object_unref (ximagesink->pool);
1854     ximagesink->pool = NULL;
1855   }
1856
1857   if (ximagesink->xwindow) {
1858     gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
1859     gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1860     ximagesink->xwindow = NULL;
1861   }
1862   g_mutex_unlock (&ximagesink->flow_lock);
1863
1864   gst_x_image_sink_xcontext_clear (ximagesink);
1865 }
1866
1867 static void
1868 gst_x_image_sink_finalize (GObject * object)
1869 {
1870   GstXImageSink *ximagesink;
1871
1872   ximagesink = GST_X_IMAGE_SINK (object);
1873
1874   gst_x_image_sink_reset (ximagesink);
1875
1876   if (ximagesink->display_name) {
1877     g_free (ximagesink->display_name);
1878     ximagesink->display_name = NULL;
1879   }
1880   if (ximagesink->par) {
1881     g_free (ximagesink->par);
1882     ximagesink->par = NULL;
1883   }
1884   g_mutex_clear (&ximagesink->x_lock);
1885   g_mutex_clear (&ximagesink->flow_lock);
1886
1887   g_free (ximagesink->media_title);
1888
1889   G_OBJECT_CLASS (parent_class)->finalize (object);
1890 }
1891
1892 static void
1893 gst_x_image_sink_init (GstXImageSink * ximagesink)
1894 {
1895   ximagesink->display_name = NULL;
1896   ximagesink->xcontext = NULL;
1897   ximagesink->xwindow = NULL;
1898   ximagesink->cur_image = NULL;
1899
1900   ximagesink->event_thread = NULL;
1901   ximagesink->running = FALSE;
1902
1903   ximagesink->fps_n = 0;
1904   ximagesink->fps_d = 1;
1905
1906   g_mutex_init (&ximagesink->x_lock);
1907   g_mutex_init (&ximagesink->flow_lock);
1908
1909   ximagesink->par = NULL;
1910
1911   ximagesink->pool = NULL;
1912
1913   ximagesink->synchronous = FALSE;
1914   ximagesink->keep_aspect = TRUE;
1915   ximagesink->handle_events = TRUE;
1916   ximagesink->handle_expose = TRUE;
1917 }
1918
1919 static void
1920 gst_x_image_sink_class_init (GstXImageSinkClass * klass)
1921 {
1922   GObjectClass *gobject_class;
1923   GstElementClass *gstelement_class;
1924   GstBaseSinkClass *gstbasesink_class;
1925   GstVideoSinkClass *videosink_class;
1926
1927   gobject_class = (GObjectClass *) klass;
1928   gstelement_class = (GstElementClass *) klass;
1929   gstbasesink_class = (GstBaseSinkClass *) klass;
1930   videosink_class = (GstVideoSinkClass *) klass;
1931
1932   gobject_class->finalize = gst_x_image_sink_finalize;
1933   gobject_class->set_property = gst_x_image_sink_set_property;
1934   gobject_class->get_property = gst_x_image_sink_get_property;
1935
1936   g_object_class_install_property (gobject_class, PROP_DISPLAY,
1937       g_param_spec_string ("display", "Display", "X Display name",
1938           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1939   g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
1940       g_param_spec_boolean ("synchronous", "Synchronous",
1941           "When enabled, runs the X display in synchronous mode. "
1942           "(unrelated to A/V sync, used only for debugging)", FALSE,
1943           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1944   g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
1945       g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
1946           "When enabled, reverse caps negotiation (scaling) will respect "
1947           "original aspect ratio", TRUE,
1948           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1949   g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
1950       g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
1951           "The pixel aspect ratio of the device", "1/1",
1952           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1953   g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
1954       g_param_spec_boolean ("handle-events", "Handle XEvents",
1955           "When enabled, XEvents will be selected and handled", TRUE,
1956           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1957   g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
1958       g_param_spec_boolean ("handle-expose", "Handle expose",
1959           "When enabled, "
1960           "the current frame will always be drawn in response to X Expose "
1961           "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1962
1963   /**
1964    * GstXImageSink:window-width
1965    *
1966    * Actual width of the video window.
1967    */
1968   g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
1969       g_param_spec_uint64 ("window-width", "window-width",
1970           "Width of the window", 0, G_MAXUINT64, 0,
1971           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1972
1973   /**
1974    * GstXImageSink:window-height
1975    *
1976    * Actual height of the video window.
1977    */
1978   g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
1979       g_param_spec_uint64 ("window-height", "window-height",
1980           "Height of the window", 0, G_MAXUINT64, 0,
1981           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1982
1983   gst_element_class_set_static_metadata (gstelement_class,
1984       "Video sink", "Sink/Video",
1985       "A standard X based videosink", "Julien Moutte <julien@moutte.net>");
1986
1987   gst_element_class_add_static_pad_template (gstelement_class,
1988       &gst_x_image_sink_sink_template_factory);
1989
1990   gstelement_class->change_state = gst_x_image_sink_change_state;
1991
1992   gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_getcaps);
1993   gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_setcaps);
1994   gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_x_image_sink_get_times);
1995   gstbasesink_class->propose_allocation =
1996       GST_DEBUG_FUNCPTR (gst_x_image_sink_propose_allocation);
1997   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_x_image_sink_event);
1998
1999   videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_x_image_sink_show_frame);
2000 }