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