Merge branch 'master' into 0.11
[platform/upstream/gst-plugins-good.git] / sys / v4l2 / gstv4l2videooverlay.c
1 /* GStreamer
2  * Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
3  *               2006 Edgard Lima <edgard.lima@indt.org.br>
4  *
5  * gstv4l2video_overlay.c: X-based overlay interface implementation for V4L2
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <string.h>
28 #include <gst/gst.h>
29 #include <X11/X.h>
30 #include <X11/Xlib.h>
31 #include <X11/extensions/Xv.h>
32 #include <X11/extensions/Xvlib.h>
33 #include <sys/stat.h>
34
35 #include <gst/interfaces/navigation.h>
36
37 #include "gstv4l2videooverlay.h"
38 #include "gstv4l2object.h"
39 #include "v4l2_calls.h"
40
41 #include "gst/gst-i18n-plugin.h"
42
43 struct _GstV4l2Xv
44 {
45   Display *dpy;
46   gint port, idle_id, event_id;
47   GMutex *mutex;                /* to serialize calls to X11 */
48 };
49
50 GST_DEBUG_CATEGORY_STATIC (v4l2xv_debug);
51 #define GST_CAT_DEFAULT v4l2xv_debug
52
53 void
54 gst_v4l2_video_overlay_interface_init (GstVideoOverlayIface * klass)
55 {
56   GST_DEBUG_CATEGORY_INIT (v4l2xv_debug, "v4l2xv", 0,
57       "V4L2 GstVideoOverlay interface debugging");
58 }
59
60 static void
61 gst_v4l2_video_overlay_open (GstV4l2Object * v4l2object)
62 {
63   struct stat s;
64   GstV4l2Xv *v4l2xv;
65   const gchar *name = g_getenv ("DISPLAY");
66   unsigned int ver, rel, req, ev, err, anum;
67   int i, id = 0, first_id = 0, min;
68   XvAdaptorInfo *ai;
69   Display *dpy;
70
71   /* we need a display, obviously */
72   if (!name || !(dpy = XOpenDisplay (name))) {
73     GST_WARNING_OBJECT (v4l2object->element,
74         "No $DISPLAY set or failed to open - no overlay");
75     return;
76   }
77
78   /* First let's check that XVideo extension is available */
79   if (!XQueryExtension (dpy, "XVideo", &i, &i, &i)) {
80     GST_WARNING_OBJECT (v4l2object->element,
81         "Xv extension not available - no overlay");
82     XCloseDisplay (dpy);
83     return;
84   }
85
86   /* find port that belongs to this device */
87   if (XvQueryExtension (dpy, &ver, &rel, &req, &ev, &err) != Success) {
88     GST_WARNING_OBJECT (v4l2object->element,
89         "Xv extension not supported - no overlay");
90     XCloseDisplay (dpy);
91     return;
92   }
93   if (XvQueryAdaptors (dpy, DefaultRootWindow (dpy), &anum, &ai) != Success) {
94     GST_WARNING_OBJECT (v4l2object->element, "Failed to query Xv adaptors");
95     XCloseDisplay (dpy);
96     return;
97   }
98   if (fstat (v4l2object->video_fd, &s) < 0) {
99     GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, NOT_FOUND,
100         (_("Cannot identify device '%s'."), v4l2object->videodev),
101         GST_ERROR_SYSTEM);
102     XCloseDisplay (dpy);
103     return;
104   }
105   min = s.st_rdev & 0xff;
106   for (i = 0; i < anum; i++) {
107     GST_DEBUG_OBJECT (v4l2object->element, "found adapter: %s", ai[i].name);
108     if (!strcmp (ai[i].name, "video4linux2") ||
109         !strcmp (ai[i].name, "video4linux")) {
110       if (first_id == 0)
111         first_id = ai[i].base_id;
112
113       GST_DEBUG_OBJECT (v4l2object->element,
114           "first_id=%d, base_id=%lu, min=%d", first_id, ai[i].base_id, min);
115
116       /* hmm... */
117       if (first_id != 0 && ai[i].base_id == first_id + min)
118         id = ai[i].base_id;
119     }
120   }
121   XvFreeAdaptorInfo (ai);
122
123   if (id == 0) {
124     GST_WARNING_OBJECT (v4l2object->element,
125         "Did not find XvPortID for device - no overlay");
126     XCloseDisplay (dpy);
127     return;
128   }
129
130   v4l2xv = g_new0 (GstV4l2Xv, 1);
131   v4l2xv->dpy = dpy;
132   v4l2xv->port = id;
133   v4l2xv->mutex = g_mutex_new ();
134   v4l2xv->idle_id = 0;
135   v4l2xv->event_id = 0;
136   v4l2object->xv = v4l2xv;
137
138   if (v4l2object->xwindow_id) {
139     gst_v4l2_video_overlay_set_window_handle (v4l2object,
140         v4l2object->xwindow_id);
141   }
142 }
143
144 static void
145 gst_v4l2_video_overlay_close (GstV4l2Object * v4l2object)
146 {
147   GstV4l2Xv *v4l2xv = v4l2object->xv;
148
149   if (!v4l2object->xv)
150     return;
151
152   if (v4l2object->xwindow_id) {
153     gst_v4l2_video_overlay_set_window_handle (v4l2object, 0);
154   }
155
156   XCloseDisplay (v4l2xv->dpy);
157   g_mutex_free (v4l2xv->mutex);
158   if (v4l2xv->idle_id)
159     g_source_remove (v4l2xv->idle_id);
160   if (v4l2xv->event_id)
161     g_source_remove (v4l2xv->event_id);
162   g_free (v4l2xv);
163   v4l2object->xv = NULL;
164 }
165
166 void
167 gst_v4l2_video_overlay_start (GstV4l2Object * v4l2object)
168 {
169   if (v4l2object->xwindow_id) {
170     gst_v4l2_video_overlay_open (v4l2object);
171   }
172 }
173
174 void
175 gst_v4l2_video_overlay_stop (GstV4l2Object * v4l2object)
176 {
177   gst_v4l2_video_overlay_close (v4l2object);
178 }
179
180 /* should be called with mutex held */
181 static gboolean
182 get_render_rect (GstV4l2Object * v4l2object, GstVideoRectangle * rect)
183 {
184   GstV4l2Xv *v4l2xv = v4l2object->xv;
185   if (v4l2xv && v4l2xv->dpy && v4l2object->xwindow_id) {
186     XWindowAttributes attr;
187     XGetWindowAttributes (v4l2xv->dpy, v4l2object->xwindow_id, &attr);
188     /* this is where we'd add support to maintain aspect ratio */
189     rect->x = 0;
190     rect->y = 0;
191     rect->w = attr.width;
192     rect->h = attr.height;
193     return TRUE;
194   } else {
195     return FALSE;
196   }
197 }
198
199 gboolean
200 gst_v4l2_video_overlay_get_render_rect (GstV4l2Object * v4l2object,
201     GstVideoRectangle * rect)
202 {
203   GstV4l2Xv *v4l2xv = v4l2object->xv;
204   gboolean ret = FALSE;
205   if (v4l2xv) {
206     g_mutex_lock (v4l2xv->mutex);
207     ret = get_render_rect (v4l2object, rect);
208     g_mutex_unlock (v4l2xv->mutex);
209   }
210   return ret;
211 }
212
213 static void
214 update_geometry (GstV4l2Object * v4l2object)
215 {
216   GstV4l2Xv *v4l2xv = v4l2object->xv;
217   GstVideoRectangle rect;
218   if (!get_render_rect (v4l2object, &rect))
219     return;
220   /* note: we don't pass in valid video x/y/w/h.. currently the xserver
221    * doesn't need to know these, as they come from v4l2 by setting the
222    * crop..
223    */
224   XvPutVideo (v4l2xv->dpy, v4l2xv->port, v4l2object->xwindow_id,
225       DefaultGC (v4l2xv->dpy, DefaultScreen (v4l2xv->dpy)),
226       0, 0, rect.w, rect.h, rect.x, rect.y, rect.w, rect.h);
227 }
228
229 static gboolean
230 idle_refresh (gpointer data)
231 {
232   GstV4l2Object *v4l2object = GST_V4L2_OBJECT (data);
233   GstV4l2Xv *v4l2xv = v4l2object->xv;
234
235   GST_LOG_OBJECT (v4l2object->element, "idle refresh");
236
237   if (v4l2xv) {
238     g_mutex_lock (v4l2xv->mutex);
239
240     update_geometry (v4l2object);
241
242     v4l2xv->idle_id = 0;
243     g_mutex_unlock (v4l2xv->mutex);
244   }
245
246   /* once */
247   return FALSE;
248 }
249
250
251 static gboolean
252 event_refresh (gpointer data)
253 {
254   GstV4l2Object *v4l2object = GST_V4L2_OBJECT (data);
255   GstV4l2Xv *v4l2xv = v4l2object->xv;
256
257   GST_LOG_OBJECT (v4l2object->element, "event refresh");
258
259   if (v4l2xv) {
260     XEvent e;
261
262     g_mutex_lock (v4l2xv->mutex);
263
264     /* If the element supports navigation, collect the relavent input
265      * events and push them upstream as navigation events
266      */
267     if (GST_IS_NAVIGATION (v4l2object->element)) {
268       guint pointer_x = 0, pointer_y = 0;
269       gboolean pointer_moved = FALSE;
270
271       /* We get all pointer motion events, only the last position is
272        * interesting.
273        */
274       while (XCheckWindowEvent (v4l2xv->dpy, v4l2object->xwindow_id,
275               PointerMotionMask, &e)) {
276         switch (e.type) {
277           case MotionNotify:
278             pointer_x = e.xmotion.x;
279             pointer_y = e.xmotion.y;
280             pointer_moved = TRUE;
281             break;
282           default:
283             break;
284         }
285       }
286       if (pointer_moved) {
287         GST_DEBUG_OBJECT (v4l2object->element,
288             "pointer moved over window at %d,%d", pointer_x, pointer_y);
289         g_mutex_unlock (v4l2xv->mutex);
290         gst_navigation_send_mouse_event (GST_NAVIGATION (v4l2object->element),
291             "mouse-move", 0, e.xbutton.x, e.xbutton.y);
292         g_mutex_lock (v4l2xv->mutex);
293       }
294
295       /* We get all events on our window to throw them upstream
296        */
297       while (XCheckWindowEvent (v4l2xv->dpy, v4l2object->xwindow_id,
298               KeyPressMask | KeyReleaseMask |
299               ButtonPressMask | ButtonReleaseMask, &e)) {
300         KeySym keysym;
301         const char *key_str = NULL;
302
303         g_mutex_unlock (v4l2xv->mutex);
304
305         switch (e.type) {
306           case ButtonPress:
307             GST_DEBUG_OBJECT (v4l2object->element,
308                 "button %d pressed over window at %d,%d",
309                 e.xbutton.button, e.xbutton.x, e.xbutton.y);
310             gst_navigation_send_mouse_event (GST_NAVIGATION
311                 (v4l2object->element), "mouse-button-press", e.xbutton.button,
312                 e.xbutton.x, e.xbutton.y);
313             break;
314           case ButtonRelease:
315             GST_DEBUG_OBJECT (v4l2object->element,
316                 "button %d released over window at %d,%d",
317                 e.xbutton.button, e.xbutton.x, e.xbutton.y);
318             gst_navigation_send_mouse_event (GST_NAVIGATION
319                 (v4l2object->element), "mouse-button-release", e.xbutton.button,
320                 e.xbutton.x, e.xbutton.y);
321             break;
322           case KeyPress:
323           case KeyRelease:
324             g_mutex_lock (v4l2xv->mutex);
325             keysym = XKeycodeToKeysym (v4l2xv->dpy, e.xkey.keycode, 0);
326             if (keysym != NoSymbol) {
327               key_str = XKeysymToString (keysym);
328             } else {
329               key_str = "unknown";
330             }
331             g_mutex_unlock (v4l2xv->mutex);
332             GST_DEBUG_OBJECT (v4l2object->element,
333                 "key %d pressed over window at %d,%d (%s)",
334                 e.xkey.keycode, e.xkey.x, e.xkey.y, key_str);
335             gst_navigation_send_key_event (GST_NAVIGATION (v4l2object->element),
336                 e.type == KeyPress ? "key-press" : "key-release", key_str);
337             break;
338           default:
339             GST_DEBUG_OBJECT (v4l2object->element,
340                 "unhandled X event (%d)", e.type);
341         }
342
343         g_mutex_lock (v4l2xv->mutex);
344       }
345     }
346
347     /* Handle ConfigureNotify */
348     while (XCheckWindowEvent (v4l2xv->dpy, v4l2object->xwindow_id,
349             StructureNotifyMask, &e)) {
350       switch (e.type) {
351         case ConfigureNotify:
352           update_geometry (v4l2object);
353           break;
354         default:
355           break;
356       }
357     }
358     g_mutex_unlock (v4l2xv->mutex);
359   }
360
361   /* repeat */
362   return TRUE;
363 }
364
365 void
366 gst_v4l2_video_overlay_set_window_handle (GstV4l2Object * v4l2object,
367     guintptr id)
368 {
369   GstV4l2Xv *v4l2xv;
370   XID xwindow_id = id;
371   gboolean change = (v4l2object->xwindow_id != xwindow_id);
372
373   GST_LOG_OBJECT (v4l2object->element, "Setting XID to %lx",
374       (gulong) xwindow_id);
375
376   if (!v4l2object->xv && GST_V4L2_IS_OPEN (v4l2object))
377     gst_v4l2_video_overlay_open (v4l2object);
378
379   v4l2xv = v4l2object->xv;
380
381   if (v4l2xv)
382     g_mutex_lock (v4l2xv->mutex);
383
384   if (change) {
385     if (v4l2object->xwindow_id && v4l2xv) {
386       GST_DEBUG_OBJECT (v4l2object->element,
387           "Deactivating old port %lx", v4l2object->xwindow_id);
388
389       XvSelectPortNotify (v4l2xv->dpy, v4l2xv->port, 0);
390       XvSelectVideoNotify (v4l2xv->dpy, v4l2object->xwindow_id, 0);
391       XvStopVideo (v4l2xv->dpy, v4l2xv->port, v4l2object->xwindow_id);
392     }
393
394     v4l2object->xwindow_id = xwindow_id;
395   }
396
397   if (!v4l2xv || xwindow_id == 0) {
398     if (v4l2xv)
399       g_mutex_unlock (v4l2xv->mutex);
400     return;
401   }
402
403   if (change) {
404     GST_DEBUG_OBJECT (v4l2object->element, "Activating new port %lx",
405         xwindow_id);
406
407     /* draw */
408     XvSelectPortNotify (v4l2xv->dpy, v4l2xv->port, 1);
409     XvSelectVideoNotify (v4l2xv->dpy, v4l2object->xwindow_id, 1);
410   }
411
412   update_geometry (v4l2object);
413
414   if (v4l2xv->idle_id)
415     g_source_remove (v4l2xv->idle_id);
416   v4l2xv->idle_id = g_idle_add (idle_refresh, v4l2object);
417   g_mutex_unlock (v4l2xv->mutex);
418 }
419
420 /**
421  * gst_v4l2_video_overlay_prepare_window_handle:
422  * @v4l2object: the v4l2object
423  * @required: %TRUE if display is required (ie. TRUE for v4l2sink, but
424  *   FALSE for any other element with optional overlay capabilities)
425  *
426  * Helper function to create a windo if none is set from the application.
427  */
428 void
429 gst_v4l2_video_overlay_prepare_window_handle (GstV4l2Object * v4l2object,
430     gboolean required)
431 {
432   GstVideoOverlay *overlay;
433
434   if (!GST_V4L2_IS_OVERLAY (v4l2object))
435     return;
436
437   overlay = GST_VIDEO_OVERLAY (v4l2object->element);
438   gst_video_overlay_prepare_window_handle (overlay);
439
440   if (required && !v4l2object->xwindow_id) {
441     GstV4l2Xv *v4l2xv;
442     Window win;
443     int width, height;
444     long event_mask;
445
446     if (!v4l2object->xv && GST_V4L2_IS_OPEN (v4l2object))
447       gst_v4l2_video_overlay_open (v4l2object);
448
449     v4l2xv = v4l2object->xv;
450
451     /* if video_overlay is not supported, just bail */
452     if (!v4l2xv)
453       return;
454
455     /* video_overlay is supported, but we don't have a window.. so create one */
456     GST_DEBUG_OBJECT (v4l2object->element, "creating window");
457
458     g_mutex_lock (v4l2xv->mutex);
459
460     width = XDisplayWidth (v4l2xv->dpy, DefaultScreen (v4l2xv->dpy));
461     height = XDisplayHeight (v4l2xv->dpy, DefaultScreen (v4l2xv->dpy));
462     GST_DEBUG_OBJECT (v4l2object->element, "dpy=%p", v4l2xv->dpy);
463
464     win = XCreateSimpleWindow (v4l2xv->dpy,
465         DefaultRootWindow (v4l2xv->dpy),
466         0, 0, width, height, 0, 0,
467         XBlackPixel (v4l2xv->dpy, DefaultScreen (v4l2xv->dpy)));
468
469     GST_DEBUG_OBJECT (v4l2object->element, "win=%lu", win);
470
471     event_mask = ExposureMask | StructureNotifyMask;
472     if (GST_IS_NAVIGATION (v4l2object->element)) {
473       event_mask |= PointerMotionMask |
474           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask;
475     }
476     XSelectInput (v4l2xv->dpy, win, event_mask);
477     v4l2xv->event_id = g_timeout_add (45, event_refresh, v4l2object);
478
479     XMapRaised (v4l2xv->dpy, win);
480
481     XSync (v4l2xv->dpy, FALSE);
482
483     g_mutex_unlock (v4l2xv->mutex);
484
485     GST_DEBUG_OBJECT (v4l2object->element, "got window");
486
487     gst_v4l2_video_overlay_set_window_handle (v4l2object, win);
488   }
489 }