xvcontext: protect X call with lock
[platform/upstream/gstreamer.git] / sys / xvimage / xvcontext.c
1 /* GStreamer
2  * Copyright (C) <2005> Julien Moutte <julien@moutte.net>
3  *               <2009>,<2010> Stefan Kost <stefan.kost@nokia.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 /* for developers: there are two useful tools : xvinfo and xvattr */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 /* Object header */
28 #include "xvcontext.h"
29
30 /* Debugging category */
31 #include <gst/gstinfo.h>
32
33 /* for XkbKeycodeToKeysym */
34 #include <X11/XKBlib.h>
35
36 GST_DEBUG_CATEGORY_EXTERN (gst_debug_xvcontext);
37 GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE);
38 #define GST_CAT_DEFAULT gst_debug_xvcontext
39
40 void
41 gst_xvcontext_config_clear (GstXvContextConfig * config)
42 {
43   if (config->display_name) {
44     g_free (config->display_name);
45     config->display_name = NULL;
46   }
47   config->adaptor_nr = -1;
48 }
49
50 GST_DEFINE_MINI_OBJECT_TYPE (GstXvContext, gst_xvcontext);
51
52 typedef struct
53 {
54   unsigned long flags;
55   unsigned long functions;
56   unsigned long decorations;
57   long input_mode;
58   unsigned long status;
59 }
60 MotifWmHints, MwmHints;
61
62 #define MWM_HINTS_DECORATIONS   (1L << 1)
63
64
65 static void
66 gst_lookup_xv_port_from_adaptor (GstXvContext * context,
67     XvAdaptorInfo * adaptors, guint adaptor_nr)
68 {
69   gint j;
70   gint res;
71
72   /* Do we support XvImageMask ? */
73   if (!(adaptors[adaptor_nr].type & XvImageMask)) {
74     GST_DEBUG ("XV Adaptor %s has no support for XvImageMask",
75         adaptors[adaptor_nr].name);
76     return;
77   }
78
79   /* We found such an adaptor, looking for an available port */
80   for (j = 0; j < adaptors[adaptor_nr].num_ports && !context->xv_port_id; j++) {
81     /* We try to grab the port */
82     res = XvGrabPort (context->disp, adaptors[adaptor_nr].base_id + j, 0);
83     if (Success == res) {
84       context->xv_port_id = adaptors[adaptor_nr].base_id + j;
85       GST_DEBUG ("XV Adaptor %s with %ld ports", adaptors[adaptor_nr].name,
86           adaptors[adaptor_nr].num_ports);
87     } else {
88       GST_DEBUG ("GrabPort %d for XV Adaptor %s failed: %d", j,
89           adaptors[adaptor_nr].name, res);
90     }
91   }
92 }
93
94 /* This function generates a caps with all supported format by the first
95    Xv grabable port we find. We store each one of the supported formats in a
96    format list and append the format to a newly created caps that we return
97    If this function does not return NULL because of an error, it also grabs
98    the port via XvGrabPort */
99 static GstCaps *
100 gst_xvcontext_get_xv_support (GstXvContext * context,
101     const GstXvContextConfig * config, GError ** error)
102 {
103   gint i;
104   XvAdaptorInfo *adaptors;
105   gint nb_formats;
106   XvImageFormatValues *formats = NULL;
107   guint nb_encodings;
108   XvEncodingInfo *encodings = NULL;
109   gulong max_w = G_MAXINT, max_h = G_MAXINT;
110   GstCaps *caps = NULL;
111   GstCaps *rgb_caps = NULL;
112
113   g_return_val_if_fail (context != NULL, NULL);
114
115   /* First let's check that XVideo extension is available */
116   if (!XQueryExtension (context->disp, "XVideo", &i, &i, &i))
117     goto no_xv;
118
119   /* Then we get adaptors list */
120   if (Success != XvQueryAdaptors (context->disp, context->root,
121           &context->nb_adaptors, &adaptors))
122     goto no_adaptors;
123
124   context->xv_port_id = 0;
125
126   GST_DEBUG ("Found %u XV adaptor(s)", context->nb_adaptors);
127
128   context->adaptors =
129       (gchar **) g_malloc0 (context->nb_adaptors * sizeof (gchar *));
130
131   /* Now fill up our adaptor name array */
132   for (i = 0; i < context->nb_adaptors; i++) {
133     context->adaptors[i] = g_strdup (adaptors[i].name);
134   }
135
136   if (config->adaptor_nr != -1 && config->adaptor_nr < context->nb_adaptors) {
137     /* Find xv port from user defined adaptor */
138     gst_lookup_xv_port_from_adaptor (context, adaptors, config->adaptor_nr);
139   }
140
141   if (!context->xv_port_id) {
142     /* Now search for an adaptor that supports XvImageMask */
143     for (i = 0; i < context->nb_adaptors && !context->xv_port_id; i++) {
144       gst_lookup_xv_port_from_adaptor (context, adaptors, i);
145       context->adaptor_nr = i;
146     }
147   }
148
149   XvFreeAdaptorInfo (adaptors);
150
151   if (!context->xv_port_id)
152     goto no_ports;
153
154   /* Set XV_AUTOPAINT_COLORKEY and XV_DOUBLE_BUFFER and XV_COLORKEY */
155   {
156     int count, todo = 3;
157     XvAttribute *const attr = XvQueryPortAttributes (context->disp,
158         context->xv_port_id, &count);
159     static const char autopaint[] = "XV_AUTOPAINT_COLORKEY";
160     static const char dbl_buffer[] = "XV_DOUBLE_BUFFER";
161     static const char colorkey[] = "XV_COLORKEY";
162
163     GST_DEBUG ("Checking %d Xv port attributes", count);
164
165     context->have_autopaint_colorkey = FALSE;
166     context->have_double_buffer = FALSE;
167     context->have_colorkey = FALSE;
168
169     for (i = 0; ((i < count) && todo); i++) {
170       GST_DEBUG ("Got attribute %s", attr[i].name);
171
172       if (!strcmp (attr[i].name, autopaint)) {
173         const Atom atom = XInternAtom (context->disp, autopaint, False);
174
175         /* turn on autopaint colorkey */
176         XvSetPortAttribute (context->disp, context->xv_port_id, atom,
177             (config->autopaint_colorkey ? 1 : 0));
178         todo--;
179         context->have_autopaint_colorkey = TRUE;
180       } else if (!strcmp (attr[i].name, dbl_buffer)) {
181         const Atom atom = XInternAtom (context->disp, dbl_buffer, False);
182
183         XvSetPortAttribute (context->disp, context->xv_port_id, atom,
184             (config->double_buffer ? 1 : 0));
185         todo--;
186         context->have_double_buffer = TRUE;
187       } else if (!strcmp (attr[i].name, colorkey)) {
188         /* Set the colorkey, default is something that is dark but hopefully
189          * won't randomly appear on the screen elsewhere (ie not black or greys)
190          * can be overridden by setting "colorkey" property
191          */
192         const Atom atom = XInternAtom (context->disp, colorkey, False);
193         guint32 ckey = 0;
194         gboolean set_attr = TRUE;
195         guint cr, cg, cb;
196
197         /* set a colorkey in the right format RGB565/RGB888
198          * We only handle these 2 cases, because they're the only types of
199          * devices we've encountered. If we don't recognise it, leave it alone
200          */
201         cr = (config->colorkey >> 16);
202         cg = (config->colorkey >> 8) & 0xFF;
203         cb = (config->colorkey) & 0xFF;
204         switch (context->depth) {
205           case 16:             /* RGB 565 */
206             cr >>= 3;
207             cg >>= 2;
208             cb >>= 3;
209             ckey = (cr << 11) | (cg << 5) | cb;
210             break;
211           case 24:
212           case 32:             /* RGB 888 / ARGB 8888 */
213             ckey = (cr << 16) | (cg << 8) | cb;
214             break;
215           default:
216             GST_DEBUG ("Unknown bit depth %d for Xv Colorkey - not adjusting",
217                 context->depth);
218             set_attr = FALSE;
219             break;
220         }
221
222         if (set_attr) {
223           ckey = CLAMP (ckey, (guint32) attr[i].min_value,
224               (guint32) attr[i].max_value);
225           GST_LOG ("Setting color key for display depth %d to 0x%x",
226               context->depth, ckey);
227
228           XvSetPortAttribute (context->disp, context->xv_port_id, atom,
229               (gint) ckey);
230         }
231         todo--;
232         context->have_colorkey = TRUE;
233       }
234     }
235
236     XFree (attr);
237   }
238
239   /* Get the list of encodings supported by the adapter and look for the
240    * XV_IMAGE encoding so we can determine the maximum width and height
241    * supported */
242   XvQueryEncodings (context->disp, context->xv_port_id, &nb_encodings,
243       &encodings);
244
245   for (i = 0; i < nb_encodings; i++) {
246     GST_LOG ("Encoding %d, name %s, max wxh %lux%lu rate %d/%d",
247         i, encodings[i].name, encodings[i].width, encodings[i].height,
248         encodings[i].rate.numerator, encodings[i].rate.denominator);
249     if (strcmp (encodings[i].name, "XV_IMAGE") == 0) {
250       max_w = encodings[i].width;
251       max_h = encodings[i].height;
252     }
253   }
254
255   XvFreeEncodingInfo (encodings);
256
257   /* We get all image formats supported by our port */
258   formats = XvListImageFormats (context->disp,
259       context->xv_port_id, &nb_formats);
260   caps = gst_caps_new_empty ();
261   for (i = 0; i < nb_formats; i++) {
262     GstCaps *format_caps = NULL;
263     gboolean is_rgb_format = FALSE;
264     GstVideoFormat vformat;
265
266     /* We set the image format of the context to an existing one. This
267        is just some valid image format for making our xshm calls check before
268        caps negotiation really happens. */
269     context->im_format = formats[i].id;
270
271     switch (formats[i].type) {
272       case XvRGB:
273       {
274         XvImageFormatValues *fmt = &(formats[i]);
275         gint endianness;
276
277         endianness =
278             (fmt->byte_order == LSBFirst ? G_LITTLE_ENDIAN : G_BIG_ENDIAN);
279
280         vformat = gst_video_format_from_masks (fmt->depth, fmt->bits_per_pixel,
281             endianness, fmt->red_mask, fmt->green_mask, fmt->blue_mask, 0);
282         if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
283           break;
284
285         format_caps = gst_caps_new_simple ("video/x-raw",
286             "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
287             "width", GST_TYPE_INT_RANGE, 1, max_w,
288             "height", GST_TYPE_INT_RANGE, 1, max_h,
289             "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
290
291         is_rgb_format = TRUE;
292         break;
293       }
294       case XvYUV:
295       {
296         vformat = gst_video_format_from_fourcc (formats[i].id);
297         if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
298           break;
299
300         format_caps = gst_caps_new_simple ("video/x-raw",
301             "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
302             "width", GST_TYPE_INT_RANGE, 1, max_w,
303             "height", GST_TYPE_INT_RANGE, 1, max_h,
304             "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
305         break;
306       }
307       default:
308         vformat = GST_VIDEO_FORMAT_UNKNOWN;
309         g_assert_not_reached ();
310         break;
311     }
312
313     if (format_caps) {
314       GstXvImageFormat *format = NULL;
315
316       format = g_new0 (GstXvImageFormat, 1);
317       if (format) {
318         format->format = formats[i].id;
319         format->vformat = vformat;
320         format->caps = gst_caps_copy (format_caps);
321         context->formats_list = g_list_append (context->formats_list, format);
322       }
323
324       if (is_rgb_format) {
325         if (rgb_caps == NULL)
326           rgb_caps = format_caps;
327         else
328           gst_caps_append (rgb_caps, format_caps);
329       } else
330         gst_caps_append (caps, format_caps);
331     }
332   }
333
334   /* Collected all caps into either the caps or rgb_caps structures.
335    * Append rgb_caps on the end of YUV, so that YUV is always preferred */
336   if (rgb_caps)
337     gst_caps_append (caps, rgb_caps);
338
339   if (formats)
340     XFree (formats);
341
342   GST_DEBUG ("Generated the following caps: %" GST_PTR_FORMAT, caps);
343
344   if (gst_caps_is_empty (caps))
345     goto no_caps;
346
347   return caps;
348
349   /* ERRORS */
350 no_xv:
351   {
352     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_SETTINGS,
353         ("XVideo extension is not available"));
354     return NULL;
355   }
356 no_adaptors:
357   {
358     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_SETTINGS,
359         ("Failed getting XV adaptors list"));
360     return NULL;
361   }
362 no_ports:
363   {
364     context->adaptor_nr = -1;
365     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_BUSY,
366         ("No Xv Port available"));
367     return NULL;
368   }
369 no_caps:
370   {
371     gst_caps_unref (caps);
372     g_set_error (error, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE,
373         ("No supported format found"));
374     return NULL;
375   }
376 }
377
378 /* This function calculates the pixel aspect ratio based on the properties
379  * in the context structure and stores it there. */
380 static void
381 gst_xvcontext_calculate_pixel_aspect_ratio (GstXvContext * context)
382 {
383   static const gint par[][2] = {
384     {1, 1},                     /* regular screen */
385     {16, 15},                   /* PAL TV */
386     {11, 10},                   /* 525 line Rec.601 video */
387     {54, 59},                   /* 625 line Rec.601 video */
388     {64, 45},                   /* 1280x1024 on 16:9 display */
389     {5, 3},                     /* 1280x1024 on 4:3 display */
390     {4, 3}                      /*  800x600 on 16:9 display */
391   };
392   gint i;
393   gint index;
394   gdouble ratio;
395   gdouble delta;
396
397 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
398
399   /* first calculate the "real" ratio based on the X values;
400    * which is the "physical" w/h divided by the w/h in pixels of the display */
401   ratio = (gdouble) (context->widthmm * context->height)
402       / (context->heightmm * context->width);
403
404   /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
405    * override here */
406   if (context->width == 720 && context->height == 576) {
407     ratio = 4.0 * 576 / (3.0 * 720);
408   }
409   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
410
411   /* now find the one from par[][2] with the lowest delta to the real one */
412   delta = DELTA (0);
413   index = 0;
414
415   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
416     gdouble this_delta = DELTA (i);
417
418     if (this_delta < delta) {
419       index = i;
420       delta = this_delta;
421     }
422   }
423
424   GST_DEBUG ("Decided on index %d (%d/%d)", index,
425       par[index][0], par[index][1]);
426
427   g_free (context->par);
428   context->par = g_new0 (GValue, 1);
429   g_value_init (context->par, GST_TYPE_FRACTION);
430   gst_value_set_fraction (context->par, par[index][0], par[index][1]);
431   GST_DEBUG ("set context PAR to %d/%d",
432       gst_value_get_fraction_numerator (context->par),
433       gst_value_get_fraction_denominator (context->par));
434 }
435
436 #ifdef HAVE_XSHM
437 /* X11 stuff */
438 static gboolean error_caught = FALSE;
439
440 static int
441 gst_xvimage_handle_xerror (Display * display, XErrorEvent * xevent)
442 {
443   char error_msg[1024];
444
445   XGetErrorText (display, xevent->error_code, error_msg, 1024);
446   GST_DEBUG ("xvimage triggered an XError. error: %s", error_msg);
447   error_caught = TRUE;
448   return 0;
449 }
450
451 /* This function checks that it is actually really possible to create an image
452    using XShm */
453 static gboolean
454 gst_xvcontext_check_xshm_calls (GstXvContext * context)
455 {
456   XvImage *xvimage;
457   XShmSegmentInfo SHMInfo;
458   size_t size;
459   int (*handler) (Display *, XErrorEvent *);
460   gboolean result = FALSE;
461   gboolean did_attach = FALSE;
462
463   g_return_val_if_fail (context != NULL, FALSE);
464
465   /* Sync to ensure any older errors are already processed */
466   XSync (context->disp, FALSE);
467
468   /* Set defaults so we don't free these later unnecessarily */
469   SHMInfo.shmaddr = ((void *) -1);
470   SHMInfo.shmid = -1;
471
472   /* Setting an error handler to catch failure */
473   error_caught = FALSE;
474   handler = XSetErrorHandler (gst_xvimage_handle_xerror);
475
476   /* Trying to create a 1x1 picture */
477   GST_DEBUG ("XvShmCreateImage of 1x1");
478   xvimage = XvShmCreateImage (context->disp, context->xv_port_id,
479       context->im_format, NULL, 1, 1, &SHMInfo);
480
481   /* Might cause an error, sync to ensure it is noticed */
482   XSync (context->disp, FALSE);
483   if (!xvimage || error_caught) {
484     GST_WARNING ("could not XvShmCreateImage a 1x1 image");
485     goto beach;
486   }
487   size = xvimage->data_size;
488   SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777);
489   if (SHMInfo.shmid == -1) {
490     GST_WARNING ("could not get shared memory of %" G_GSIZE_FORMAT " bytes",
491         size);
492     goto beach;
493   }
494
495   SHMInfo.shmaddr = shmat (SHMInfo.shmid, NULL, 0);
496   if (SHMInfo.shmaddr == ((void *) -1)) {
497     GST_WARNING ("Failed to shmat: %s", g_strerror (errno));
498     /* Clean up the shared memory segment */
499     shmctl (SHMInfo.shmid, IPC_RMID, NULL);
500     goto beach;
501   }
502
503   xvimage->data = SHMInfo.shmaddr;
504   SHMInfo.readOnly = FALSE;
505
506   if (XShmAttach (context->disp, &SHMInfo) == 0) {
507     GST_WARNING ("Failed to XShmAttach");
508     /* Clean up the shared memory segment */
509     shmctl (SHMInfo.shmid, IPC_RMID, NULL);
510     goto beach;
511   }
512
513   /* Sync to ensure we see any errors we caused */
514   XSync (context->disp, FALSE);
515
516   /* Delete the shared memory segment as soon as everyone is attached.
517    * This way, it will be deleted as soon as we detach later, and not
518    * leaked if we crash. */
519   shmctl (SHMInfo.shmid, IPC_RMID, NULL);
520
521   if (!error_caught) {
522     GST_DEBUG ("XServer ShmAttached to 0x%x, id 0x%lx", SHMInfo.shmid,
523         SHMInfo.shmseg);
524
525     did_attach = TRUE;
526     /* store whether we succeeded in result */
527     result = TRUE;
528   } else {
529     GST_WARNING ("MIT-SHM extension check failed at XShmAttach. "
530         "Not using shared memory.");
531   }
532
533 beach:
534   /* Sync to ensure we swallow any errors we caused and reset error_caught */
535   XSync (context->disp, FALSE);
536
537   error_caught = FALSE;
538   XSetErrorHandler (handler);
539
540   if (did_attach) {
541     GST_DEBUG ("XServer ShmDetaching from 0x%x id 0x%lx",
542         SHMInfo.shmid, SHMInfo.shmseg);
543     XShmDetach (context->disp, &SHMInfo);
544     XSync (context->disp, FALSE);
545   }
546   if (SHMInfo.shmaddr != ((void *) -1))
547     shmdt (SHMInfo.shmaddr);
548   if (xvimage)
549     XFree (xvimage);
550   return result;
551 }
552 #endif /* HAVE_XSHM */
553
554 static GstXvContext *
555 gst_xvcontext_copy (GstXvContext * context)
556 {
557   return NULL;
558 }
559
560 static void
561 gst_xvcontext_free (GstXvContext * context)
562 {
563   GList *formats_list, *channels_list;
564   gint i = 0;
565
566   GST_LOG ("free %p", context);
567
568   formats_list = context->formats_list;
569
570   while (formats_list) {
571     GstXvImageFormat *format = formats_list->data;
572
573     gst_caps_unref (format->caps);
574     g_free (format);
575     formats_list = g_list_next (formats_list);
576   }
577
578   if (context->formats_list)
579     g_list_free (context->formats_list);
580
581   channels_list = context->channels_list;
582
583   while (channels_list) {
584     GstColorBalanceChannel *channel = channels_list->data;
585
586     g_object_unref (channel);
587     channels_list = g_list_next (channels_list);
588   }
589
590   if (context->channels_list)
591     g_list_free (context->channels_list);
592
593   if (context->caps)
594     gst_caps_unref (context->caps);
595   if (context->last_caps)
596     gst_caps_unref (context->last_caps);
597
598   for (i = 0; i < context->nb_adaptors; i++) {
599     g_free (context->adaptors[i]);
600   }
601
602   g_free (context->adaptors);
603
604   g_free (context->par);
605
606   GST_DEBUG ("Closing display and freeing X Context");
607
608   if (context->xv_port_id)
609     XvUngrabPort (context->disp, context->xv_port_id, 0);
610
611   if (context->disp)
612     XCloseDisplay (context->disp);
613
614   g_mutex_clear (&context->lock);
615
616   g_slice_free1 (sizeof (GstXvContext), context);
617 }
618
619
620 /* This function gets the X Display and global info about it. Everything is
621    stored in our object and will be cleaned when the object is disposed. Note
622    here that caps for supported format are generated without any window or
623    image creation */
624 GstXvContext *
625 gst_xvcontext_new (GstXvContextConfig * config, GError ** error)
626 {
627   GstXvContext *context = NULL;
628   XPixmapFormatValues *px_formats = NULL;
629   gint nb_formats = 0, i, j, N_attr;
630   XvAttribute *xv_attr;
631   Atom prop_atom;
632   const char *channels[4] = { "XV_HUE", "XV_SATURATION",
633     "XV_BRIGHTNESS", "XV_CONTRAST"
634   };
635
636   g_return_val_if_fail (config != NULL, NULL);
637
638   context = g_slice_new0 (GstXvContext);
639
640   gst_mini_object_init (GST_MINI_OBJECT_CAST (context), 0,
641       gst_xvcontext_get_type (),
642       (GstMiniObjectCopyFunction) gst_xvcontext_copy,
643       (GstMiniObjectDisposeFunction) NULL,
644       (GstMiniObjectFreeFunction) gst_xvcontext_free);
645
646   g_mutex_init (&context->lock);
647   context->im_format = 0;
648   context->adaptor_nr = -1;
649
650   if (!(context->disp = XOpenDisplay (config->display_name)))
651     goto no_display;
652
653   context->screen = DefaultScreenOfDisplay (context->disp);
654   context->screen_num = DefaultScreen (context->disp);
655   context->visual = DefaultVisual (context->disp, context->screen_num);
656   context->root = DefaultRootWindow (context->disp);
657   context->white = XWhitePixel (context->disp, context->screen_num);
658   context->black = XBlackPixel (context->disp, context->screen_num);
659   context->depth = DefaultDepthOfScreen (context->screen);
660
661   context->width = DisplayWidth (context->disp, context->screen_num);
662   context->height = DisplayHeight (context->disp, context->screen_num);
663   context->widthmm = DisplayWidthMM (context->disp, context->screen_num);
664   context->heightmm = DisplayHeightMM (context->disp, context->screen_num);
665
666   GST_DEBUG ("X reports %dx%d pixels and %d mm x %d mm",
667       context->width, context->height, context->widthmm, context->heightmm);
668
669   gst_xvcontext_calculate_pixel_aspect_ratio (context);
670   /* We get supported pixmap formats at supported depth */
671   px_formats = XListPixmapFormats (context->disp, &nb_formats);
672
673   if (!px_formats)
674     goto no_pixel_formats;
675
676   /* We get bpp value corresponding to our running depth */
677   for (i = 0; i < nb_formats; i++) {
678     if (px_formats[i].depth == context->depth)
679       context->bpp = px_formats[i].bits_per_pixel;
680   }
681
682   XFree (px_formats);
683
684   context->endianness =
685       (ImageByteOrder (context->disp) ==
686       LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
687
688   /* our caps system handles 24/32bpp RGB as big-endian. */
689   if ((context->bpp == 24 || context->bpp == 32) &&
690       context->endianness == G_LITTLE_ENDIAN) {
691     context->endianness = G_BIG_ENDIAN;
692     context->visual->red_mask = GUINT32_TO_BE (context->visual->red_mask);
693     context->visual->green_mask = GUINT32_TO_BE (context->visual->green_mask);
694     context->visual->blue_mask = GUINT32_TO_BE (context->visual->blue_mask);
695     if (context->bpp == 24) {
696       context->visual->red_mask >>= 8;
697       context->visual->green_mask >>= 8;
698       context->visual->blue_mask >>= 8;
699     }
700   }
701
702   if (!(context->caps = gst_xvcontext_get_xv_support (context, config, error)))
703     goto no_caps;
704
705   /* Search for XShm extension support */
706 #ifdef HAVE_XSHM
707   if (XShmQueryExtension (context->disp) &&
708       gst_xvcontext_check_xshm_calls (context)) {
709     context->use_xshm = TRUE;
710     GST_DEBUG ("xvimagesink is using XShm extension");
711   } else
712 #endif /* HAVE_XSHM */
713   {
714     context->use_xshm = FALSE;
715     GST_DEBUG ("xvimagesink is not using XShm extension");
716   }
717
718   xv_attr = XvQueryPortAttributes (context->disp, context->xv_port_id, &N_attr);
719
720   /* Generate the channels list */
721   for (i = 0; i < (sizeof (channels) / sizeof (char *)); i++) {
722     XvAttribute *matching_attr = NULL;
723
724     /* Retrieve the property atom if it exists. If it doesn't exist,
725      * the attribute itself must not either, so we can skip */
726     prop_atom = XInternAtom (context->disp, channels[i], True);
727     if (prop_atom == None)
728       continue;
729
730     if (xv_attr != NULL) {
731       for (j = 0; j < N_attr && matching_attr == NULL; ++j)
732         if (!g_ascii_strcasecmp (channels[i], xv_attr[j].name))
733           matching_attr = xv_attr + j;
734     }
735
736     if (matching_attr) {
737       GstColorBalanceChannel *channel;
738
739       channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL);
740       channel->label = g_strdup (channels[i]);
741       channel->min_value = matching_attr ? matching_attr->min_value : -1000;
742       channel->max_value = matching_attr ? matching_attr->max_value : 1000;
743
744       context->channels_list = g_list_append (context->channels_list, channel);
745
746       /* If the colorbalance settings have not been touched we get Xv values
747          as defaults and update our internal variables */
748       if (!config->cb_changed) {
749         gint val;
750
751         XvGetPortAttribute (context->disp, context->xv_port_id,
752             prop_atom, &val);
753         /* Normalize val to [-1000, 1000] */
754         val = floor (0.5 + -1000 + 2000 * (val - channel->min_value) /
755             (double) (channel->max_value - channel->min_value));
756
757         if (!g_ascii_strcasecmp (channels[i], "XV_HUE"))
758           config->hue = val;
759         else if (!g_ascii_strcasecmp (channels[i], "XV_SATURATION"))
760           config->saturation = val;
761         else if (!g_ascii_strcasecmp (channels[i], "XV_BRIGHTNESS"))
762           config->brightness = val;
763         else if (!g_ascii_strcasecmp (channels[i], "XV_CONTRAST"))
764           config->contrast = val;
765       }
766     }
767   }
768
769   if (xv_attr)
770     XFree (xv_attr);
771
772   return context;
773
774   /* ERRORS */
775 no_display:
776   {
777     gst_xvcontext_unref (context);
778     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE,
779         "Could not open display %s", config->display_name);
780     return NULL;
781   }
782 no_pixel_formats:
783   {
784     gst_xvcontext_unref (context);
785     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_SETTINGS,
786         ("Could not get pixel formats"));
787     return NULL;
788   }
789 no_caps:
790   {
791     gst_xvcontext_unref (context);
792     return NULL;
793   }
794 }
795
796 void
797 gst_xvcontext_set_synchronous (GstXvContext * context, gboolean synchronous)
798 {
799   /* call XSynchronize with the current value of synchronous */
800   GST_DEBUG ("XSynchronize called with %s", synchronous ? "TRUE" : "FALSE");
801   g_mutex_lock (&context->lock);
802   XSynchronize (context->disp, synchronous);
803   g_mutex_unlock (&context->lock);
804 }
805
806 void
807 gst_xvcontext_update_colorbalance (GstXvContext * context,
808     GstXvContextConfig * config)
809 {
810   GList *channels = NULL;
811
812   /* Don't set the attributes if they haven't been changed, to avoid
813    * rounding errors changing the values */
814   if (!config->cb_changed)
815     return;
816
817   /* For each channel of the colorbalance we calculate the correct value
818      doing range conversion and then set the Xv port attribute to match our
819      values. */
820   channels = context->channels_list;
821
822   while (channels) {
823     if (channels->data && GST_IS_COLOR_BALANCE_CHANNEL (channels->data)) {
824       GstColorBalanceChannel *channel = NULL;
825       Atom prop_atom;
826       gint value = 0;
827       gdouble convert_coef;
828
829       channel = GST_COLOR_BALANCE_CHANNEL (channels->data);
830       g_object_ref (channel);
831
832       /* Our range conversion coef */
833       convert_coef = (channel->max_value - channel->min_value) / 2000.0;
834
835       if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
836         value = config->hue;
837       } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
838         value = config->saturation;
839       } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
840         value = config->contrast;
841       } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
842         value = config->brightness;
843       } else {
844         g_warning ("got an unknown channel %s", channel->label);
845         g_object_unref (channel);
846         return;
847       }
848
849       /* Committing to Xv port */
850       g_mutex_lock (&context->lock);
851       prop_atom = XInternAtom (context->disp, channel->label, True);
852       if (prop_atom != None) {
853         int xv_value;
854         xv_value =
855             floor (0.5 + (value + 1000) * convert_coef + channel->min_value);
856         XvSetPortAttribute (context->disp,
857             context->xv_port_id, prop_atom, xv_value);
858       }
859       g_mutex_unlock (&context->lock);
860
861       g_object_unref (channel);
862     }
863     channels = g_list_next (channels);
864   }
865 }
866
867 /* This function tries to get a format matching with a given caps in the
868    supported list of formats we generated in gst_xvimagesink_get_xv_support */
869 gint
870 gst_xvcontext_get_format_from_info (GstXvContext * context, GstVideoInfo * info)
871 {
872   GList *list = NULL;
873
874   list = context->formats_list;
875
876   while (list) {
877     GstXvImageFormat *format = list->data;
878
879     if (format && format->vformat == GST_VIDEO_INFO_FORMAT (info))
880       return format->format;
881
882     list = g_list_next (list);
883   }
884   return -1;
885 }
886
887 GstXWindow *
888 gst_xvcontext_create_xwindow (GstXvContext * context, gint width, gint height)
889 {
890   GstXWindow *window;
891   Atom wm_delete;
892   Atom hints_atom = None;
893
894   g_return_val_if_fail (GST_IS_XVCONTEXT (context), NULL);
895
896   window = g_slice_new0 (GstXWindow);
897
898   window->context = gst_xvcontext_ref (context);
899   window->render_rect.x = window->render_rect.y = 0;
900   window->render_rect.w = width;
901   window->render_rect.h = height;
902   window->have_render_rect = FALSE;
903
904   window->width = width;
905   window->height = height;
906   window->internal = TRUE;
907
908   g_mutex_lock (&context->lock);
909
910   window->win = XCreateSimpleWindow (context->disp,
911       context->root, 0, 0, width, height, 0, 0, context->black);
912
913   /* We have to do that to prevent X from redrawing the background on
914    * ConfigureNotify. This takes away flickering of video when resizing. */
915   XSetWindowBackgroundPixmap (context->disp, window->win, None);
916
917   /* Tell the window manager we'd like delete client messages instead of
918    * being killed */
919   wm_delete = XInternAtom (context->disp, "WM_DELETE_WINDOW", True);
920   if (wm_delete != None) {
921     (void) XSetWMProtocols (context->disp, window->win, &wm_delete, 1);
922   }
923
924   hints_atom = XInternAtom (context->disp, "_MOTIF_WM_HINTS", True);
925   if (hints_atom != None) {
926     MotifWmHints *hints;
927
928     hints = g_malloc0 (sizeof (MotifWmHints));
929
930     hints->flags |= MWM_HINTS_DECORATIONS;
931     hints->decorations = 1 << 0;
932
933     XChangeProperty (context->disp, window->win,
934         hints_atom, hints_atom, 32, PropModeReplace,
935         (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
936
937     XSync (context->disp, FALSE);
938
939     g_free (hints);
940   }
941
942   window->gc = XCreateGC (context->disp, window->win, 0, NULL);
943
944   XMapRaised (context->disp, window->win);
945
946   XSync (context->disp, FALSE);
947
948   g_mutex_unlock (&context->lock);
949
950   return window;
951 }
952
953 GstXWindow *
954 gst_xvcontext_create_xwindow_from_xid (GstXvContext * context, XID xid)
955 {
956   GstXWindow *window;
957   XWindowAttributes attr;
958
959   window = g_slice_new0 (GstXWindow);
960   window->win = xid;
961   window->context = gst_xvcontext_ref (context);
962
963   /* Set the event we want to receive and create a GC */
964   g_mutex_lock (&context->lock);
965
966   XGetWindowAttributes (context->disp, window->win, &attr);
967
968   window->width = attr.width;
969   window->height = attr.height;
970   window->internal = FALSE;
971
972   window->have_render_rect = FALSE;
973   window->render_rect.x = window->render_rect.y = 0;
974   window->render_rect.w = attr.width;
975   window->render_rect.h = attr.height;
976
977   window->gc = XCreateGC (context->disp, window->win, 0, NULL);
978   g_mutex_unlock (&context->lock);
979
980   return window;
981 }
982
983 void
984 gst_xwindow_destroy (GstXWindow * window)
985 {
986   GstXvContext *context;
987
988   g_return_if_fail (window != NULL);
989
990   context = window->context;
991
992   g_mutex_lock (&context->lock);
993
994   /* If we did not create that window we just free the GC and let it live */
995   if (window->internal)
996     XDestroyWindow (context->disp, window->win);
997   else
998     XSelectInput (context->disp, window->win, 0);
999
1000   XFreeGC (context->disp, window->gc);
1001
1002   XSync (context->disp, FALSE);
1003
1004   g_mutex_unlock (&context->lock);
1005
1006   gst_xvcontext_unref (context);
1007
1008   g_slice_free1 (sizeof (GstXWindow), window);
1009 }
1010
1011 void
1012 gst_xwindow_set_event_handling (GstXWindow * window, gboolean handle_events)
1013 {
1014   GstXvContext *context;
1015
1016   g_return_if_fail (window != NULL);
1017
1018   context = window->context;
1019
1020   g_mutex_lock (&context->lock);
1021   if (handle_events) {
1022     if (window->internal) {
1023       XSelectInput (context->disp, window->win,
1024           ExposureMask | StructureNotifyMask | PointerMotionMask |
1025           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1026     } else {
1027       XSelectInput (context->disp, window->win,
1028           ExposureMask | StructureNotifyMask | PointerMotionMask |
1029           KeyPressMask | KeyReleaseMask);
1030     }
1031   } else {
1032     XSelectInput (context->disp, window->win, 0);
1033   }
1034   g_mutex_unlock (&context->lock);
1035 }
1036
1037 void
1038 gst_xwindow_set_title (GstXWindow * window, const gchar * title)
1039 {
1040   GstXvContext *context;
1041
1042   g_return_if_fail (window != NULL);
1043
1044   context = window->context;
1045
1046   /* we have a window */
1047   if (window->internal && title) {
1048     XTextProperty xproperty;
1049
1050     if ((XStringListToTextProperty (((char **) &title), 1, &xproperty)) != 0) {
1051       XSetWMName (context->disp, window->win, &xproperty);
1052       XFree (xproperty.value);
1053     }
1054   }
1055 }
1056
1057 void
1058 gst_xwindow_update_geometry (GstXWindow * window)
1059 {
1060   XWindowAttributes attr;
1061   GstXvContext *context;
1062
1063   g_return_if_fail (window != NULL);
1064
1065   context = window->context;
1066
1067   /* Update the window geometry */
1068   g_mutex_lock (&context->lock);
1069   XGetWindowAttributes (context->disp, window->win, &attr);
1070
1071   window->width = attr.width;
1072   window->height = attr.height;
1073
1074   if (!window->have_render_rect) {
1075     window->render_rect.x = window->render_rect.y = 0;
1076     window->render_rect.w = attr.width;
1077     window->render_rect.h = attr.height;
1078   }
1079
1080   g_mutex_unlock (&context->lock);
1081 }
1082
1083
1084 void
1085 gst_xwindow_clear (GstXWindow * window)
1086 {
1087   GstXvContext *context;
1088
1089   g_return_if_fail (window != NULL);
1090
1091   context = window->context;
1092
1093   g_mutex_lock (&context->lock);
1094
1095   XvStopVideo (context->disp, context->xv_port_id, window->win);
1096
1097   XSync (context->disp, FALSE);
1098
1099   g_mutex_unlock (&context->lock);
1100 }
1101
1102 void
1103 gst_xwindow_set_render_rectangle (GstXWindow * window,
1104     gint x, gint y, gint width, gint height)
1105 {
1106   g_return_if_fail (window != NULL);
1107
1108   if (width >= 0 && height >= 0) {
1109     window->render_rect.x = x;
1110     window->render_rect.y = y;
1111     window->render_rect.w = width;
1112     window->render_rect.h = height;
1113     window->have_render_rect = TRUE;
1114   } else {
1115     window->render_rect.x = 0;
1116     window->render_rect.y = 0;
1117     window->render_rect.w = window->width;
1118     window->render_rect.h = window->height;
1119     window->have_render_rect = FALSE;
1120   }
1121 }