Update and add documentation for platform specific plugins (sys).
[platform/upstream/gst-plugins-good.git] / sys / ximage / gstximagesrc.c
1 /* GStreamer
2  *
3  * Copyright (C) 2006 Zaheer Merali <zaheerabbas at merali dot org>
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., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * SECTION:element-ximagesrc
23  *
24  * This element captures your X Display and creates raw RGB video.  It uses
25  * the XDamage extension if available to only capture areas of the screen that
26  * have changed since the last frame.  It uses the XFixes extension if
27  * available to also capture your mouse pointer.  By default it will fixate to
28  * 25 frames per second.
29  *
30  * <refsect2>
31  * <title>Example pipelines</title>
32  * |[
33  * gst-launch ximagesrc ! video/x-raw-rgb,framerate=5/1 ! ffmpegcolorspace ! theoraenc ! oggmux ! filesink location=desktop.ogg
34  * ]| Encodes your X display to an Ogg theora video at 5 frames per second.
35  * </refsect2>
36  */
37
38 #ifdef HAVE_CONFIG_H
39 #include "config.h"
40 #endif
41 #include "gstximagesrc.h"
42
43 #include <string.h>
44 #include <stdlib.h>
45
46 #include <X11/Xlib.h>
47 #include <X11/Xutil.h>
48
49 #include <gst/gst.h>
50 #include <gst/gst-i18n-plugin.h>
51
52 GST_DEBUG_CATEGORY_STATIC (gst_debug_ximage_src);
53 #define GST_CAT_DEFAULT gst_debug_ximage_src
54
55 /* elementfactory information */
56 static const GstElementDetails ximagesrc_details =
57 GST_ELEMENT_DETAILS ("Ximage video source",
58     "Source/Video",
59     "Creates a screenshot video stream",
60     "Lutz Mueller <lutz@users.sourceforge.net>, "
61     "Jan Schmidt <thaytan@mad.scientist.com>, "
62     "Zaheer Merali <zaheerabbas at merali dot org>");
63
64 static GstStaticPadTemplate t =
65 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
66     GST_STATIC_CAPS ("video/x-raw-rgb, "
67         "framerate = (fraction) [ 0, MAX ], "
68         "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ], "
69         "pixel-aspect-ratio = (fraction) [ 0, MAX ]"));
70
71 enum
72 {
73   PROP_0,
74   PROP_DISPLAY_NAME,
75   PROP_SCREEN_NUM,
76   PROP_SHOW_POINTER,
77   PROP_USE_DAMAGE,
78   PROP_STARTX,
79   PROP_STARTY,
80   PROP_ENDX,
81   PROP_ENDY
82 };
83
84 GST_BOILERPLATE (GstXImageSrc, gst_ximage_src, GstPushSrc, GST_TYPE_PUSH_SRC);
85
86 static void gst_ximage_src_fixate (GstPad * pad, GstCaps * caps);
87 static void gst_ximage_src_clear_bufpool (GstXImageSrc * ximagesrc);
88
89 /* Called when a buffer is returned from the pipeline */
90 static void
91 gst_ximage_src_return_buf (GstXImageSrc * ximagesrc,
92     GstXImageSrcBuffer * ximage)
93 {
94   /* If our geometry changed we can't reuse that image. */
95   if ((ximage->width != ximagesrc->width) ||
96       (ximage->height != ximagesrc->height)) {
97     GST_DEBUG_OBJECT (ximagesrc,
98         "destroy image %p as its size changed %dx%d vs current %dx%d",
99         ximage, ximage->width, ximage->height,
100         ximagesrc->width, ximagesrc->height);
101     g_mutex_lock (ximagesrc->x_lock);
102     gst_ximageutil_ximage_destroy (ximagesrc->xcontext, ximage);
103     g_mutex_unlock (ximagesrc->x_lock);
104   } else {
105     /* In that case we can reuse the image and add it to our image pool. */
106     GST_LOG_OBJECT (ximagesrc, "recycling image %p in pool", ximage);
107     /* need to increment the refcount again to recycle */
108     gst_buffer_ref (GST_BUFFER (ximage));
109     g_mutex_lock (ximagesrc->pool_lock);
110     ximagesrc->buffer_pool = g_slist_prepend (ximagesrc->buffer_pool, ximage);
111     g_mutex_unlock (ximagesrc->pool_lock);
112   }
113 }
114
115 static gboolean
116 gst_ximage_src_open_display (GstXImageSrc * s, const gchar * name)
117 {
118   g_return_val_if_fail (GST_IS_XIMAGE_SRC (s), FALSE);
119
120   if (s->xcontext != NULL)
121     return TRUE;
122
123   g_mutex_lock (s->x_lock);
124   s->xcontext = ximageutil_xcontext_get (GST_ELEMENT (s), name);
125   if (s->xcontext == NULL) {
126     g_mutex_unlock (s->x_lock);
127     GST_ELEMENT_ERROR (s, RESOURCE, OPEN_READ,
128         ("Could not open X display for reading"),
129         ("NULL returned from getting xcontext"));
130     return FALSE;
131   }
132   s->width = s->xcontext->width;
133   s->height = s->xcontext->height;
134
135   /* Always capture root window, for now */
136   s->xwindow = s->xcontext->root;
137
138 #ifdef HAVE_XFIXES
139   /* check if xfixes supported */
140   {
141     int error_base;
142
143     if (XFixesQueryExtension (s->xcontext->disp, &s->fixes_event_base,
144             &error_base)) {
145       s->have_xfixes = TRUE;
146       GST_DEBUG_OBJECT (s, "X Server supports XFixes");
147     } else {
148
149       GST_DEBUG_OBJECT (s, "X Server does not support XFixes");
150     }
151   }
152
153 #ifdef HAVE_XDAMAGE
154   /* check if xdamage is supported */
155   {
156     int error_base;
157     long evmask = NoEventMask;
158
159     s->have_xdamage = FALSE;
160     s->damage = None;
161     s->damage_copy_gc = None;
162     s->damage_region = None;
163
164     if (XDamageQueryExtension (s->xcontext->disp, &s->damage_event_base,
165             &error_base)) {
166       s->damage =
167           XDamageCreate (s->xcontext->disp, s->xwindow, XDamageReportNonEmpty);
168       if (s->damage != None) {
169         s->damage_region = XFixesCreateRegion (s->xcontext->disp, NULL, 0);
170         if (s->damage_region != None) {
171           XGCValues values;
172
173           GST_DEBUG_OBJECT (s, "Using XDamage extension");
174           values.subwindow_mode = IncludeInferiors;
175           s->damage_copy_gc = XCreateGC (s->xcontext->disp,
176               s->xwindow, GCSubwindowMode, &values);
177           XSelectInput (s->xcontext->disp, s->xwindow, evmask);
178
179           s->have_xdamage = TRUE;
180         } else {
181           XDamageDestroy (s->xcontext->disp, s->damage);
182           s->damage = None;
183         }
184       } else
185         GST_DEBUG_OBJECT (s, "Could not attach to XDamage");
186     } else {
187       GST_DEBUG_OBJECT (s, "X Server does not have XDamage extension");
188     }
189   }
190 #endif
191 #endif
192
193   g_mutex_unlock (s->x_lock);
194
195   if (s->xcontext == NULL)
196     return FALSE;
197
198   return TRUE;
199 }
200
201 static gboolean
202 gst_ximage_src_start (GstBaseSrc * basesrc)
203 {
204   GstXImageSrc *s = GST_XIMAGE_SRC (basesrc);
205
206   s->last_frame_no = -1;
207 #ifdef HAVE_XDAMAGE
208   if (s->last_ximage)
209     gst_buffer_unref (GST_BUFFER_CAST (s->last_ximage));
210   s->last_ximage = NULL;
211 #endif
212   return gst_ximage_src_open_display (s, s->display_name);
213 }
214
215 static gboolean
216 gst_ximage_src_stop (GstBaseSrc * basesrc)
217 {
218   GstXImageSrc *src = GST_XIMAGE_SRC (basesrc);
219
220 #ifdef HAVE_XDAMAGE
221   if (src->last_ximage)
222     gst_buffer_unref (GST_BUFFER_CAST (src->last_ximage));
223   src->last_ximage = NULL;
224 #endif
225
226   gst_ximage_src_clear_bufpool (src);
227
228 #ifdef HAVE_XFIXES
229   if (src->cursor_image)
230     XFree (src->cursor_image);
231   src->cursor_image = NULL;
232 #endif
233
234   if (src->xcontext) {
235     g_mutex_lock (src->x_lock);
236
237 #ifdef HAVE_XDAMAGE
238     if (src->damage_copy_gc != None) {
239       XFreeGC (src->xcontext->disp, src->damage_copy_gc);
240       src->damage_copy_gc = None;
241     }
242     if (src->damage_region != None) {
243       XFixesDestroyRegion (src->xcontext->disp, src->damage_region);
244       src->damage_region = None;
245     }
246     if (src->damage != None) {
247       XDamageDestroy (src->xcontext->disp, src->damage);
248       src->damage = None;
249     }
250 #endif
251
252     ximageutil_xcontext_clear (src->xcontext);
253     src->xcontext = NULL;
254     g_mutex_unlock (src->x_lock);
255   }
256
257   return TRUE;
258 }
259
260 static gboolean
261 gst_ximage_src_unlock (GstBaseSrc * basesrc)
262 {
263   GstXImageSrc *src = GST_XIMAGE_SRC (basesrc);
264
265   /* Awaken the create() func if it's waiting on the clock */
266   GST_OBJECT_LOCK (src);
267   if (src->clock_id) {
268     GST_DEBUG_OBJECT (src, "Waking up waiting clock");
269     gst_clock_id_unschedule (src->clock_id);
270   }
271   GST_OBJECT_UNLOCK (src);
272
273   return TRUE;
274 }
275
276 static gboolean
277 gst_ximage_src_recalc (GstXImageSrc * src)
278 {
279   if (!src->xcontext)
280     return FALSE;
281
282   /* Maybe later we can check the display hasn't changed size */
283   /* We could use XQueryPointer to get only the current window. */
284   return TRUE;
285 }
286
287 #ifdef HAVE_XFIXES
288 static void
289 composite_pixel (GstXContext * xcontext, guchar * dest, guchar * src)
290 {
291   guint8 r = src[2];
292   guint8 g = src[1];
293   guint8 b = src[0];
294   guint8 a = src[3];
295   guint8 dr, dg, db;
296   guint32 color;
297   gint r_shift, r_max, r_shift_out;
298   gint g_shift, g_max, g_shift_out;
299   gint b_shift, b_max, b_shift_out;
300
301   switch (xcontext->bpp) {
302     case 8:
303       color = *dest;
304       break;
305     case 16:
306       color = GUINT16_FROM_LE (*(guint16 *) (dest));
307       break;
308     case 32:
309       color = GUINT32_FROM_LE (*(guint32 *) (dest));
310       break;
311     default:
312       /* Should not reach here */
313       g_return_if_reached ();
314   }
315
316   /* possible optimisation:
317    * move the code that finds shift and max in the _link function */
318   for (r_shift = 0; !(xcontext->visual->red_mask & (1 << r_shift)); r_shift++);
319   for (g_shift = 0; !(xcontext->visual->green_mask & (1 << g_shift));
320       g_shift++);
321   for (b_shift = 0; !(xcontext->visual->blue_mask & (1 << b_shift)); b_shift++);
322
323   for (r_shift_out = 0; !(xcontext->visual->red_mask & (1 << r_shift_out));
324       r_shift_out++);
325   for (g_shift_out = 0; !(xcontext->visual->green_mask & (1 << g_shift_out));
326       g_shift_out++);
327   for (b_shift_out = 0; !(xcontext->visual->blue_mask & (1 << b_shift_out));
328       b_shift_out++);
329
330
331   r_max = (xcontext->visual->red_mask >> r_shift);
332   b_max = (xcontext->visual->blue_mask >> b_shift);
333   g_max = (xcontext->visual->green_mask >> g_shift);
334
335 #define RGBXXX_R(x)  (((x)>>r_shift) & (r_max))
336 #define RGBXXX_G(x)  (((x)>>g_shift) & (g_max))
337 #define RGBXXX_B(x)  (((x)>>b_shift) & (b_max))
338
339   dr = (RGBXXX_R (color) * 255) / r_max;
340   dg = (RGBXXX_G (color) * 255) / g_max;
341   db = (RGBXXX_B (color) * 255) / b_max;
342
343   dr = (r * a + (0xff - a) * dr) / 0xff;
344   dg = (g * a + (0xff - a) * dg) / 0xff;
345   db = (b * a + (0xff - a) * db) / 0xff;
346
347   color = (((dr * r_max) / 255) << r_shift_out) +
348       (((dg * g_max) / 255) << g_shift_out) +
349       (((db * b_max) / 255) << b_shift_out);
350
351   switch (xcontext->bpp) {
352     case 8:
353       *dest = color;
354       break;
355     case 16:
356       *(guint16 *) (dest) = color;
357       break;
358     case 32:
359       *(guint32 *) (dest) = color;
360       break;
361     default:
362       g_warning ("bpp %d not supported\n", xcontext->bpp);
363   }
364 }
365 #endif
366
367 /* Retrieve an XImageSrcBuffer, preferably from our
368  * pool of existing images and populate it from the window */
369 static GstXImageSrcBuffer *
370 gst_ximage_src_ximage_get (GstXImageSrc * ximagesrc)
371 {
372   GstXImageSrcBuffer *ximage = NULL;
373
374   g_mutex_lock (ximagesrc->pool_lock);
375   while (ximagesrc->buffer_pool != NULL) {
376     ximage = ximagesrc->buffer_pool->data;
377
378     if ((ximage->width != ximagesrc->width) ||
379         (ximage->height != ximagesrc->height)) {
380       gst_ximage_buffer_free (ximage);
381     }
382
383     ximagesrc->buffer_pool = g_slist_delete_link (ximagesrc->buffer_pool,
384         ximagesrc->buffer_pool);
385   }
386   g_mutex_unlock (ximagesrc->pool_lock);
387
388   if (ximage == NULL) {
389     GstXContext *xcontext;
390     GstCaps *caps = NULL;
391
392     GST_DEBUG_OBJECT (ximagesrc, "creating image (%dx%d)",
393         ximagesrc->width, ximagesrc->height);
394
395     g_mutex_lock (ximagesrc->x_lock);
396     ximage = gst_ximageutil_ximage_new (ximagesrc->xcontext,
397         GST_ELEMENT (ximagesrc), ximagesrc->width, ximagesrc->height,
398         (BufferReturnFunc) (gst_ximage_src_return_buf));
399     if (ximage == NULL) {
400       GST_ELEMENT_ERROR (ximagesrc, RESOURCE, WRITE, (NULL),
401           ("could not create a %dx%d ximage", ximage->width, ximage->height));
402       g_mutex_unlock (ximagesrc->x_lock);
403       return NULL;
404     }
405
406     xcontext = ximagesrc->xcontext;
407
408
409     caps = gst_caps_new_simple ("video/x-raw-rgb",
410         "bpp", G_TYPE_INT, xcontext->bpp,
411         "depth", G_TYPE_INT, xcontext->depth,
412         "endianness", G_TYPE_INT, xcontext->endianness,
413         "red_mask", G_TYPE_INT, xcontext->r_mask_output,
414         "green_mask", G_TYPE_INT, xcontext->g_mask_output,
415         "blue_mask", G_TYPE_INT, xcontext->b_mask_output,
416         "width", G_TYPE_INT, ximagesrc->width,
417         "height", G_TYPE_INT, ximagesrc->height,
418         "framerate", GST_TYPE_FRACTION, ximagesrc->fps_n, ximagesrc->fps_d,
419         "pixel-aspect-ratio", GST_TYPE_FRACTION,
420         gst_value_get_fraction_numerator (xcontext->par),
421         gst_value_get_fraction_denominator (xcontext->par), NULL);
422
423     gst_buffer_set_caps (GST_BUFFER (ximage), caps);
424     g_mutex_unlock (ximagesrc->x_lock);
425
426     gst_caps_unref (caps);
427   }
428
429   g_return_val_if_fail (GST_IS_XIMAGE_SRC (ximagesrc), NULL);
430 #ifdef HAVE_XDAMAGE
431   if (ximagesrc->have_xdamage && ximagesrc->use_damage &&
432       ximagesrc->last_ximage != NULL) {
433     XEvent ev;
434
435     /* have_frame is TRUE when either the entire screen has been
436      * grabbed or when the last image has been copied */
437     gboolean have_frame = FALSE;
438
439     GST_DEBUG_OBJECT (ximagesrc, "Retrieving screen using XDamage");
440
441     do {
442       XNextEvent (ximagesrc->xcontext->disp, &ev);
443
444       if (ev.type == ximagesrc->damage_event_base + XDamageNotify) {
445         XserverRegion parts;
446         XRectangle *rects;
447         int nrects;
448
449         parts = XFixesCreateRegion (ximagesrc->xcontext->disp, 0, 0);
450         XDamageSubtract (ximagesrc->xcontext->disp, ximagesrc->damage, None,
451             parts);
452         /* Now copy out all of the damaged rectangles. */
453         rects = XFixesFetchRegion (ximagesrc->xcontext->disp, parts, &nrects);
454         if (rects != NULL) {
455           int i;
456
457           if (!have_frame) {
458             GST_LOG_OBJECT (ximagesrc,
459                 "Copying from last frame ximage->size: %d",
460                 GST_BUFFER_SIZE (GST_BUFFER (ximage)));
461             memcpy (GST_BUFFER_DATA (GST_BUFFER (ximage)),
462                 GST_BUFFER_DATA (GST_BUFFER (ximagesrc->last_ximage)),
463                 GST_BUFFER_SIZE (GST_BUFFER (ximage)));
464             have_frame = TRUE;
465           }
466           for (i = 0; i < nrects; i++) {
467             GST_LOG_OBJECT (ximagesrc,
468                 "Damaged sub-region @ %d,%d size %dx%d reported",
469                 rects[i].x, rects[i].y, rects[i].width, rects[i].height);
470
471             /* if we only want a small area, clip this damage region to
472              * area we want */
473             if (ximagesrc->endx > ximagesrc->startx &&
474                 ximagesrc->endy > ximagesrc->starty) {
475               /* see if damage area intersects */
476               if (rects[i].x + rects[i].width < ximagesrc->startx ||
477                   rects[i].x > ximagesrc->endx) {
478                 /* trivial reject */
479               } else if (rects[i].y + rects[i].height < ximagesrc->starty ||
480                   rects[i].y > ximagesrc->endy) {
481                 /* trivial reject */
482               } else {
483                 /* find intersect region */
484                 int startx, starty, width, height;
485
486                 startx = (rects[i].x < ximagesrc->startx) ? ximagesrc->startx :
487                     rects[i].x;
488                 starty = (rects[i].y < ximagesrc->starty) ? ximagesrc->starty :
489                     rects[i].y;
490                 width = (rects[i].x + rects[i].width < ximagesrc->endx) ?
491                     rects[i].x + rects[i].width - startx :
492                     ximagesrc->endx - startx;
493                 height = (rects[i].y + rects[i].height < ximagesrc->endy) ?
494                     rects[i].y + rects[i].height - starty : ximagesrc->endy -
495                     starty;
496
497                 GST_LOG_OBJECT (ximagesrc,
498                     "Retrieving damaged sub-region @ %d,%d size %dx%d as intersect region",
499                     startx, starty, width, height);
500                 XGetSubImage (ximagesrc->xcontext->disp, ximagesrc->xwindow,
501                     startx, starty, width, height, AllPlanes, ZPixmap,
502                     ximage->ximage, startx - ximagesrc->startx,
503                     starty - ximagesrc->starty);
504               }
505             } else {
506
507               GST_LOG_OBJECT (ximagesrc,
508                   "Retrieving damaged sub-region @ %d,%d size %dx%d",
509                   rects[i].x, rects[i].y, rects[i].width, rects[i].height);
510
511               XGetSubImage (ximagesrc->xcontext->disp, ximagesrc->xwindow,
512                   rects[i].x, rects[i].y,
513                   rects[i].width, rects[i].height,
514                   AllPlanes, ZPixmap, ximage->ximage, rects[i].x, rects[i].y);
515             }
516           }
517           free (rects);
518         }
519       }
520     } while (XPending (ximagesrc->xcontext->disp));
521     if (!have_frame) {
522       GST_LOG_OBJECT (ximagesrc,
523           "Copying from last frame ximage->size: %d",
524           GST_BUFFER_SIZE (GST_BUFFER (ximage)));
525       memcpy (GST_BUFFER_DATA (GST_BUFFER (ximage)),
526           GST_BUFFER_DATA (GST_BUFFER (ximagesrc->last_ximage)),
527           GST_BUFFER_SIZE (GST_BUFFER (ximage)));
528       have_frame = TRUE;
529     }
530 #ifdef HAVE_XFIXES
531     /* re-get area where last mouse pointer was  but only if in our clipping
532      * bounds */
533     if (ximagesrc->cursor_image) {
534       gint x, y, width, height;
535
536       x = ximagesrc->cursor_image->x - ximagesrc->cursor_image->xhot;
537       y = ximagesrc->cursor_image->y - ximagesrc->cursor_image->yhot;
538       width = ximagesrc->cursor_image->width;
539       height = ximagesrc->cursor_image->height;
540
541       /* bounds checking */
542       if (x < 0)
543         x = 0;
544       if (y < 0)
545         y = 0;
546       if (x + width > ximagesrc->xcontext->width)
547         width = ximagesrc->xcontext->width - x;
548       if (y + height > ximagesrc->xcontext->height)
549         height = ximagesrc->xcontext->height - y;
550       g_assert (x >= 0);
551       g_assert (y >= 0);
552       GST_DEBUG_OBJECT (ximagesrc,
553           "Cursor was at (%d,%d) width: %d, height: %d and our range is: (%d,%d) - (%d,%d)",
554           x, y, width, height, ximagesrc->startx, ximagesrc->starty,
555           ximagesrc->endx, ximagesrc->endy);
556       /* only get where cursor last was, if it is in our range */
557       if (ximagesrc->endx > ximagesrc->startx &&
558           ximagesrc->endy > ximagesrc->starty) {
559         /* check bounds */
560         if (x + width < ximagesrc->startx || x > ximagesrc->endx) {
561           /* trivial reject */
562         } else if (y + height < ximagesrc->starty || y > ximagesrc->endy) {
563           /* trivial reject */
564         } else {
565           /* find intersect region */
566           int startx, starty, iwidth, iheight;
567
568           startx = (x < ximagesrc->startx) ? ximagesrc->startx : x;
569           starty = (y < ximagesrc->starty) ? ximagesrc->starty : y;
570           iwidth = (x + width < ximagesrc->endx) ?
571               x + width - startx : ximagesrc->endx - startx;
572           iheight = (y + height < ximagesrc->endy) ?
573               y + height - starty : ximagesrc->endy - starty;
574           GST_DEBUG_OBJECT (ximagesrc, "Removing cursor from %d,%d", x, y);
575           XGetSubImage (ximagesrc->xcontext->disp, ximagesrc->xwindow,
576               startx, starty, iwidth, iheight, AllPlanes, ZPixmap,
577               ximage->ximage, startx - ximagesrc->startx,
578               starty - ximagesrc->starty);
579         }
580       } else {
581
582         GST_DEBUG_OBJECT (ximagesrc, "Removing cursor from %d,%d", x, y);
583         XGetSubImage (ximagesrc->xcontext->disp, ximagesrc->xwindow,
584             x, y, width, height, AllPlanes, ZPixmap, ximage->ximage, x, y);
585       }
586     }
587 #endif
588
589
590   } else {
591 #endif
592
593 #ifdef HAVE_XSHM
594     if (ximagesrc->xcontext->use_xshm) {
595       GST_DEBUG_OBJECT (ximagesrc, "Retrieving screen using XShm");
596       XShmGetImage (ximagesrc->xcontext->disp, ximagesrc->xwindow,
597           ximage->ximage, ximagesrc->startx, ximagesrc->starty, AllPlanes);
598
599     } else
600 #endif /* HAVE_XSHM */
601     {
602       GST_DEBUG_OBJECT (ximagesrc, "Retrieving screen using XGetImage");
603       ximage->ximage = XGetImage (ximagesrc->xcontext->disp, ximagesrc->xwindow,
604           ximagesrc->startx, ximagesrc->starty, ximagesrc->width,
605           ximagesrc->height, AllPlanes, ZPixmap);
606     }
607 #ifdef HAVE_XDAMAGE
608   }
609 #endif
610
611 #ifdef HAVE_XFIXES
612   if (ximagesrc->show_pointer && ximagesrc->have_xfixes) {
613
614     GST_DEBUG_OBJECT (ximagesrc, "Using XFixes to draw cursor");
615     /* get cursor */
616     if (ximagesrc->cursor_image)
617       XFree (ximagesrc->cursor_image);
618     ximagesrc->cursor_image = XFixesGetCursorImage (ximagesrc->xcontext->disp);
619     if (ximagesrc->cursor_image != NULL) {
620       int cx, cy, i, j, count;
621       int startx, starty, iwidth, iheight;
622       gboolean clipped = FALSE;
623       gboolean cursor_in_image = TRUE;
624
625       cx = ximagesrc->cursor_image->x - ximagesrc->cursor_image->xhot;
626       if (cx < 0)
627         cx = 0;
628       cy = ximagesrc->cursor_image->y - ximagesrc->cursor_image->yhot;
629       if (cy < 0)
630         cy = 0;
631       count = ximagesrc->cursor_image->width * ximagesrc->cursor_image->height;
632
633       /* only get where cursor last was, if it is in our range */
634       if (ximagesrc->endx > ximagesrc->startx &&
635           ximagesrc->endy > ximagesrc->starty) {
636         /* check bounds */
637         if (cx + ximagesrc->cursor_image->width < ximagesrc->startx ||
638             cx > ximagesrc->endx) {
639           /* trivial reject */
640           cursor_in_image = FALSE;
641         } else if (cy + ximagesrc->cursor_image->height < ximagesrc->starty ||
642             cy > ximagesrc->endy) {
643           /* trivial reject */
644           cursor_in_image = FALSE;
645         } else {
646           /* find intersect region */
647
648           startx = (cx < ximagesrc->startx) ? ximagesrc->startx : cx;
649           starty = (cy < ximagesrc->starty) ? ximagesrc->starty : cy;
650           iwidth = (cx + ximagesrc->cursor_image->width < ximagesrc->endx) ?
651               cx + ximagesrc->cursor_image->width - startx :
652               ximagesrc->endx - startx;
653           iheight = (cy + ximagesrc->cursor_image->height < ximagesrc->endy) ?
654               cy + ximagesrc->cursor_image->height - starty :
655               ximagesrc->endy - starty;
656           clipped = TRUE;
657         }
658       } else {
659         startx = cx;
660         starty = cy;
661         iwidth = ximagesrc->cursor_image->width;
662         iheight = ximagesrc->cursor_image->height;
663       }
664
665       if (cursor_in_image) {
666         GST_DEBUG_OBJECT (ximagesrc, "Cursor is in image so trying to draw it");
667         for (i = 0; i < count; i++)
668           ximagesrc->cursor_image->pixels[i] =
669               GUINT_TO_LE (ximagesrc->cursor_image->pixels[i]);
670         /* copy those pixels across */
671         for (j = starty;
672             j < starty + iheight && j < ximagesrc->starty + ximagesrc->height;
673             j++) {
674           for (i = startx;
675               i < startx + iwidth && i < ximagesrc->startx + ximagesrc->width;
676               i++) {
677             guint8 *src, *dest;
678
679             src =
680                 (guint8 *) & (ximagesrc->cursor_image->pixels[((j -
681                             cy) * ximagesrc->cursor_image->width + (i - cx))]);
682             dest =
683                 (guint8 *) & (ximage->ximage->data[((j -
684                             ximagesrc->starty) * ximagesrc->width + (i -
685                             ximagesrc->startx)) * (ximagesrc->xcontext->bpp /
686                         8)]);
687
688             composite_pixel (ximagesrc->xcontext, (guint8 *) dest,
689                 (guint8 *) src);
690           }
691         }
692       }
693     }
694   }
695 #endif
696 #ifdef HAVE_XDAMAGE
697   if (ximagesrc->have_xdamage && ximagesrc->use_damage) {
698     /* need to ref ximage to put in last_ximage */
699     gst_buffer_ref (GST_BUFFER (ximage));
700     if (ximagesrc->last_ximage) {
701       gst_buffer_unref (GST_BUFFER (ximagesrc->last_ximage));
702     }
703     ximagesrc->last_ximage = ximage;
704     GST_LOG_OBJECT (ximagesrc, "reffing current buffer for last_ximage");
705   }
706 #endif
707   return ximage;
708 }
709
710 static GstFlowReturn
711 gst_ximage_src_create (GstPushSrc * bs, GstBuffer ** buf)
712 {
713   GstXImageSrc *s = GST_XIMAGE_SRC (bs);
714   GstXImageSrcBuffer *image;
715   GstClockTime base_time;
716   GstClockTime next_capture_ts;
717   GstClockTime dur;
718   gint64 next_frame_no;
719
720   if (!gst_ximage_src_recalc (s)) {
721     GST_ELEMENT_ERROR (s, RESOURCE, FAILED,
722         (_("Changing resolution at runtime is not yet supported.")), (NULL));
723     return GST_FLOW_ERROR;
724   }
725
726   if (s->fps_n <= 0 || s->fps_d <= 0)
727     return GST_FLOW_NOT_NEGOTIATED;     /* FPS must be > 0 */
728
729   /* Now, we might need to wait for the next multiple of the fps
730    * before capturing */
731
732   GST_OBJECT_LOCK (s);
733   if (GST_ELEMENT_CLOCK (s) == NULL) {
734     GST_OBJECT_UNLOCK (s);
735     GST_ELEMENT_ERROR (s, RESOURCE, FAILED,
736         (_("Cannot operate without a clock")), (NULL));
737     return GST_FLOW_ERROR;
738   }
739
740   base_time = GST_ELEMENT_CAST (s)->base_time;
741   next_capture_ts = gst_clock_get_time (GST_ELEMENT_CLOCK (s));
742   next_capture_ts -= base_time;
743
744   /* Figure out which 'frame number' position we're at, based on the cur time
745    * and frame rate */
746   next_frame_no = gst_util_uint64_scale (next_capture_ts,
747       s->fps_n, GST_SECOND * s->fps_d);
748   if (next_frame_no == s->last_frame_no) {
749     GstClockID id;
750     GstClockReturn ret;
751
752     /* Need to wait for the next frame */
753     next_frame_no += 1;
754
755     /* Figure out what the next frame time is */
756     next_capture_ts = gst_util_uint64_scale (next_frame_no,
757         s->fps_d * GST_SECOND, s->fps_n);
758
759     id = gst_clock_new_single_shot_id (GST_ELEMENT_CLOCK (s),
760         next_capture_ts + base_time);
761     s->clock_id = id;
762
763     /* release the object lock while waiting */
764     GST_OBJECT_UNLOCK (s);
765
766     GST_DEBUG_OBJECT (s, "Waiting for next frame time %" G_GUINT64_FORMAT,
767         next_capture_ts);
768     ret = gst_clock_id_wait (id, NULL);
769     GST_OBJECT_LOCK (s);
770
771     gst_clock_id_unref (id);
772     s->clock_id = NULL;
773     if (ret == GST_CLOCK_UNSCHEDULED) {
774       /* Got woken up by the unlock function */
775       GST_OBJECT_UNLOCK (s);
776       return GST_FLOW_WRONG_STATE;
777     }
778     /* Duration is a complete 1/fps frame duration */
779     dur = gst_util_uint64_scale_int (GST_SECOND, s->fps_d, s->fps_n);
780   } else {
781     GstClockTime next_frame_ts;
782
783     GST_DEBUG_OBJECT (s, "No need to wait for next frame time %"
784         G_GUINT64_FORMAT " next frame = %" G_GINT64_FORMAT " prev = %"
785         G_GINT64_FORMAT, next_capture_ts, next_frame_no, s->last_frame_no);
786     next_frame_ts = gst_util_uint64_scale (next_frame_no + 1,
787         s->fps_d * GST_SECOND, s->fps_n);
788     /* Frame duration is from now until the next expected capture time */
789     dur = next_frame_ts - next_capture_ts;
790   }
791   s->last_frame_no = next_frame_no;
792   GST_OBJECT_UNLOCK (s);
793
794   image = gst_ximage_src_ximage_get (s);
795   if (!image)
796     return GST_FLOW_ERROR;
797
798   *buf = GST_BUFFER (image);
799   GST_BUFFER_TIMESTAMP (*buf) = next_capture_ts;
800   GST_BUFFER_DURATION (*buf) = dur;
801
802   return GST_FLOW_OK;
803 }
804
805 static void
806 gst_ximage_src_set_property (GObject * object, guint prop_id,
807     const GValue * value, GParamSpec * pspec)
808 {
809   GstXImageSrc *src = GST_XIMAGE_SRC (object);
810
811   switch (prop_id) {
812     case PROP_DISPLAY_NAME:
813
814       g_free (src->display_name);
815       src->display_name = g_strdup (g_value_get_string (value));
816       break;
817     case PROP_SCREEN_NUM:
818       src->screen_num = g_value_get_uint (value);
819       break;
820     case PROP_SHOW_POINTER:
821       src->show_pointer = g_value_get_boolean (value);
822       break;
823     case PROP_USE_DAMAGE:
824       src->use_damage = g_value_get_boolean (value);
825       break;
826     case PROP_STARTX:
827       src->startx = g_value_get_uint (value);
828       break;
829     case PROP_STARTY:
830       src->starty = g_value_get_uint (value);
831       break;
832     case PROP_ENDX:
833       src->endx = g_value_get_uint (value);
834       break;
835     case PROP_ENDY:
836       src->endy = g_value_get_uint (value);
837       break;
838     default:
839       break;
840   }
841 }
842
843 static void
844 gst_ximage_src_get_property (GObject * object, guint prop_id, GValue * value,
845     GParamSpec * pspec)
846 {
847   GstXImageSrc *src = GST_XIMAGE_SRC (object);
848
849   switch (prop_id) {
850     case PROP_DISPLAY_NAME:
851       if (src->xcontext)
852         g_value_set_string (value, DisplayString (src->xcontext->disp));
853       else
854         g_value_set_string (value, src->display_name);
855
856       break;
857     case PROP_SCREEN_NUM:
858       g_value_set_uint (value, src->screen_num);
859       break;
860     case PROP_SHOW_POINTER:
861       g_value_set_boolean (value, src->show_pointer);
862       break;
863     case PROP_USE_DAMAGE:
864       g_value_set_boolean (value, src->use_damage);
865       break;
866     case PROP_STARTX:
867       g_value_set_uint (value, src->startx);
868       break;
869     case PROP_STARTY:
870       g_value_set_uint (value, src->starty);
871       break;
872     case PROP_ENDX:
873       g_value_set_uint (value, src->endx);
874       break;
875     case PROP_ENDY:
876       g_value_set_uint (value, src->endy);
877       break;
878     default:
879       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
880       break;
881   }
882 }
883
884 static void
885 gst_ximage_src_clear_bufpool (GstXImageSrc * ximagesrc)
886 {
887   g_mutex_lock (ximagesrc->pool_lock);
888   while (ximagesrc->buffer_pool != NULL) {
889     GstXImageSrcBuffer *ximage = ximagesrc->buffer_pool->data;
890
891     gst_ximage_buffer_free (ximage);
892
893     ximagesrc->buffer_pool = g_slist_delete_link (ximagesrc->buffer_pool,
894         ximagesrc->buffer_pool);
895   }
896   g_mutex_unlock (ximagesrc->pool_lock);
897 }
898
899 static void
900 gst_ximage_src_base_init (gpointer g_class)
901 {
902   GstElementClass *ec = GST_ELEMENT_CLASS (g_class);
903
904   gst_element_class_set_details (ec, &ximagesrc_details);
905   gst_element_class_add_pad_template (ec, gst_static_pad_template_get (&t));
906 }
907
908 static void
909 gst_ximage_src_dispose (GObject * object)
910 {
911   /* Drop references in the buffer_pool */
912   gst_ximage_src_clear_bufpool (GST_XIMAGE_SRC (object));
913
914   G_OBJECT_CLASS (parent_class)->dispose (object);
915 }
916
917 static void
918 gst_ximage_src_finalize (GObject * object)
919 {
920   GstXImageSrc *src = GST_XIMAGE_SRC (object);
921
922   if (src->xcontext)
923     ximageutil_xcontext_clear (src->xcontext);
924
925   g_mutex_free (src->pool_lock);
926   g_mutex_free (src->x_lock);
927
928   G_OBJECT_CLASS (parent_class)->finalize (object);
929 }
930
931 static GstCaps *
932 gst_ximage_src_get_caps (GstBaseSrc * bs)
933 {
934   GstXImageSrc *s = GST_XIMAGE_SRC (bs);
935   GstXContext *xcontext;
936   gint x, y, width, height;
937
938   if ((!s->xcontext) && (!gst_ximage_src_open_display (s, s->display_name)))
939     return
940         gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SRC
941             (s)->srcpad));
942
943   if (!gst_ximage_src_recalc (s))
944     return
945         gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SRC
946             (s)->srcpad));
947
948   xcontext = s->xcontext;
949
950   x = y = 0;
951   width = xcontext->width;
952   height = xcontext->height;
953   if (s->endx > s->startx && s->endy > s->starty) {
954     /* this means user has put in values */
955     if (s->startx < xcontext->width && s->endx < xcontext->width &&
956         s->starty < xcontext->height && s->endy < xcontext->height) {
957       /* values are fine */
958       x = s->startx;
959       y = s->starty;
960       s->width = width = s->endx - s->startx;
961       s->height = height = s->endy - s->starty;
962     } else {
963       GST_WARNING
964           ("User put in co-ordinates overshooting the X resolution, setting to full screen");
965       s->startx = 0;
966       s->starty = 0;
967       s->endx = 0;
968       s->endy = 0;
969     }
970   } else {
971     GST_WARNING ("User put in bogus co-ordinates, setting to full screen");
972     s->startx = 0;
973     s->starty = 0;
974     s->endx = 0;
975     s->endy = 0;
976   }
977   GST_DEBUG ("width = %d, height=%d", width, height);
978   return gst_caps_new_simple ("video/x-raw-rgb",
979       "bpp", G_TYPE_INT, xcontext->bpp,
980       "depth", G_TYPE_INT, xcontext->depth,
981       "endianness", G_TYPE_INT, xcontext->endianness,
982       "red_mask", G_TYPE_INT, xcontext->r_mask_output,
983       "green_mask", G_TYPE_INT, xcontext->g_mask_output,
984       "blue_mask", G_TYPE_INT, xcontext->b_mask_output,
985       "width", G_TYPE_INT, width,
986       "height", G_TYPE_INT, height,
987       "framerate", GST_TYPE_FRACTION_RANGE, 1, G_MAXINT, G_MAXINT, 1,
988       "pixel-aspect-ratio", GST_TYPE_FRACTION_RANGE, 1, G_MAXINT, G_MAXINT, 1,
989       NULL);
990 }
991
992 static gboolean
993 gst_ximage_src_set_caps (GstBaseSrc * bs, GstCaps * caps)
994 {
995   GstXImageSrc *s = GST_XIMAGE_SRC (bs);
996   GstStructure *structure;
997   const GValue *new_fps;
998
999   /* If not yet opened, disallow setcaps until later */
1000   if (!s->xcontext)
1001     return FALSE;
1002
1003   /* The only thing that can change is the framerate downstream wants */
1004   structure = gst_caps_get_structure (caps, 0);
1005   new_fps = gst_structure_get_value (structure, "framerate");
1006   if (!new_fps)
1007     return FALSE;
1008
1009   /* Store this FPS for use when generating buffers */
1010   s->fps_n = gst_value_get_fraction_numerator (new_fps);
1011   s->fps_d = gst_value_get_fraction_denominator (new_fps);
1012
1013   GST_DEBUG_OBJECT (s, "peer wants %d/%d fps", s->fps_n, s->fps_d);
1014
1015   return TRUE;
1016 }
1017
1018 static void
1019 gst_ximage_src_fixate (GstPad * pad, GstCaps * caps)
1020 {
1021   gint i;
1022   GstStructure *structure;
1023
1024   for (i = 0; i < gst_caps_get_size (caps); ++i) {
1025     structure = gst_caps_get_structure (caps, i);
1026
1027     gst_structure_fixate_field_nearest_fraction (structure, "framerate", 25, 1);
1028   }
1029 }
1030
1031 static void
1032 gst_ximage_src_class_init (GstXImageSrcClass * klass)
1033 {
1034   GObjectClass *gc = G_OBJECT_CLASS (klass);
1035   GstBaseSrcClass *bc = GST_BASE_SRC_CLASS (klass);
1036   GstPushSrcClass *push_class = GST_PUSH_SRC_CLASS (klass);
1037
1038   gc->set_property = gst_ximage_src_set_property;
1039   gc->get_property = gst_ximage_src_get_property;
1040   gc->dispose = gst_ximage_src_dispose;
1041   gc->finalize = gst_ximage_src_finalize;
1042
1043   g_object_class_install_property (gc, PROP_DISPLAY_NAME,
1044       g_param_spec_string ("display-name", "Display", "X Display Name", NULL,
1045           G_PARAM_READWRITE));
1046   g_object_class_install_property (gc, PROP_SCREEN_NUM,
1047       g_param_spec_uint ("screen-num", "Screen number", "X Screen Number",
1048           0, G_MAXINT, 0, G_PARAM_READWRITE));
1049   g_object_class_install_property (gc, PROP_SHOW_POINTER,
1050       g_param_spec_boolean ("show-pointer", "Show Mouse Pointer",
1051           "Show mouse pointer (if XFixes extension enabled)", TRUE,
1052           G_PARAM_READWRITE));
1053   /**
1054    * GstXImageSrc:use-damage
1055    *
1056    * Use XDamage (if the XDamage extension is enabled)
1057    *
1058    * Since: 0.10.4
1059    **/
1060   g_object_class_install_property (gc, PROP_USE_DAMAGE,
1061       g_param_spec_boolean ("use-damage", "Use XDamage",
1062           "Use XDamage (if XDamage extension enabled)", TRUE,
1063           G_PARAM_READWRITE));
1064   /**
1065    * GstXImageSrc:startx
1066    *
1067    * X coordinate of top left corner of area to be recorded
1068    * (0 for top left of screen)
1069    *
1070    * Since: 0.10.4
1071    **/
1072   g_object_class_install_property (gc, PROP_STARTX,
1073       g_param_spec_uint ("startx", "Start X co-ordinate",
1074           "X coordinate of top left corner of area to be recorded (0 for top left of screen)",
1075           0, G_MAXINT, 0, G_PARAM_READWRITE));
1076   /**
1077    * GstXImageSrc:starty
1078    *
1079    * Y coordinate of top left corner of area to be recorded
1080    * (0 for top left of screen)
1081    *
1082    * Since: 0.10.4
1083    **/
1084   g_object_class_install_property (gc, PROP_STARTY,
1085       g_param_spec_uint ("starty", "Start Y co-ordinate",
1086           "Y coordinate of top left corner of area to be recorded (0 for top left of screen)",
1087           0, G_MAXINT, 0, G_PARAM_READWRITE));
1088   /**
1089    * GstXImageSrc:endx
1090    *
1091    * X coordinate of bottom right corner of area to be recorded
1092    * (0 for bottom right of screen)
1093    *
1094    * Since: 0.10.4
1095    **/
1096   g_object_class_install_property (gc, PROP_ENDX,
1097       g_param_spec_uint ("endx", "End X",
1098           "X coordinate of bottom right corner of area to be recorded (0 for bottom right of screen)",
1099           0, G_MAXINT, 0, G_PARAM_READWRITE));
1100   /**
1101    * GstXImageSrc:endy
1102    *
1103    * Y coordinate of bottom right corner of area to be recorded
1104    * (0 for bottom right of screen)
1105    *
1106    * Since: 0.10.4
1107    **/
1108   g_object_class_install_property (gc, PROP_ENDY,
1109       g_param_spec_uint ("endy", "End Y",
1110           "Y coordinate of bottom right corner of area to be recorded (0 for bottom right of screen)",
1111           0, G_MAXINT, 0, G_PARAM_READWRITE));
1112
1113   parent_class = g_type_class_peek_parent (klass);
1114
1115   push_class->create = gst_ximage_src_create;
1116   bc->get_caps = gst_ximage_src_get_caps;
1117   bc->set_caps = gst_ximage_src_set_caps;
1118   bc->start = gst_ximage_src_start;
1119   bc->stop = gst_ximage_src_stop;
1120   bc->unlock = gst_ximage_src_unlock;
1121 }
1122
1123 static void
1124 gst_ximage_src_init (GstXImageSrc * ximagesrc, GstXImageSrcClass * klass)
1125 {
1126   gst_base_src_set_live (GST_BASE_SRC (ximagesrc), TRUE);
1127   gst_pad_set_fixatecaps_function (GST_BASE_SRC_PAD (ximagesrc),
1128       gst_ximage_src_fixate);
1129
1130   ximagesrc->pool_lock = g_mutex_new ();
1131   ximagesrc->x_lock = g_mutex_new ();
1132   ximagesrc->show_pointer = TRUE;
1133   ximagesrc->use_damage = TRUE;
1134   ximagesrc->startx = 0;
1135   ximagesrc->starty = 0;
1136   ximagesrc->endx = 0;
1137   ximagesrc->endy = 0;
1138 }
1139
1140 static gboolean
1141 plugin_init (GstPlugin * plugin)
1142 {
1143   gboolean ret;
1144
1145   GST_DEBUG_CATEGORY_INIT (gst_debug_ximage_src, "ximagesrc", 0,
1146       "ximagesrc element debug");
1147
1148   ret = gst_element_register (plugin, "ximagesrc", GST_RANK_NONE,
1149       GST_TYPE_XIMAGE_SRC);
1150
1151   return ret;
1152 }
1153
1154 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1155     GST_VERSION_MINOR,
1156     "ximagesrc",
1157     "X11 video input plugin using standard Xlib calls",
1158     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);