upload tizen1.0 source
[framework/multimedia/gst-plugins-good0.10.git] / sys / v4l2 / gstv4l2xoverlay.c
1 /* GStreamer
2  *
3  * Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
4  *               2006 Edgard Lima <edgard.lima@indt.org.br>
5  *
6  * gstv4l2xoverlay.c: X-based overlay interface implementation for V4L2
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <string.h>
29 #include <gst/gst.h>
30 #include <X11/X.h>
31 #include <X11/Xlib.h>
32 #include <X11/extensions/Xv.h>
33 #include <X11/extensions/Xvlib.h>
34 #include <sys/stat.h>
35
36 #include <gst/interfaces/navigation.h>
37
38 #include "gstv4l2xoverlay.h"
39 #include "gstv4l2object.h"
40 #include "v4l2_calls.h"
41
42 #include "gst/gst-i18n-plugin.h"
43
44 struct _GstV4l2Xv
45 {
46   Display *dpy;
47   gint port, idle_id, event_id;
48   GMutex *mutex;                /* to serialize calls to X11 */
49 };
50
51 GST_DEBUG_CATEGORY_STATIC (v4l2xv_debug);
52 #define GST_CAT_DEFAULT v4l2xv_debug
53
54 void
55 gst_v4l2_xoverlay_interface_init (GstXOverlayClass * klass)
56 {
57   GST_DEBUG_CATEGORY_INIT (v4l2xv_debug, "v4l2xv", 0,
58       "V4L2 XOverlay interface debugging");
59 }
60
61 static void
62 gst_v4l2_xoverlay_open (GstV4l2Object * v4l2object)
63 {
64   struct stat s;
65   GstV4l2Xv *v4l2xv;
66   const gchar *name = g_getenv ("DISPLAY");
67   unsigned int ver, rel, req, ev, err, anum;
68   int i, id = 0, first_id = 0, min;
69   XvAdaptorInfo *ai;
70   Display *dpy;
71
72   /* we need a display, obviously */
73   if (!name || !(dpy = XOpenDisplay (name))) {
74     GST_WARNING_OBJECT (v4l2object->element,
75         "No $DISPLAY set or failed to open - no overlay");
76     return;
77   }
78
79   /* First let's check that XVideo extension is available */
80   if (!XQueryExtension (dpy, "XVideo", &i, &i, &i)) {
81     GST_WARNING_OBJECT (v4l2object->element,
82         "Xv extension not available - no overlay");
83     XCloseDisplay (dpy);
84     return;
85   }
86
87   /* find port that belongs to this device */
88   if (XvQueryExtension (dpy, &ver, &rel, &req, &ev, &err) != Success) {
89     GST_WARNING_OBJECT (v4l2object->element,
90         "Xv extension not supported - no overlay");
91     XCloseDisplay (dpy);
92     return;
93   }
94   if (XvQueryAdaptors (dpy, DefaultRootWindow (dpy), &anum, &ai) != Success) {
95     GST_WARNING_OBJECT (v4l2object->element, "Failed to query Xv adaptors");
96     XCloseDisplay (dpy);
97     return;
98   }
99   if (fstat (v4l2object->video_fd, &s) < 0) {
100     GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, NOT_FOUND,
101         (_("Cannot identify device '%s'."), v4l2object->videodev),
102         GST_ERROR_SYSTEM);
103     XCloseDisplay (dpy);
104     return;
105   }
106   min = s.st_rdev & 0xff;
107   for (i = 0; i < anum; i++) {
108     GST_DEBUG_OBJECT (v4l2object->element, "found adapter: %s", ai[i].name);
109     if (!strcmp (ai[i].name, "video4linux2") ||
110         !strcmp (ai[i].name, "video4linux")) {
111       if (first_id == 0)
112         first_id = ai[i].base_id;
113
114       GST_DEBUG_OBJECT (v4l2object->element,
115           "first_id=%d, base_id=%lu, min=%d", first_id, ai[i].base_id, min);
116
117       /* hmm... */
118       if (first_id != 0 && ai[i].base_id == first_id + min)
119         id = ai[i].base_id;
120     }
121   }
122   XvFreeAdaptorInfo (ai);
123
124   if (id == 0) {
125     GST_WARNING_OBJECT (v4l2object->element,
126         "Did not find XvPortID for device - no overlay");
127     XCloseDisplay (dpy);
128     return;
129   }
130
131   v4l2xv = g_new0 (GstV4l2Xv, 1);
132   v4l2xv->dpy = dpy;
133   v4l2xv->port = id;
134   v4l2xv->mutex = g_mutex_new ();
135   v4l2xv->idle_id = 0;
136   v4l2xv->event_id = 0;
137   v4l2object->xv = v4l2xv;
138
139   if (v4l2object->xwindow_id) {
140     gst_v4l2_xoverlay_set_window_handle (v4l2object, v4l2object->xwindow_id);
141   }
142 }
143
144 static void
145 gst_v4l2_xoverlay_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_xoverlay_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_xoverlay_start (GstV4l2Object * v4l2object)
168 {
169   if (v4l2object->xwindow_id) {
170     gst_v4l2_xoverlay_open (v4l2object);
171   }
172 }
173
174 void
175 gst_v4l2_xoverlay_stop (GstV4l2Object * v4l2object)
176 {
177   gst_v4l2_xoverlay_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_xoverlay_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_xoverlay_set_window_handle (GstV4l2Object * v4l2object, guintptr id)
367 {
368   GstV4l2Xv *v4l2xv;
369   XID xwindow_id = id;
370   gboolean change = (v4l2object->xwindow_id != xwindow_id);
371
372   GST_LOG_OBJECT (v4l2object->element, "Setting XID to %lx",
373       (gulong) xwindow_id);
374
375   if (!v4l2object->xv && GST_V4L2_IS_OPEN (v4l2object))
376     gst_v4l2_xoverlay_open (v4l2object);
377
378   v4l2xv = v4l2object->xv;
379
380   if (v4l2xv)
381     g_mutex_lock (v4l2xv->mutex);
382
383   if (change) {
384     if (v4l2object->xwindow_id && v4l2xv) {
385       GST_DEBUG_OBJECT (v4l2object->element,
386           "Deactivating old port %lx", v4l2object->xwindow_id);
387
388       XvSelectPortNotify (v4l2xv->dpy, v4l2xv->port, 0);
389       XvSelectVideoNotify (v4l2xv->dpy, v4l2object->xwindow_id, 0);
390       XvStopVideo (v4l2xv->dpy, v4l2xv->port, v4l2object->xwindow_id);
391     }
392
393     v4l2object->xwindow_id = xwindow_id;
394   }
395
396   if (!v4l2xv || xwindow_id == 0) {
397     if (v4l2xv)
398       g_mutex_unlock (v4l2xv->mutex);
399     return;
400   }
401
402   if (change) {
403     GST_DEBUG_OBJECT (v4l2object->element, "Activating new port %lx",
404         xwindow_id);
405
406     /* draw */
407     XvSelectPortNotify (v4l2xv->dpy, v4l2xv->port, 1);
408     XvSelectVideoNotify (v4l2xv->dpy, v4l2object->xwindow_id, 1);
409   }
410
411   update_geometry (v4l2object);
412
413   if (v4l2xv->idle_id)
414     g_source_remove (v4l2xv->idle_id);
415   v4l2xv->idle_id = g_idle_add (idle_refresh, v4l2object);
416   g_mutex_unlock (v4l2xv->mutex);
417 }
418
419 /**
420  * gst_v4l2_xoverlay_prepare_xwindow_id:
421  * @v4l2object: the v4l2object
422  * @required: %TRUE if display is required (ie. TRUE for v4l2sink, but
423  *   FALSE for any other element with optional overlay capabilities)
424  *
425  * Helper function to create a windo if none is set from the application.
426  */
427 void
428 gst_v4l2_xoverlay_prepare_xwindow_id (GstV4l2Object * v4l2object,
429     gboolean required)
430 {
431   if (!GST_V4L2_IS_OVERLAY (v4l2object))
432     return;
433
434   gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (v4l2object->element));
435
436   if (required && !v4l2object->xwindow_id) {
437     GstV4l2Xv *v4l2xv;
438     Window win;
439     int width, height;
440     long event_mask;
441
442     if (!v4l2object->xv && GST_V4L2_IS_OPEN (v4l2object))
443       gst_v4l2_xoverlay_open (v4l2object);
444
445     v4l2xv = v4l2object->xv;
446
447     /* if xoverlay is not supported, just bail */
448     if (!v4l2xv)
449       return;
450
451     /* xoverlay is supported, but we don't have a window.. so create one */
452     GST_DEBUG_OBJECT (v4l2object->element, "creating window");
453
454     g_mutex_lock (v4l2xv->mutex);
455
456     width = XDisplayWidth (v4l2xv->dpy, DefaultScreen (v4l2xv->dpy));
457     height = XDisplayHeight (v4l2xv->dpy, DefaultScreen (v4l2xv->dpy));
458     GST_DEBUG_OBJECT (v4l2object->element, "dpy=%p", v4l2xv->dpy);
459
460     win = XCreateSimpleWindow (v4l2xv->dpy,
461         DefaultRootWindow (v4l2xv->dpy),
462         0, 0, width, height, 0, 0,
463         XBlackPixel (v4l2xv->dpy, DefaultScreen (v4l2xv->dpy)));
464
465     GST_DEBUG_OBJECT (v4l2object->element, "win=%lu", win);
466
467     event_mask = ExposureMask | StructureNotifyMask;
468     if (GST_IS_NAVIGATION (v4l2object->element)) {
469       event_mask |= PointerMotionMask |
470           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask;
471     }
472     XSelectInput (v4l2xv->dpy, win, event_mask);
473     v4l2xv->event_id = g_timeout_add (45, event_refresh, v4l2object);
474
475     XMapRaised (v4l2xv->dpy, win);
476
477     XSync (v4l2xv->dpy, FALSE);
478
479     g_mutex_unlock (v4l2xv->mutex);
480
481     GST_DEBUG_OBJECT (v4l2object->element, "got window");
482
483     gst_v4l2_xoverlay_set_window_handle (v4l2object, win);
484   }
485 }