Tizen 2.0 Release
[framework/multimedia/gstreamer-vaapi.git] / gst-libs / gst / vaapi / gstvaapiwindow_x11.c
1 /*
2  *  gstvaapiwindow_x11.c - VA/X11 window abstraction
3  *
4  *  Copyright (C) 2010-2011 Splitted-Desktop Systems
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public License
8  *  as published by the Free Software Foundation; either version 2.1
9  *  of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free
18  *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  *  Boston, MA 02110-1301 USA
20  */
21
22 /**
23  * SECTION:gstvaapiwindow_x11
24  * @short_description: VA/X11 window abstraction
25  */
26
27 #include "sysdeps.h"
28 #include <string.h>
29 #include <X11/Xatom.h>
30 #include "gstvaapicompat.h"
31 #include "gstvaapiwindow_x11.h"
32 #include "gstvaapidisplay_x11.h"
33 #include "gstvaapidisplay_x11_priv.h"
34 #include "gstvaapiutils.h"
35 #include "gstvaapiutils_x11.h"
36 #include "gstvaapi_priv.h"
37
38 #define DEBUG 1
39 #include "gstvaapidebug.h"
40
41 G_DEFINE_TYPE(GstVaapiWindowX11, gst_vaapi_window_x11, GST_VAAPI_TYPE_WINDOW);
42
43 #define GST_VAAPI_WINDOW_X11_GET_PRIVATE(obj)                   \
44     (G_TYPE_INSTANCE_GET_PRIVATE((obj),                         \
45                                  GST_VAAPI_TYPE_WINDOW_X11,     \
46                                  GstVaapiWindowX11Private))
47
48 struct _GstVaapiWindowX11Private {
49     Atom                atom_NET_WM_STATE;
50     Atom                atom_NET_WM_STATE_FULLSCREEN;
51     guint               create_window           : 1;
52     guint               is_mapped               : 1;
53     guint               fullscreen_on_map       : 1;
54     guint               is_pixmap               : 1;
55 };
56 /*
57   * is_pixmap will be used by _create(), but _new_with_xid() doesn't have
58   * a chance to set the flag, so add this flag here.
59   * TODO, we'd better add is_pixmap flag to base class: GstVaapiWindow
60 */
61
62 static gboolean _is_pixmap = FALSE;
63
64 #define _NET_WM_STATE_REMOVE    0 /* remove/unset property */
65 #define _NET_WM_STATE_ADD       1 /* add/set property      */
66 #define _NET_WM_STATE_TOGGLE    2 /* toggle property       */
67
68 static void
69 send_wmspec_change_state(GstVaapiWindowX11 *window, Atom state, gboolean add)
70 {
71     GstVaapiWindowX11Private * const priv = window->priv;
72     Display * const dpy = GST_VAAPI_OBJECT_XDISPLAY(window);
73     XClientMessageEvent xclient;
74
75     memset(&xclient, 0, sizeof(xclient));
76
77     xclient.type         = ClientMessage;
78     xclient.window       = GST_VAAPI_OBJECT_ID(window);
79     xclient.message_type = priv->atom_NET_WM_STATE;
80     xclient.format       = 32;
81
82     xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
83     xclient.data.l[1] = state;
84     xclient.data.l[2] = 0;
85     xclient.data.l[3] = 0;
86     xclient.data.l[4] = 0;
87
88     XSendEvent(
89         dpy,
90         DefaultRootWindow(dpy),
91         False,
92         SubstructureRedirectMask|SubstructureNotifyMask,
93         (XEvent *)&xclient
94     );
95 }
96
97 static void
98 wait_event(GstVaapiWindow *window, int type)
99 {
100     Display * const     dpy = GST_VAAPI_OBJECT_XDISPLAY(window);
101     const Window        xid = GST_VAAPI_OBJECT_ID(window);
102     XEvent              e;
103     Bool                got_event;
104
105     for (;;) {
106         GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
107         got_event = XCheckTypedWindowEvent(dpy, xid, type, &e);
108         GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
109         if (got_event)
110             break;
111         g_usleep(10);
112     }
113 }
114
115 static gboolean
116 timed_wait_event(GstVaapiWindow *window, int type, guint64 end_time, XEvent *e)
117 {
118     Display * const     dpy = GST_VAAPI_OBJECT_XDISPLAY(window);
119     const Window        xid = GST_VAAPI_OBJECT_ID(window);
120     XEvent              tmp_event;
121     GTimeVal            now;
122     guint64             now_time;
123     Bool                got_event;
124
125     if (!e)
126         e = &tmp_event;
127
128     GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
129     got_event = XCheckTypedWindowEvent(dpy, xid, type, e);
130     GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
131     if (got_event)
132         return TRUE;
133
134     do {
135         g_usleep(10);
136         GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
137         got_event = XCheckTypedWindowEvent(dpy, xid, type, e);
138         GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
139         if (got_event)
140             return TRUE;
141         g_get_current_time(&now);
142         now_time = (guint64)now.tv_sec * 1000000 + now.tv_usec;
143     } while (now_time < end_time);
144     return FALSE;
145 }
146
147 static gboolean
148 gst_vaapi_window_x11_show(GstVaapiWindow *window)
149 {
150     GstVaapiWindowX11Private * const priv = GST_VAAPI_WINDOW_X11(window)->priv;
151     Display * const                  dpy  = GST_VAAPI_OBJECT_XDISPLAY(window);
152     const Window                     xid  = GST_VAAPI_OBJECT_ID(window);
153     XWindowAttributes                wattr;
154     gboolean                         has_errors;
155
156     if (priv->is_mapped)
157         return TRUE;
158
159     GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
160     x11_trap_errors();
161     if (!priv->create_window) {
162         XGetWindowAttributes(dpy, xid, &wattr);
163         if (!(wattr.your_event_mask & StructureNotifyMask))
164             XSelectInput(dpy, xid, StructureNotifyMask);
165     }
166     XMapWindow(dpy, xid);
167     has_errors = x11_untrap_errors() != 0;
168     GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
169
170     if (!has_errors) {
171         wait_event(window, MapNotify);
172         if (!priv->create_window &&
173             !(wattr.your_event_mask & StructureNotifyMask)) {
174             GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
175             x11_trap_errors();
176             XSelectInput(dpy, xid, wattr.your_event_mask);
177             has_errors = x11_untrap_errors() != 0;
178             GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
179         }
180         priv->is_mapped = TRUE;
181
182         if (priv->fullscreen_on_map)
183             gst_vaapi_window_set_fullscreen(window, TRUE);
184     }
185     return !has_errors;
186 }
187
188 static gboolean
189 gst_vaapi_window_x11_hide(GstVaapiWindow *window)
190 {
191     GstVaapiWindowX11Private * const priv = GST_VAAPI_WINDOW_X11(window)->priv;
192     Display * const                  dpy  = GST_VAAPI_OBJECT_XDISPLAY(window);
193     const Window                     xid  = GST_VAAPI_OBJECT_ID(window);
194     XWindowAttributes                wattr;
195     gboolean                         has_errors;
196
197     if (!priv->is_mapped)
198         return TRUE;
199
200     GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
201     x11_trap_errors();
202     if (!priv->create_window) {
203         XGetWindowAttributes(dpy, xid, &wattr);
204         if (!(wattr.your_event_mask & StructureNotifyMask))
205             XSelectInput(dpy, xid, StructureNotifyMask);
206     }
207     XUnmapWindow(dpy, xid);
208     has_errors = x11_untrap_errors() != 0;
209     GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
210
211     if (!has_errors) {
212         wait_event(window, UnmapNotify);
213         if (!priv->create_window &&
214             !(wattr.your_event_mask & StructureNotifyMask)) {
215             GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
216             x11_trap_errors();
217             XSelectInput(dpy, xid, wattr.your_event_mask);
218             has_errors = x11_untrap_errors() != 0;
219             GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
220         }
221         priv->is_mapped = FALSE;
222     }
223     return !has_errors;
224 }
225
226 static gboolean
227 gst_vaapi_window_x11_create(GstVaapiWindow *window, guint *width, guint *height)
228 {
229     GstVaapiWindowX11Private * const priv = GST_VAAPI_WINDOW_X11(window)->priv;
230     Display * const                  dpy  = GST_VAAPI_OBJECT_XDISPLAY(window);
231     Window                           xid  = GST_VAAPI_OBJECT_ID(window);
232     Visual                          *vis  = NULL;
233     Colormap                         cmap = None;
234     GstVaapiWindowX11Class          *klass;
235     XWindowAttributes                wattr;
236     Atom                             atoms[2];
237     gboolean                         ok;
238
239     static const char *atom_names[2] = {
240         "_NET_WM_STATE",
241         "_NET_WM_STATE_FULLSCREEN",
242     };
243
244     if (!priv->create_window && xid) {
245         GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
246         if (_is_pixmap) {
247             priv->is_mapped = TRUE;
248         }
249         else {
250             XGetWindowAttributes(dpy, xid, &wattr);
251             priv->is_mapped = wattr.map_state == IsViewable;
252         }
253         ok = x11_get_geometry(dpy, xid, NULL, NULL, width, height);
254         GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
255         return ok;
256     }
257
258     klass = GST_VAAPI_WINDOW_X11_GET_CLASS(window);
259     if (klass) {
260         if (klass->get_visual)
261             vis = klass->get_visual(window);
262         if (klass->get_colormap)
263             cmap = klass->get_colormap(window);
264     }
265
266     GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
267     XInternAtoms(
268         dpy,
269         (char **)atom_names, G_N_ELEMENTS(atom_names),
270         False,
271         atoms
272     );
273     priv->atom_NET_WM_STATE            = atoms[0];
274     priv->atom_NET_WM_STATE_FULLSCREEN = atoms[1];
275
276     xid = x11_create_window(dpy, *width, *height, vis, cmap);
277     if (xid)
278         XRaiseWindow(dpy, xid);
279     GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
280
281     GST_DEBUG("xid %" GST_VAAPI_ID_FORMAT, GST_VAAPI_ID_ARGS(xid));
282     GST_VAAPI_OBJECT_ID(window) = xid;
283     return xid != None;
284 }
285
286 static void
287 gst_vaapi_window_x11_destroy(GstVaapiWindow *window)
288 {
289     GstVaapiWindowX11Private * const priv = GST_VAAPI_WINDOW_X11(window)->priv;
290     Display * const                  dpy  = GST_VAAPI_OBJECT_XDISPLAY(window);
291     const Window                     xid  = GST_VAAPI_OBJECT_ID(window);
292
293     if (xid) {
294         if (priv->create_window) {
295             GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
296             XDestroyWindow(dpy, xid);
297             GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
298         }
299         GST_VAAPI_OBJECT_ID(window) = None;
300     }
301 }
302
303 static gboolean
304 gst_vaapi_window_x11_get_geometry(
305     GstVaapiWindow *window,
306     gint           *px,
307     gint           *py,
308     guint          *pwidth,
309     guint          *pheight)
310 {
311     Display * const     dpy = GST_VAAPI_OBJECT_XDISPLAY(window);
312     const Window        xid = GST_VAAPI_OBJECT_ID(window);
313
314     return x11_get_geometry(dpy, xid, px, py, pwidth, pheight);
315 }
316
317 static gboolean
318 gst_vaapi_window_x11_set_fullscreen(GstVaapiWindow *window, gboolean fullscreen)
319 {
320     GstVaapiWindowX11Private * const priv = GST_VAAPI_WINDOW_X11(window)->priv;
321     Display * const                  dpy  = GST_VAAPI_OBJECT_XDISPLAY(window);
322     const Window                     xid  = GST_VAAPI_OBJECT_ID(window);
323     XEvent e;
324     guint width, height;
325     gboolean has_errors;
326     GTimeVal now;
327     guint64 end_time;
328
329     GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
330     x11_trap_errors();
331     if (fullscreen) {
332         if (!priv->is_mapped) {
333             priv->fullscreen_on_map = TRUE;
334
335             XChangeProperty(
336                 dpy,
337                 xid,
338                 priv->atom_NET_WM_STATE, XA_ATOM, 32,
339                 PropModeReplace,
340                 (unsigned char *)&priv->atom_NET_WM_STATE_FULLSCREEN, 1
341             );
342         }
343         else {
344             send_wmspec_change_state(
345                 GST_VAAPI_WINDOW_X11(window),
346                 priv->atom_NET_WM_STATE_FULLSCREEN,
347                 TRUE
348             );
349         }
350     }
351     else {
352         if (!priv->is_mapped) {
353             priv->fullscreen_on_map = FALSE;
354
355             XDeleteProperty(
356                 dpy,
357                 xid,
358                 priv->atom_NET_WM_STATE
359             );
360         }
361         else {
362             send_wmspec_change_state(
363                 GST_VAAPI_WINDOW_X11(window),
364                 priv->atom_NET_WM_STATE_FULLSCREEN,
365                 FALSE
366             );
367         }
368     }
369     XSync(dpy, False);
370     has_errors = x11_untrap_errors() != 0;
371     GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
372     if (has_errors)
373         return FALSE;
374
375     /* Try to wait for the completion of the fullscreen mode switch */
376     if (priv->create_window && priv->is_mapped) {
377         const guint DELAY = 100000; /* 100 ms */
378         g_get_current_time(&now);
379         end_time = DELAY + ((guint64)now.tv_sec * 1000000 + now.tv_usec);
380         while (timed_wait_event(window, ConfigureNotify, end_time, &e)) {
381             if (fullscreen) {
382                 gst_vaapi_display_get_size(
383                     GST_VAAPI_OBJECT_DISPLAY(window),
384                     &width,
385                     &height
386                 );
387                 if (e.xconfigure.width == width && e.xconfigure.height == height)
388                     return TRUE;
389             }
390             else {
391                 gst_vaapi_window_get_size(window, &width, &height);
392                 if (e.xconfigure.width != width || e.xconfigure.height != height)
393                     return TRUE;
394             }
395         }
396     }
397     return FALSE;
398 }
399
400 static gboolean
401 gst_vaapi_window_x11_resize(GstVaapiWindow *window, guint width, guint height)
402 {
403     gboolean has_errors;
404
405     if (!GST_VAAPI_OBJECT_ID(window))
406         return FALSE;
407
408     GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
409     x11_trap_errors();
410     XResizeWindow(
411         GST_VAAPI_OBJECT_XDISPLAY(window),
412         GST_VAAPI_OBJECT_ID(window),
413         width,
414         height
415     );
416     has_errors = x11_untrap_errors() != 0;
417     GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
418     return !has_errors;
419 }
420
421 static gboolean
422 gst_vaapi_window_x11_render(
423     GstVaapiWindow          *window,
424     GstVaapiSurface         *surface,
425     const GstVaapiRectangle *src_rect,
426     const GstVaapiRectangle *dst_rect,
427     guint                    flags
428 )
429 {
430     VASurfaceID surface_id;
431     VAStatus status;
432
433     surface_id = GST_VAAPI_OBJECT_ID(surface);
434     if (surface_id == VA_INVALID_ID)
435         return FALSE;
436
437     GST_VAAPI_OBJECT_LOCK_DISPLAY(window);
438     status = vaPutSurface(
439         GST_VAAPI_OBJECT_VADISPLAY(window),
440         surface_id,
441         GST_VAAPI_OBJECT_ID(window),
442         src_rect->x,
443         src_rect->y,
444         src_rect->width,
445         src_rect->height,
446         dst_rect->x,
447         dst_rect->y,
448         dst_rect->width,
449         dst_rect->height,
450         NULL, 0,
451         from_GstVaapiSurfaceRenderFlags(flags)
452     );
453     GST_VAAPI_OBJECT_UNLOCK_DISPLAY(window);
454     if (!vaapi_check_status(status, "vaPutSurface()"))
455         return FALSE;
456
457     return TRUE;
458 }
459
460 static void
461 gst_vaapi_window_x11_finalize(GObject *object)
462 {
463     G_OBJECT_CLASS(gst_vaapi_window_x11_parent_class)->finalize(object);
464 }
465
466 static void
467 gst_vaapi_window_x11_constructed(GObject *object)
468 {
469     GstVaapiWindowX11 * const window = GST_VAAPI_WINDOW_X11(object);
470     GObjectClass *parent_class;
471
472     window->priv->create_window = GST_VAAPI_OBJECT_ID(object) == None;
473
474     parent_class = G_OBJECT_CLASS(gst_vaapi_window_x11_parent_class);
475     if (parent_class->constructed)
476         parent_class->constructed(object);
477 }
478
479 static void
480 gst_vaapi_window_x11_class_init(GstVaapiWindowX11Class *klass)
481 {
482     GObjectClass * const object_class = G_OBJECT_CLASS(klass);
483     GstVaapiWindowClass * const window_class = GST_VAAPI_WINDOW_CLASS(klass);
484
485     g_type_class_add_private(klass, sizeof(GstVaapiWindowX11Private));
486
487     object_class->finalize       = gst_vaapi_window_x11_finalize;
488     object_class->constructed    = gst_vaapi_window_x11_constructed;
489
490     window_class->create         = gst_vaapi_window_x11_create;
491     window_class->destroy        = gst_vaapi_window_x11_destroy;
492     window_class->show           = gst_vaapi_window_x11_show;
493     window_class->hide           = gst_vaapi_window_x11_hide;
494     window_class->get_geometry   = gst_vaapi_window_x11_get_geometry;
495     window_class->set_fullscreen = gst_vaapi_window_x11_set_fullscreen;
496     window_class->resize         = gst_vaapi_window_x11_resize;
497     window_class->render         = gst_vaapi_window_x11_render;
498 }
499
500 static void
501 gst_vaapi_window_x11_init(GstVaapiWindowX11 *window)
502 {
503     GstVaapiWindowX11Private *priv = GST_VAAPI_WINDOW_X11_GET_PRIVATE(window);
504
505     window->priv                = priv;
506     priv->create_window         = TRUE;
507     priv->is_mapped             = FALSE;
508     priv->fullscreen_on_map     = FALSE;
509     priv->is_pixmap             = FALSE;
510 }
511
512 /**
513  * gst_vaapi_window_x11_new:
514  * @display: a #GstVaapiDisplay
515  * @width: the requested window width, in pixels
516  * @height: the requested windo height, in pixels
517  *
518  * Creates a window with the specified @width and @height. The window
519  * will be attached to the @display and remains invisible to the user
520  * until gst_vaapi_window_show() is called.
521  *
522  * Return value: the newly allocated #GstVaapiWindow object
523  */
524 GstVaapiWindow *
525 gst_vaapi_window_x11_new(GstVaapiDisplay *display, guint width, guint height)
526 {
527     GST_DEBUG("new window, size %ux%u", width, height);
528
529     g_return_val_if_fail(GST_VAAPI_IS_DISPLAY(display), NULL);
530     g_return_val_if_fail(width  > 0, NULL);
531     g_return_val_if_fail(height > 0, NULL);
532
533     return g_object_new(GST_VAAPI_TYPE_WINDOW_X11,
534                         "display", display,
535                         "id",      GST_VAAPI_ID(None),
536                         "width",   width,
537                         "height",  height,
538                         NULL);
539 }
540
541 /**
542  * gst_vaapi_window_x11_new_with_xid:
543  * @display: a #GstVaapiDisplay
544  * @xid: an X11 #Window id
545  *
546  * Creates a #GstVaapiWindow using the X11 #Window @xid. The caller
547  * still owns the window and must call XDestroyWindow() when all
548  * #GstVaapiWindow references are released. Doing so too early can
549  * yield undefined behaviour.
550  *
551  * Return value: the newly allocated #GstVaapiWindow object
552  */
553 GstVaapiWindow *
554 gst_vaapi_window_x11_new_with_xid(GstVaapiDisplay *display, Window xid, gboolean is_pixmap)
555 {
556     GstVaapiWindow * window = NULL;
557     GST_DEBUG("new window from xid 0x%08x", xid);
558
559     g_return_val_if_fail(GST_VAAPI_IS_DISPLAY(display), NULL);
560     g_return_val_if_fail(xid != None, NULL);
561
562     _is_pixmap = is_pixmap;
563     window = g_object_new(GST_VAAPI_TYPE_WINDOW_X11,
564                         "display", display,
565                         "id",      GST_VAAPI_ID(xid),
566                         NULL);
567
568     if (window) {
569         GstVaapiWindowX11Private *priv = GST_VAAPI_WINDOW_X11_GET_PRIVATE(window);
570         priv->is_pixmap = is_pixmap;
571     }
572
573     return window;
574 }
575
576 /**
577  * gst_vaapi_window_x11_get_xid:
578  * @window: a #GstVaapiWindowX11
579  *
580  * Returns the underlying X11 #Window that was created by
581  * gst_vaapi_window_x11_new() or that was bound with
582  * gst_vaapi_window_x11_new_with_xid().
583  *
584  * Return value: the underlying X11 #Window bound to @window.
585  */
586 Window
587 gst_vaapi_window_x11_get_xid(GstVaapiWindowX11 *window)
588 {
589     g_return_val_if_fail(GST_VAAPI_IS_WINDOW_X11(window), None);
590
591     return GST_VAAPI_OBJECT_ID(window);
592 }
593
594 /**
595  * gst_vaapi_window_x11_is_foreign_xid:
596  * @window: a #GstVaapiWindowX11
597  *
598  * Checks whether the @window XID was created by gst_vaapi_window_x11_new() or bound with gst_vaapi_window_x11_new_with_xid().
599  *
600  * Return value: %TRUE if the underlying X window is owned by the
601  *   caller (foreign window)
602  */
603 gboolean
604 gst_vaapi_window_x11_is_foreign_xid(GstVaapiWindowX11 *window)
605 {
606     g_return_val_if_fail(GST_VAAPI_IS_WINDOW_X11(window), FALSE);
607
608     return !window->priv->create_window;
609 }
610
611 /**
612  * gst_vaapi_window_x11_is_pixmap:
613  * @window: a #GstVaapiWindowX11
614  *
615  * Checks whether the @window XID is Pixmap or Window
616  *
617  * Return value: %TRUE if the underlying X Drawble is Pixmap
618  */
619 gboolean
620 gst_vaapi_window_x11_is_pixmap(GstVaapiWindowX11 *window)
621 {
622     g_return_val_if_fail(GST_VAAPI_IS_WINDOW_X11(window), FALSE);
623
624     return window->priv->is_pixmap;
625 }