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
gst_x_image_sink_xwindow_draw_borders(GstXImageSink * ximagesink,GstXWindow * xwindow,GstVideoRectangle rect)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
gst_x_image_sink_ximage_put(GstXImageSink * ximagesink,GstBuffer * ximage)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
gst_x_image_sink_xwindow_decorate(GstXImageSink * ximagesink,GstXWindow * window)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
gst_x_image_sink_xwindow_set_title(GstXImageSink * ximagesink,GstXWindow * xwindow,const gchar * media_title)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 *
gst_x_image_sink_xwindow_new(GstXImageSink * ximagesink,gint width,gint height)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
gst_x_image_sink_xwindow_destroy(GstXImageSink * ximagesink,GstXWindow * xwindow)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
gst_x_image_sink_xwindow_update_geometry(GstXImageSink * ximagesink)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
gst_x_image_sink_xwindow_clear(GstXImageSink * ximagesink,GstXWindow * xwindow)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
gst_x_image_sink_handle_xevents(GstXImageSink * ximagesink)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         keysym = XkbKeycodeToKeysym (ximagesink->xcontext->disp,
635             e.xkey.keycode, 0, 0);
636         if (keysym != NoSymbol) {
637           key_str = XKeysymToString (keysym);
638         } else {
639           key_str = "unknown";
640         }
641         g_mutex_unlock (&ximagesink->x_lock);
642         GST_DEBUG_OBJECT (ximagesink,
643             "key %d pressed over window at %d,%d (%s)",
644             e.xkey.keycode, e.xkey.x, e.xkey.y, key_str);
645         gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
646             e.type == KeyPress ? "key-press" : "key-release", key_str);
647         break;
648       default:
649         GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)",
650             e.type);
651     }
652     g_mutex_lock (&ximagesink->flow_lock);
653     g_mutex_lock (&ximagesink->x_lock);
654   }
655 
656   /* Handle Expose */
657   while (XCheckWindowEvent (ximagesink->xcontext->disp,
658           ximagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) {
659     switch (e.type) {
660       case Expose:
661         exposed = TRUE;
662         break;
663       case ConfigureNotify:
664         g_mutex_unlock (&ximagesink->x_lock);
665         gst_x_image_sink_xwindow_update_geometry (ximagesink);
666         g_mutex_lock (&ximagesink->x_lock);
667         configured = TRUE;
668         break;
669       default:
670         break;
671     }
672   }
673 
674   if (ximagesink->handle_expose && (exposed || configured)) {
675     g_mutex_unlock (&ximagesink->x_lock);
676     g_mutex_unlock (&ximagesink->flow_lock);
677 
678     gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink));
679 
680     g_mutex_lock (&ximagesink->flow_lock);
681     g_mutex_lock (&ximagesink->x_lock);
682   }
683 
684   /* Handle Display events */
685   while (XPending (ximagesink->xcontext->disp)) {
686     XNextEvent (ximagesink->xcontext->disp, &e);
687 
688     switch (e.type) {
689       case ClientMessage:{
690         Atom wm_delete;
691 
692         wm_delete = XInternAtom (ximagesink->xcontext->disp,
693             "WM_DELETE_WINDOW", False);
694         if (wm_delete == (Atom) e.xclient.data.l[0]) {
695           /* Handle window deletion by posting an error on the bus */
696           GST_ELEMENT_ERROR (ximagesink, RESOURCE, NOT_FOUND,
697               ("Output window was closed"), (NULL));
698 
699           g_mutex_unlock (&ximagesink->x_lock);
700           gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
701           ximagesink->xwindow = NULL;
702           g_mutex_lock (&ximagesink->x_lock);
703         }
704         break;
705       }
706       default:
707         break;
708     }
709   }
710 
711   g_mutex_unlock (&ximagesink->x_lock);
712   g_mutex_unlock (&ximagesink->flow_lock);
713 }
714 
715 static gpointer
gst_x_image_sink_event_thread(GstXImageSink * ximagesink)716 gst_x_image_sink_event_thread (GstXImageSink * ximagesink)
717 {
718   g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
719 
720   GST_OBJECT_LOCK (ximagesink);
721   while (ximagesink->running) {
722     GST_OBJECT_UNLOCK (ximagesink);
723 
724     if (ximagesink->xwindow) {
725       gst_x_image_sink_handle_xevents (ximagesink);
726     }
727     /* FIXME: do we want to align this with the framerate or anything else? */
728     g_usleep (G_USEC_PER_SEC / 20);
729 
730     GST_OBJECT_LOCK (ximagesink);
731   }
732   GST_OBJECT_UNLOCK (ximagesink);
733 
734   return NULL;
735 }
736 
737 static void
gst_x_image_sink_manage_event_thread(GstXImageSink * ximagesink)738 gst_x_image_sink_manage_event_thread (GstXImageSink * ximagesink)
739 {
740   GThread *thread = NULL;
741 
742   /* don't start the thread too early */
743   if (ximagesink->xcontext == NULL) {
744     return;
745   }
746 
747   GST_OBJECT_LOCK (ximagesink);
748   if (ximagesink->handle_expose || ximagesink->handle_events) {
749     if (!ximagesink->event_thread) {
750       /* Setup our event listening thread */
751       GST_DEBUG_OBJECT (ximagesink, "run xevent thread, expose %d, events %d",
752           ximagesink->handle_expose, ximagesink->handle_events);
753       ximagesink->running = TRUE;
754       ximagesink->event_thread = g_thread_try_new ("ximagesink-events",
755           (GThreadFunc) gst_x_image_sink_event_thread, ximagesink, NULL);
756     }
757   } else {
758     if (ximagesink->event_thread) {
759       GST_DEBUG_OBJECT (ximagesink, "stop xevent thread, expose %d, events %d",
760           ximagesink->handle_expose, ximagesink->handle_events);
761       ximagesink->running = FALSE;
762       /* grab thread and mark it as NULL */
763       thread = ximagesink->event_thread;
764       ximagesink->event_thread = NULL;
765     }
766   }
767   GST_OBJECT_UNLOCK (ximagesink);
768 
769   /* Wait for our event thread to finish */
770   if (thread)
771     g_thread_join (thread);
772 
773 }
774 
775 
776 /* This function calculates the pixel aspect ratio based on the properties
777  * in the xcontext structure and stores it there. */
778 static void
gst_x_image_sink_calculate_pixel_aspect_ratio(GstXContext * xcontext)779 gst_x_image_sink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
780 {
781   static const gint par[][2] = {
782     {1, 1},                     /* regular screen */
783     {16, 15},                   /* PAL TV */
784     {11, 10},                   /* 525 line Rec.601 video */
785     {54, 59},                   /* 625 line Rec.601 video */
786     {64, 45},                   /* 1280x1024 on 16:9 display */
787     {5, 3},                     /* 1280x1024 on 4:3 display */
788     {4, 3}                      /*  800x600 on 16:9 display */
789   };
790   gint i;
791   gint index;
792   gdouble ratio;
793   gdouble delta;
794 
795 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
796 
797   /* first calculate the "real" ratio based on the X values;
798    * which is the "physical" w/h divided by the w/h in pixels of the display */
799   ratio = (gdouble) (xcontext->widthmm * xcontext->height)
800       / (xcontext->heightmm * xcontext->width);
801 
802   /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
803    * override here */
804   if (xcontext->width == 720 && xcontext->height == 576) {
805     ratio = 4.0 * 576 / (3.0 * 720);
806   }
807   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
808 
809   /* now find the one from par[][2] with the lowest delta to the real one */
810   delta = DELTA (0);
811   index = 0;
812 
813   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
814     gdouble this_delta = DELTA (i);
815 
816     if (this_delta < delta) {
817       index = i;
818       delta = this_delta;
819     }
820   }
821 
822   GST_DEBUG ("Decided on index %d (%d/%d)", index,
823       par[index][0], par[index][1]);
824 
825   g_free (xcontext->par);
826   xcontext->par = g_new0 (GValue, 1);
827   g_value_init (xcontext->par, GST_TYPE_FRACTION);
828   gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
829   GST_DEBUG ("set xcontext PAR to %d/%d",
830       gst_value_get_fraction_numerator (xcontext->par),
831       gst_value_get_fraction_denominator (xcontext->par));
832 }
833 
834 /* This function gets the X Display and global info about it. Everything is
835    stored in our object and will be cleaned when the object is disposed. Note
836    here that caps for supported format are generated without any window or
837    image creation */
838 static GstXContext *
gst_x_image_sink_xcontext_get(GstXImageSink * ximagesink)839 gst_x_image_sink_xcontext_get (GstXImageSink * ximagesink)
840 {
841   GstXContext *xcontext = NULL;
842   XPixmapFormatValues *px_formats = NULL;
843   gint nb_formats = 0, i;
844   gint endianness;
845   GstVideoFormat vformat;
846   guint32 alpha_mask;
847 
848   g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
849 
850   xcontext = g_new0 (GstXContext, 1);
851 
852   g_mutex_lock (&ximagesink->x_lock);
853 
854   xcontext->disp = XOpenDisplay (ximagesink->display_name);
855 
856   if (!xcontext->disp) {
857     g_mutex_unlock (&ximagesink->x_lock);
858     g_free (xcontext);
859     GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
860         ("Could not initialise X output"), ("Could not open display"));
861     return NULL;
862   }
863 
864   xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
865   xcontext->screen_num = DefaultScreen (xcontext->disp);
866   xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
867   xcontext->root = DefaultRootWindow (xcontext->disp);
868   xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
869   xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
870   xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
871 
872   xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
873   xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
874   xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
875   xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
876 
877   GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm",
878       xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
879 
880   gst_x_image_sink_calculate_pixel_aspect_ratio (xcontext);
881 
882   /* We get supported pixmap formats at supported depth */
883   px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
884 
885   if (!px_formats) {
886     XCloseDisplay (xcontext->disp);
887     g_mutex_unlock (&ximagesink->x_lock);
888     g_free (xcontext->par);
889     g_free (xcontext);
890     GST_ELEMENT_ERROR (ximagesink, RESOURCE, SETTINGS,
891         ("Could not get supported pixmap formats"), (NULL));
892     return NULL;
893   }
894 
895   /* We get bpp value corresponding to our running depth */
896   for (i = 0; i < nb_formats; i++) {
897     if (px_formats[i].depth == xcontext->depth)
898       xcontext->bpp = px_formats[i].bits_per_pixel;
899   }
900 
901   XFree (px_formats);
902 
903   endianness = (ImageByteOrder (xcontext->disp) ==
904       LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
905 
906   /* Search for XShm extension support */
907 #ifdef HAVE_XSHM
908   if (XShmQueryExtension (xcontext->disp) &&
909       gst_x_image_sink_check_xshm_calls (ximagesink, xcontext)) {
910     xcontext->use_xshm = TRUE;
911     GST_DEBUG ("ximagesink is using XShm extension");
912   } else
913 #endif /* HAVE_XSHM */
914   {
915     xcontext->use_xshm = FALSE;
916     GST_DEBUG ("ximagesink is not using XShm extension");
917   }
918 
919   /* extrapolate alpha mask */
920   if (xcontext->depth == 32) {
921     alpha_mask = ~(xcontext->visual->red_mask
922         | xcontext->visual->green_mask | xcontext->visual->blue_mask);
923   } else {
924     alpha_mask = 0;
925   }
926 
927   vformat =
928       gst_video_format_from_masks (xcontext->depth, xcontext->bpp, endianness,
929       xcontext->visual->red_mask, xcontext->visual->green_mask,
930       xcontext->visual->blue_mask, alpha_mask);
931 
932   if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
933     goto unknown_format;
934 
935   /* update object's par with calculated one if not set yet */
936   if (!ximagesink->par) {
937     ximagesink->par = g_new0 (GValue, 1);
938     gst_value_init_and_copy (ximagesink->par, xcontext->par);
939     GST_DEBUG_OBJECT (ximagesink, "set calculated PAR on object's PAR");
940   }
941   xcontext->caps = gst_caps_new_simple ("video/x-raw",
942       "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
943       "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
944       "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
945       "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
946   if (ximagesink->par) {
947     int nom, den;
948 
949     nom = gst_value_get_fraction_numerator (ximagesink->par);
950     den = gst_value_get_fraction_denominator (ximagesink->par);
951     gst_caps_set_simple (xcontext->caps, "pixel-aspect-ratio",
952         GST_TYPE_FRACTION, nom, den, NULL);
953   }
954 
955   g_mutex_unlock (&ximagesink->x_lock);
956 
957   return xcontext;
958 
959   /* ERRORS */
960 unknown_format:
961   {
962     GST_ERROR_OBJECT (ximagesink, "unknown format");
963     return NULL;
964   }
965 }
966 
967 /* This function cleans the X context. Closing the Display and unrefing the
968    caps for supported formats. */
969 static void
gst_x_image_sink_xcontext_clear(GstXImageSink * ximagesink)970 gst_x_image_sink_xcontext_clear (GstXImageSink * ximagesink)
971 {
972   GstXContext *xcontext;
973 
974   g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
975 
976   GST_OBJECT_LOCK (ximagesink);
977   if (ximagesink->xcontext == NULL) {
978     GST_OBJECT_UNLOCK (ximagesink);
979     return;
980   }
981 
982   /* Take the xcontext reference and NULL it while we
983    * clean it up, so that any buffer-alloced buffers
984    * arriving after this will be freed correctly */
985   xcontext = ximagesink->xcontext;
986   ximagesink->xcontext = NULL;
987 
988   GST_OBJECT_UNLOCK (ximagesink);
989 
990   gst_caps_unref (xcontext->caps);
991   g_free (xcontext->par);
992   g_free (ximagesink->par);
993   ximagesink->par = NULL;
994 
995   if (xcontext->last_caps)
996     gst_caps_replace (&xcontext->last_caps, NULL);
997 
998   g_mutex_lock (&ximagesink->x_lock);
999 
1000   XCloseDisplay (xcontext->disp);
1001 
1002   g_mutex_unlock (&ximagesink->x_lock);
1003 
1004   g_free (xcontext);
1005 }
1006 
1007 /* Element stuff */
1008 
1009 static GstCaps *
gst_x_image_sink_getcaps(GstBaseSink * bsink,GstCaps * filter)1010 gst_x_image_sink_getcaps (GstBaseSink * bsink, GstCaps * filter)
1011 {
1012   GstXImageSink *ximagesink;
1013   GstCaps *caps;
1014   int i;
1015 
1016   ximagesink = GST_X_IMAGE_SINK (bsink);
1017 
1018   g_mutex_lock (&ximagesink->x_lock);
1019   if (ximagesink->xcontext) {
1020     GstCaps *caps;
1021 
1022     caps = gst_caps_ref (ximagesink->xcontext->caps);
1023 
1024     if (filter) {
1025       GstCaps *intersection;
1026 
1027       intersection =
1028           gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1029       gst_caps_unref (caps);
1030       caps = intersection;
1031     }
1032 
1033     if (gst_caps_is_empty (caps)) {
1034       g_mutex_unlock (&ximagesink->x_lock);
1035       return caps;
1036     }
1037 
1038     if (ximagesink->xwindow && ximagesink->xwindow->width) {
1039       GstStructure *s0, *s1;
1040 
1041       caps = gst_caps_make_writable (caps);
1042 
1043       /* There can only be a single structure because the xcontext
1044        * caps only have a single structure */
1045       s0 = gst_caps_get_structure (caps, 0);
1046       s1 = gst_structure_copy (gst_caps_get_structure (caps, 0));
1047 
1048       gst_structure_set (s0, "width", G_TYPE_INT, ximagesink->xwindow->width,
1049           "height", G_TYPE_INT, ximagesink->xwindow->height, NULL);
1050       gst_caps_append_structure (caps, s1);
1051 
1052       /* This will not change the order but will remove the
1053        * fixed width/height caps again if not possible
1054        * upstream */
1055       if (filter) {
1056         GstCaps *intersection;
1057 
1058         intersection =
1059             gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1060         gst_caps_unref (caps);
1061         caps = intersection;
1062       }
1063     }
1064 
1065     g_mutex_unlock (&ximagesink->x_lock);
1066     return caps;
1067   }
1068   g_mutex_unlock (&ximagesink->x_lock);
1069 
1070   /* get a template copy and add the pixel aspect ratio */
1071   caps = gst_pad_get_pad_template_caps (GST_BASE_SINK (ximagesink)->sinkpad);
1072   if (ximagesink->par) {
1073     caps = gst_caps_make_writable (caps);
1074     for (i = 0; i < gst_caps_get_size (caps); ++i) {
1075       GstStructure *structure = gst_caps_get_structure (caps, i);
1076       int nom, den;
1077 
1078       nom = gst_value_get_fraction_numerator (ximagesink->par);
1079       den = gst_value_get_fraction_denominator (ximagesink->par);
1080       gst_structure_set (structure, "pixel-aspect-ratio",
1081           GST_TYPE_FRACTION, nom, den, NULL);
1082     }
1083   }
1084 
1085   if (filter) {
1086     GstCaps *intersection;
1087 
1088     intersection =
1089         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1090     gst_caps_unref (caps);
1091     caps = intersection;
1092   }
1093 
1094   return caps;
1095 }
1096 
1097 static GstBufferPool *
gst_x_image_sink_create_pool(GstXImageSink * ximagesink,GstCaps * caps,gsize size,gint min)1098 gst_x_image_sink_create_pool (GstXImageSink * ximagesink, GstCaps * caps,
1099     gsize size, gint min)
1100 {
1101   static GstAllocationParams params = { 0, 15, 0, 0, };
1102   GstBufferPool *pool;
1103   GstStructure *config;
1104 
1105   /* create a new pool for the new configuration */
1106   pool = gst_ximage_buffer_pool_new (ximagesink);
1107 
1108   config = gst_buffer_pool_get_config (pool);
1109   gst_buffer_pool_config_set_params (config, caps, size, min, 0);
1110   gst_buffer_pool_config_set_allocator (config, NULL, &params);
1111 
1112   if (!gst_buffer_pool_set_config (pool, config))
1113     goto config_failed;
1114 
1115   return pool;
1116 
1117 config_failed:
1118   {
1119     GST_WARNING_OBJECT (ximagesink, "failed setting config");
1120     gst_object_unref (pool);
1121     return NULL;
1122   }
1123 }
1124 
1125 static gboolean
gst_x_image_sink_setcaps(GstBaseSink * bsink,GstCaps * caps)1126 gst_x_image_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1127 {
1128   GstXImageSink *ximagesink;
1129   GstStructure *structure;
1130   GstVideoInfo info;
1131   GstBufferPool *newpool, *oldpool;
1132   const GValue *par;
1133 
1134   ximagesink = GST_X_IMAGE_SINK (bsink);
1135 
1136   if (!ximagesink->xcontext)
1137     return FALSE;
1138 
1139   GST_DEBUG_OBJECT (ximagesink,
1140       "sinkconnect possible caps %" GST_PTR_FORMAT " with given caps %"
1141       GST_PTR_FORMAT, ximagesink->xcontext->caps, caps);
1142 
1143   /* We intersect those caps with our template to make sure they are correct */
1144   if (!gst_caps_can_intersect (ximagesink->xcontext->caps, caps))
1145     goto incompatible_caps;
1146 
1147   if (!gst_video_info_from_caps (&info, caps))
1148     goto invalid_format;
1149 
1150   structure = gst_caps_get_structure (caps, 0);
1151   /* if the caps contain pixel-aspect-ratio, they have to match ours,
1152    * otherwise linking should fail */
1153   par = gst_structure_get_value (structure, "pixel-aspect-ratio");
1154   if (par) {
1155     if (ximagesink->par) {
1156       if (gst_value_compare (par, ximagesink->par) != GST_VALUE_EQUAL) {
1157         goto wrong_aspect;
1158       }
1159     } else if (ximagesink->xcontext->par) {
1160       if (gst_value_compare (par, ximagesink->xcontext->par) != GST_VALUE_EQUAL) {
1161         goto wrong_aspect;
1162       }
1163     }
1164   }
1165 
1166   GST_VIDEO_SINK_WIDTH (ximagesink) = info.width;
1167   GST_VIDEO_SINK_HEIGHT (ximagesink) = info.height;
1168   ximagesink->fps_n = info.fps_n;
1169   ximagesink->fps_d = info.fps_d;
1170 
1171   /* Notify application to set xwindow id now */
1172   g_mutex_lock (&ximagesink->flow_lock);
1173   if (!ximagesink->xwindow) {
1174     g_mutex_unlock (&ximagesink->flow_lock);
1175     gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (ximagesink));
1176   } else {
1177     g_mutex_unlock (&ximagesink->flow_lock);
1178   }
1179 
1180   /* Creating our window and our image */
1181   if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 ||
1182       GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0)
1183     goto invalid_size;
1184 
1185   g_mutex_lock (&ximagesink->flow_lock);
1186   if (!ximagesink->xwindow) {
1187     ximagesink->xwindow = gst_x_image_sink_xwindow_new (ximagesink,
1188         GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1189   }
1190 
1191   ximagesink->info = info;
1192 
1193   /* Remember to draw borders for next frame */
1194   ximagesink->draw_border = TRUE;
1195 
1196   /* create a new internal pool for the new configuration */
1197   newpool = gst_x_image_sink_create_pool (ximagesink, caps, info.size, 2);
1198 
1199   /* we don't activate the internal pool yet as it may not be needed */
1200   oldpool = ximagesink->pool;
1201   ximagesink->pool = newpool;
1202   g_mutex_unlock (&ximagesink->flow_lock);
1203 
1204   /* deactivate and unref the old internal pool */
1205   if (oldpool) {
1206     gst_buffer_pool_set_active (oldpool, FALSE);
1207     gst_object_unref (oldpool);
1208   }
1209 
1210   return TRUE;
1211 
1212   /* ERRORS */
1213 incompatible_caps:
1214   {
1215     GST_ERROR_OBJECT (ximagesink, "caps incompatible");
1216     return FALSE;
1217   }
1218 invalid_format:
1219   {
1220     GST_ERROR_OBJECT (ximagesink, "caps invalid");
1221     return FALSE;
1222   }
1223 wrong_aspect:
1224   {
1225     GST_INFO_OBJECT (ximagesink, "pixel aspect ratio does not match");
1226     return FALSE;
1227   }
1228 invalid_size:
1229   {
1230     GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL),
1231         ("Invalid image size."));
1232     return FALSE;
1233   }
1234 }
1235 
1236 static GstStateChangeReturn
gst_x_image_sink_change_state(GstElement * element,GstStateChange transition)1237 gst_x_image_sink_change_state (GstElement * element, GstStateChange transition)
1238 {
1239   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1240   GstXImageSink *ximagesink;
1241   GstXContext *xcontext = NULL;
1242 
1243   ximagesink = GST_X_IMAGE_SINK (element);
1244 
1245   switch (transition) {
1246     case GST_STATE_CHANGE_NULL_TO_READY:
1247       /* Initializing the XContext */
1248       if (ximagesink->xcontext == NULL) {
1249         xcontext = gst_x_image_sink_xcontext_get (ximagesink);
1250         if (xcontext == NULL) {
1251           ret = GST_STATE_CHANGE_FAILURE;
1252           goto beach;
1253         }
1254         GST_OBJECT_LOCK (ximagesink);
1255         if (xcontext)
1256           ximagesink->xcontext = xcontext;
1257         GST_OBJECT_UNLOCK (ximagesink);
1258       }
1259 
1260       /* call XSynchronize with the current value of synchronous */
1261       GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1262           ximagesink->synchronous ? "TRUE" : "FALSE");
1263       g_mutex_lock (&ximagesink->x_lock);
1264       XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1265       g_mutex_unlock (&ximagesink->x_lock);
1266       gst_x_image_sink_manage_event_thread (ximagesink);
1267       break;
1268     case GST_STATE_CHANGE_READY_TO_PAUSED:
1269       g_mutex_lock (&ximagesink->flow_lock);
1270       if (ximagesink->xwindow)
1271         gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
1272       g_mutex_unlock (&ximagesink->flow_lock);
1273       break;
1274     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1275       break;
1276     default:
1277       break;
1278   }
1279 
1280   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1281 
1282   switch (transition) {
1283     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1284       break;
1285     case GST_STATE_CHANGE_PAUSED_TO_READY:
1286       ximagesink->fps_n = 0;
1287       ximagesink->fps_d = 1;
1288       GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
1289       GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
1290       g_mutex_lock (&ximagesink->flow_lock);
1291       if (ximagesink->pool)
1292         gst_buffer_pool_set_active (ximagesink->pool, FALSE);
1293       g_mutex_unlock (&ximagesink->flow_lock);
1294       break;
1295     case GST_STATE_CHANGE_READY_TO_NULL:
1296       gst_x_image_sink_reset (ximagesink);
1297       break;
1298     default:
1299       break;
1300   }
1301 
1302 beach:
1303   return ret;
1304 }
1305 
1306 static void
gst_x_image_sink_get_times(GstBaseSink * bsink,GstBuffer * buf,GstClockTime * start,GstClockTime * end)1307 gst_x_image_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1308     GstClockTime * start, GstClockTime * end)
1309 {
1310   GstXImageSink *ximagesink;
1311 
1312   ximagesink = GST_X_IMAGE_SINK (bsink);
1313 
1314   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1315     *start = GST_BUFFER_TIMESTAMP (buf);
1316     if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1317       *end = *start + GST_BUFFER_DURATION (buf);
1318     } else {
1319       if (ximagesink->fps_n > 0) {
1320         *end = *start +
1321             gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d,
1322             ximagesink->fps_n);
1323       }
1324     }
1325   }
1326 }
1327 
1328 static GstFlowReturn
gst_x_image_sink_show_frame(GstVideoSink * vsink,GstBuffer * buf)1329 gst_x_image_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1330 {
1331   GstFlowReturn res;
1332   GstXImageSink *ximagesink;
1333   GstXImageMemory *mem;
1334   GstBuffer *to_put = NULL;
1335 
1336   ximagesink = GST_X_IMAGE_SINK (vsink);
1337 
1338   if (gst_buffer_n_memory (buf) == 1
1339       && (mem = (GstXImageMemory *) gst_buffer_peek_memory (buf, 0))
1340       && g_strcmp0 (mem->parent.allocator->mem_type, "ximage") == 0
1341       && mem->sink == ximagesink) {
1342     /* If this buffer has been allocated using our buffer management we simply
1343        put the ximage which is in the PRIVATE pointer */
1344     GST_LOG_OBJECT (ximagesink, "buffer from our pool, writing directly");
1345     to_put = buf;
1346     res = GST_FLOW_OK;
1347   } else {
1348     GstVideoFrame src, dest;
1349     GstBufferPoolAcquireParams params = { 0, };
1350 
1351     /* Else we have to copy the data into our private image, */
1352     /* if we have one... */
1353     GST_LOG_OBJECT (ximagesink, "buffer not from our pool, copying");
1354 
1355     /* an internal pool should have been created in setcaps */
1356     if (G_UNLIKELY (ximagesink->pool == NULL))
1357       goto no_pool;
1358 
1359     if (!gst_buffer_pool_set_active (ximagesink->pool, TRUE))
1360       goto activate_failed;
1361 
1362     /* take a buffer from our pool, if there is no buffer in the pool something
1363      * is seriously wrong, waiting for the pool here might deadlock when we try
1364      * to go to PAUSED because we never flush the pool. */
1365     params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT;
1366     res = gst_buffer_pool_acquire_buffer (ximagesink->pool, &to_put, &params);
1367     if (res != GST_FLOW_OK)
1368       goto no_buffer;
1369 
1370     GST_CAT_LOG_OBJECT (CAT_PERFORMANCE, ximagesink,
1371         "slow copy into bufferpool buffer %p", to_put);
1372 
1373     if (!gst_video_frame_map (&src, &ximagesink->info, buf, GST_MAP_READ))
1374       goto invalid_buffer;
1375 
1376     if (!gst_video_frame_map (&dest, &ximagesink->info, to_put, GST_MAP_WRITE)) {
1377       gst_video_frame_unmap (&src);
1378       goto invalid_buffer;
1379     }
1380 
1381     gst_video_frame_copy (&dest, &src);
1382 
1383     gst_video_frame_unmap (&dest);
1384     gst_video_frame_unmap (&src);
1385   }
1386 
1387   if (!gst_x_image_sink_ximage_put (ximagesink, to_put))
1388     goto no_window;
1389 
1390 done:
1391   if (to_put != buf)
1392     gst_buffer_unref (to_put);
1393 
1394   return res;
1395 
1396   /* ERRORS */
1397 no_pool:
1398   {
1399     GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1400         ("Internal error: can't allocate images"),
1401         ("We don't have a bufferpool negotiated"));
1402     return GST_FLOW_ERROR;
1403   }
1404 no_buffer:
1405   {
1406     /* No image available. That's very bad ! */
1407     GST_WARNING_OBJECT (ximagesink, "could not create image");
1408     return GST_FLOW_OK;
1409   }
1410 invalid_buffer:
1411   {
1412     /* No Window available to put our image into */
1413     GST_WARNING_OBJECT (ximagesink, "could not map image");
1414     res = GST_FLOW_OK;
1415     goto done;
1416   }
1417 no_window:
1418   {
1419     /* No Window available to put our image into */
1420     GST_WARNING_OBJECT (ximagesink, "could not output image - no window");
1421     res = GST_FLOW_ERROR;
1422     goto done;
1423   }
1424 activate_failed:
1425   {
1426     GST_ERROR_OBJECT (ximagesink, "failed to activate bufferpool.");
1427     res = GST_FLOW_ERROR;
1428     goto done;
1429   }
1430 }
1431 
1432 static gboolean
gst_x_image_sink_event(GstBaseSink * sink,GstEvent * event)1433 gst_x_image_sink_event (GstBaseSink * sink, GstEvent * event)
1434 {
1435   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (sink);
1436 
1437   switch (GST_EVENT_TYPE (event)) {
1438     case GST_EVENT_TAG:{
1439       GstTagList *l;
1440       gchar *title = NULL;
1441 
1442       gst_event_parse_tag (event, &l);
1443       gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1444 
1445       if (title) {
1446         GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title);
1447         gst_x_image_sink_xwindow_set_title (ximagesink, ximagesink->xwindow,
1448             title);
1449 
1450         g_free (title);
1451       }
1452       break;
1453     }
1454     default:
1455       break;
1456   }
1457   return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1458 }
1459 
1460 static gboolean
gst_x_image_sink_propose_allocation(GstBaseSink * bsink,GstQuery * query)1461 gst_x_image_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1462 {
1463   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (bsink);
1464   GstBufferPool *pool = NULL;
1465   GstCaps *caps;
1466   GstVideoInfo info;
1467   guint size;
1468   gboolean need_pool;
1469 
1470   gst_query_parse_allocation (query, &caps, &need_pool);
1471 
1472   if (caps == NULL)
1473     goto no_caps;
1474 
1475   if (!gst_video_info_from_caps (&info, caps))
1476     goto invalid_caps;
1477 
1478   /* the normal size of a frame */
1479   size = info.size;
1480 
1481   if (need_pool) {
1482     pool = gst_x_image_sink_create_pool (ximagesink, caps, info.size, 0);
1483 
1484     if (pool == NULL)
1485       goto no_pool;
1486   }
1487 
1488   /* we need at least 2 buffer because we hold on to the last one */
1489   gst_query_add_allocation_pool (query, pool, size, 2, 0);
1490   if (pool)
1491     gst_object_unref (pool);
1492 
1493   /* we also support various metadata */
1494   gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
1495   gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL);
1496 
1497   return TRUE;
1498 
1499   /* ERRORS */
1500 no_caps:
1501   {
1502     GST_DEBUG_OBJECT (bsink, "no caps specified");
1503     return FALSE;
1504   }
1505 invalid_caps:
1506   {
1507     GST_DEBUG_OBJECT (bsink, "invalid caps specified");
1508     return FALSE;
1509   }
1510 no_pool:
1511   {
1512     /* Already warned in create_pool */
1513     return FALSE;
1514   }
1515 }
1516 
1517 /* Interfaces stuff */
1518 static void
gst_x_image_sink_navigation_send_event(GstNavigation * navigation,GstStructure * structure)1519 gst_x_image_sink_navigation_send_event (GstNavigation * navigation,
1520     GstStructure * structure)
1521 {
1522   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (navigation);
1523   GstEvent *event = NULL;
1524   gint x_offset, y_offset;
1525   gdouble x, y;
1526   gboolean handled = FALSE;
1527 
1528   /* We are not converting the pointer coordinates as there's no hardware
1529      scaling done here. The only possible scaling is done by videoscale and
1530      videoscale will have to catch those events and tranform the coordinates
1531      to match the applied scaling. So here we just add the offset if the image
1532      is centered in the window.  */
1533 
1534   /* We take the flow_lock while we look at the window */
1535   g_mutex_lock (&ximagesink->flow_lock);
1536 
1537   if (!ximagesink->xwindow) {
1538     g_mutex_unlock (&ximagesink->flow_lock);
1539     gst_structure_free (structure);
1540     return;
1541   }
1542 
1543   x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
1544   y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
1545 
1546   g_mutex_unlock (&ximagesink->flow_lock);
1547 
1548   if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) {
1549     x -= x_offset / 2;
1550     gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
1551   }
1552   if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
1553     y -= y_offset / 2;
1554     gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
1555   }
1556 
1557   event = gst_event_new_navigation (structure);
1558   if (event) {
1559     gst_event_ref (event);
1560     handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (ximagesink), event);
1561 
1562     if (!handled)
1563       gst_element_post_message (GST_ELEMENT_CAST (ximagesink),
1564           gst_navigation_message_new_event (GST_OBJECT_CAST (ximagesink),
1565               event));
1566 
1567     gst_event_unref (event);
1568   }
1569 }
1570 
1571 static void
gst_x_image_sink_navigation_init(GstNavigationInterface * iface)1572 gst_x_image_sink_navigation_init (GstNavigationInterface * iface)
1573 {
1574   iface->send_event = gst_x_image_sink_navigation_send_event;
1575 }
1576 
1577 static void
gst_x_image_sink_set_window_handle(GstVideoOverlay * overlay,guintptr id)1578 gst_x_image_sink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
1579 {
1580   XID xwindow_id = id;
1581   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1582   GstXWindow *xwindow = NULL;
1583 
1584   /* We acquire the stream lock while setting this window in the element.
1585      We are basically cleaning tons of stuff replacing the old window, putting
1586      images while we do that would surely crash */
1587   g_mutex_lock (&ximagesink->flow_lock);
1588 
1589   /* If we already use that window return */
1590   if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) {
1591     g_mutex_unlock (&ximagesink->flow_lock);
1592     return;
1593   }
1594 
1595   /* If the element has not initialized the X11 context try to do so */
1596   if (!ximagesink->xcontext &&
1597       !(ximagesink->xcontext = gst_x_image_sink_xcontext_get (ximagesink))) {
1598     g_mutex_unlock (&ximagesink->flow_lock);
1599     /* we have thrown a GST_ELEMENT_ERROR now */
1600     return;
1601   }
1602 
1603   /* If a window is there already we destroy it */
1604   if (ximagesink->xwindow) {
1605     gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1606     ximagesink->xwindow = NULL;
1607   }
1608 
1609   /* If the xid is 0 we go back to an internal window */
1610   if (xwindow_id == 0) {
1611     /* If no width/height caps nego did not happen window will be created
1612        during caps nego then */
1613     if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
1614       xwindow = gst_x_image_sink_xwindow_new (ximagesink,
1615           GST_VIDEO_SINK_WIDTH (ximagesink),
1616           GST_VIDEO_SINK_HEIGHT (ximagesink));
1617     }
1618   } else {
1619     xwindow = g_new0 (GstXWindow, 1);
1620 
1621     xwindow->win = xwindow_id;
1622 
1623     /* We set the events we want to receive and create a GC. */
1624     g_mutex_lock (&ximagesink->x_lock);
1625     xwindow->internal = FALSE;
1626     if (ximagesink->handle_events) {
1627       XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
1628           StructureNotifyMask | PointerMotionMask | KeyPressMask |
1629           KeyReleaseMask);
1630     }
1631 
1632     xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
1633     g_mutex_unlock (&ximagesink->x_lock);
1634   }
1635 
1636   if (xwindow) {
1637     ximagesink->xwindow = xwindow;
1638     /* Update the window geometry, possibly generating a reconfigure event. */
1639     gst_x_image_sink_xwindow_update_geometry (ximagesink);
1640   }
1641 
1642   g_mutex_unlock (&ximagesink->flow_lock);
1643 }
1644 
1645 static void
gst_x_image_sink_expose(GstVideoOverlay * overlay)1646 gst_x_image_sink_expose (GstVideoOverlay * overlay)
1647 {
1648   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1649 
1650   gst_x_image_sink_xwindow_update_geometry (ximagesink);
1651   gst_x_image_sink_ximage_put (ximagesink, NULL);
1652 }
1653 
1654 static void
gst_x_image_sink_set_event_handling(GstVideoOverlay * overlay,gboolean handle_events)1655 gst_x_image_sink_set_event_handling (GstVideoOverlay * overlay,
1656     gboolean handle_events)
1657 {
1658   GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1659 
1660   ximagesink->handle_events = handle_events;
1661 
1662   g_mutex_lock (&ximagesink->flow_lock);
1663 
1664   if (G_UNLIKELY (!ximagesink->xwindow)) {
1665     g_mutex_unlock (&ximagesink->flow_lock);
1666     return;
1667   }
1668 
1669   g_mutex_lock (&ximagesink->x_lock);
1670 
1671   if (handle_events) {
1672     if (ximagesink->xwindow->internal) {
1673       XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1674           ExposureMask | StructureNotifyMask | PointerMotionMask |
1675           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1676     } else {
1677       XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1678           ExposureMask | StructureNotifyMask | PointerMotionMask |
1679           KeyPressMask | KeyReleaseMask);
1680     }
1681   } else {
1682     XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0);
1683   }
1684 
1685   g_mutex_unlock (&ximagesink->x_lock);
1686 
1687   g_mutex_unlock (&ximagesink->flow_lock);
1688 }
1689 
1690 static void
gst_x_image_sink_video_overlay_init(GstVideoOverlayInterface * iface)1691 gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface * iface)
1692 {
1693   iface->set_window_handle = gst_x_image_sink_set_window_handle;
1694   iface->expose = gst_x_image_sink_expose;
1695   iface->handle_events = gst_x_image_sink_set_event_handling;
1696 }
1697 
1698 /* =========================================== */
1699 /*                                             */
1700 /*              Init & Class init              */
1701 /*                                             */
1702 /* =========================================== */
1703 
1704 static void
gst_x_image_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1705 gst_x_image_sink_set_property (GObject * object, guint prop_id,
1706     const GValue * value, GParamSpec * pspec)
1707 {
1708   GstXImageSink *ximagesink;
1709 
1710   g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
1711 
1712   ximagesink = GST_X_IMAGE_SINK (object);
1713 
1714   switch (prop_id) {
1715     case PROP_DISPLAY:
1716       ximagesink->display_name = g_strdup (g_value_get_string (value));
1717       break;
1718     case PROP_SYNCHRONOUS:
1719       ximagesink->synchronous = g_value_get_boolean (value);
1720       if (ximagesink->xcontext) {
1721         GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1722             ximagesink->synchronous ? "TRUE" : "FALSE");
1723         g_mutex_lock (&ximagesink->x_lock);
1724         XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1725         g_mutex_unlock (&ximagesink->x_lock);
1726       }
1727       break;
1728     case PROP_FORCE_ASPECT_RATIO:
1729       ximagesink->keep_aspect = g_value_get_boolean (value);
1730       break;
1731     case PROP_PIXEL_ASPECT_RATIO:
1732     {
1733       GValue *tmp;
1734 
1735       tmp = g_new0 (GValue, 1);
1736       g_value_init (tmp, GST_TYPE_FRACTION);
1737 
1738       if (!g_value_transform (value, tmp)) {
1739         GST_WARNING_OBJECT (ximagesink,
1740             "Could not transform string to aspect ratio");
1741         g_free (tmp);
1742       } else {
1743         GST_DEBUG_OBJECT (ximagesink, "set PAR to %d/%d",
1744             gst_value_get_fraction_numerator (tmp),
1745             gst_value_get_fraction_denominator (tmp));
1746         g_free (ximagesink->par);
1747         ximagesink->par = tmp;
1748       }
1749     }
1750       break;
1751     case PROP_HANDLE_EVENTS:
1752       gst_x_image_sink_set_event_handling (GST_VIDEO_OVERLAY (ximagesink),
1753           g_value_get_boolean (value));
1754       gst_x_image_sink_manage_event_thread (ximagesink);
1755       break;
1756     case PROP_HANDLE_EXPOSE:
1757       ximagesink->handle_expose = g_value_get_boolean (value);
1758       gst_x_image_sink_manage_event_thread (ximagesink);
1759       break;
1760     default:
1761       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1762       break;
1763   }
1764 }
1765 
1766 static void
gst_x_image_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1767 gst_x_image_sink_get_property (GObject * object, guint prop_id,
1768     GValue * value, GParamSpec * pspec)
1769 {
1770   GstXImageSink *ximagesink;
1771 
1772   g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
1773 
1774   ximagesink = GST_X_IMAGE_SINK (object);
1775 
1776   switch (prop_id) {
1777     case PROP_DISPLAY:
1778       g_value_set_string (value, ximagesink->display_name);
1779       break;
1780     case PROP_SYNCHRONOUS:
1781       g_value_set_boolean (value, ximagesink->synchronous);
1782       break;
1783     case PROP_FORCE_ASPECT_RATIO:
1784       g_value_set_boolean (value, ximagesink->keep_aspect);
1785       break;
1786     case PROP_PIXEL_ASPECT_RATIO:
1787       if (ximagesink->par)
1788         g_value_transform (ximagesink->par, value);
1789       break;
1790     case PROP_HANDLE_EVENTS:
1791       g_value_set_boolean (value, ximagesink->handle_events);
1792       break;
1793     case PROP_HANDLE_EXPOSE:
1794       g_value_set_boolean (value, ximagesink->handle_expose);
1795       break;
1796     case PROP_WINDOW_WIDTH:
1797       if (ximagesink->xwindow)
1798         g_value_set_uint64 (value, ximagesink->xwindow->width);
1799       else
1800         g_value_set_uint64 (value, 0);
1801       break;
1802     case PROP_WINDOW_HEIGHT:
1803       if (ximagesink->xwindow)
1804         g_value_set_uint64 (value, ximagesink->xwindow->height);
1805       else
1806         g_value_set_uint64 (value, 0);
1807       break;
1808     default:
1809       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1810       break;
1811   }
1812 }
1813 
1814 static void
gst_x_image_sink_reset(GstXImageSink * ximagesink)1815 gst_x_image_sink_reset (GstXImageSink * ximagesink)
1816 {
1817   GThread *thread;
1818 
1819   GST_OBJECT_LOCK (ximagesink);
1820   ximagesink->running = FALSE;
1821   /* grab thread and mark it as NULL */
1822   thread = ximagesink->event_thread;
1823   ximagesink->event_thread = NULL;
1824   GST_OBJECT_UNLOCK (ximagesink);
1825 
1826   /* Wait for our event thread to finish before we clean up our stuff. */
1827   if (thread)
1828     g_thread_join (thread);
1829 
1830   if (ximagesink->cur_image) {
1831     gst_buffer_unref (ximagesink->cur_image);
1832     ximagesink->cur_image = NULL;
1833   }
1834 
1835   g_mutex_lock (&ximagesink->flow_lock);
1836 
1837   if (ximagesink->pool) {
1838     gst_object_unref (ximagesink->pool);
1839     ximagesink->pool = NULL;
1840   }
1841 
1842   if (ximagesink->xwindow) {
1843     gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
1844     gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1845     ximagesink->xwindow = NULL;
1846   }
1847   g_mutex_unlock (&ximagesink->flow_lock);
1848 
1849   gst_x_image_sink_xcontext_clear (ximagesink);
1850 }
1851 
1852 static void
gst_x_image_sink_finalize(GObject * object)1853 gst_x_image_sink_finalize (GObject * object)
1854 {
1855   GstXImageSink *ximagesink;
1856 
1857   ximagesink = GST_X_IMAGE_SINK (object);
1858 
1859   gst_x_image_sink_reset (ximagesink);
1860 
1861   if (ximagesink->display_name) {
1862     g_free (ximagesink->display_name);
1863     ximagesink->display_name = NULL;
1864   }
1865   if (ximagesink->par) {
1866     g_free (ximagesink->par);
1867     ximagesink->par = NULL;
1868   }
1869   g_mutex_clear (&ximagesink->x_lock);
1870   g_mutex_clear (&ximagesink->flow_lock);
1871 
1872   g_free (ximagesink->media_title);
1873 
1874   G_OBJECT_CLASS (parent_class)->finalize (object);
1875 }
1876 
1877 static void
gst_x_image_sink_init(GstXImageSink * ximagesink)1878 gst_x_image_sink_init (GstXImageSink * ximagesink)
1879 {
1880   ximagesink->display_name = NULL;
1881   ximagesink->xcontext = NULL;
1882   ximagesink->xwindow = NULL;
1883   ximagesink->cur_image = NULL;
1884 
1885   ximagesink->event_thread = NULL;
1886   ximagesink->running = FALSE;
1887 
1888   ximagesink->fps_n = 0;
1889   ximagesink->fps_d = 1;
1890 
1891   g_mutex_init (&ximagesink->x_lock);
1892   g_mutex_init (&ximagesink->flow_lock);
1893 
1894   ximagesink->par = NULL;
1895 
1896   ximagesink->pool = NULL;
1897 
1898   ximagesink->synchronous = FALSE;
1899   ximagesink->keep_aspect = TRUE;
1900   ximagesink->handle_events = TRUE;
1901   ximagesink->handle_expose = TRUE;
1902 }
1903 
1904 static void
gst_x_image_sink_class_init(GstXImageSinkClass * klass)1905 gst_x_image_sink_class_init (GstXImageSinkClass * klass)
1906 {
1907   GObjectClass *gobject_class;
1908   GstElementClass *gstelement_class;
1909   GstBaseSinkClass *gstbasesink_class;
1910   GstVideoSinkClass *videosink_class;
1911 
1912   gobject_class = (GObjectClass *) klass;
1913   gstelement_class = (GstElementClass *) klass;
1914   gstbasesink_class = (GstBaseSinkClass *) klass;
1915   videosink_class = (GstVideoSinkClass *) klass;
1916 
1917   gobject_class->finalize = gst_x_image_sink_finalize;
1918   gobject_class->set_property = gst_x_image_sink_set_property;
1919   gobject_class->get_property = gst_x_image_sink_get_property;
1920 
1921   g_object_class_install_property (gobject_class, PROP_DISPLAY,
1922       g_param_spec_string ("display", "Display", "X Display name",
1923           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1924   g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
1925       g_param_spec_boolean ("synchronous", "Synchronous",
1926           "When enabled, runs the X display in synchronous mode. "
1927           "(unrelated to A/V sync, used only for debugging)", FALSE,
1928           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1929   g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
1930       g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
1931           "When enabled, reverse caps negotiation (scaling) will respect "
1932           "original aspect ratio", TRUE,
1933           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1934   g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
1935       g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
1936           "The pixel aspect ratio of the device", "1/1",
1937           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1938   g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
1939       g_param_spec_boolean ("handle-events", "Handle XEvents",
1940           "When enabled, XEvents will be selected and handled", TRUE,
1941           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1942   g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
1943       g_param_spec_boolean ("handle-expose", "Handle expose",
1944           "When enabled, "
1945           "the current frame will always be drawn in response to X Expose "
1946           "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1947 
1948   /**
1949    * GstXImageSink:window-width
1950    *
1951    * Actual width of the video window.
1952    */
1953   g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
1954       g_param_spec_uint64 ("window-width", "window-width",
1955           "Width of the window", 0, G_MAXUINT64, 0,
1956           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1957 
1958   /**
1959    * GstXImageSink:window-height
1960    *
1961    * Actual height of the video window.
1962    */
1963   g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
1964       g_param_spec_uint64 ("window-height", "window-height",
1965           "Height of the window", 0, G_MAXUINT64, 0,
1966           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1967 
1968   gst_element_class_set_static_metadata (gstelement_class,
1969       "Video sink", "Sink/Video",
1970       "A standard X based videosink", "Julien Moutte <julien@moutte.net>");
1971 
1972   gst_element_class_add_static_pad_template (gstelement_class,
1973       &gst_x_image_sink_sink_template_factory);
1974 
1975   gstelement_class->change_state = gst_x_image_sink_change_state;
1976 
1977   gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_getcaps);
1978   gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_setcaps);
1979   gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_x_image_sink_get_times);
1980   gstbasesink_class->propose_allocation =
1981       GST_DEBUG_FUNCPTR (gst_x_image_sink_propose_allocation);
1982   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_x_image_sink_event);
1983 
1984   videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_x_image_sink_show_frame);
1985 }
1986