2011-07-02 09:57:30 +01:00
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas.
|
|
|
|
* Copyright 2009-2010 Chia-I Wu <olvaffe@gmail.com>
|
|
|
|
* Copyright 2010-2011 LunarG, Inc.
|
|
|
|
* All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
* copy of this software and associated documentation files (the
|
|
|
|
* "Software"), to deal in the Software without restriction, including
|
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
* distribute, sub license, and/or sell copies of the Software, and to
|
|
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
* the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice (including the
|
|
|
|
* next paragraph) shall be included in all copies or substantial portions
|
|
|
|
* of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
|
* DEALINGS IN THE SOFTWARE.
|
|
|
|
*
|
|
|
|
**************************************************************************/
|
|
|
|
|
|
|
|
|
2008-05-27 23:48:23 +01:00
|
|
|
/**
|
|
|
|
* Functions related to EGLDisplay.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <assert.h>
|
2005-04-22 22:09:39 +01:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "eglcontext.h"
|
2009-07-17 18:48:27 +01:00
|
|
|
#include "eglsurface.h"
|
2005-04-22 22:09:39 +01:00
|
|
|
#include "egldisplay.h"
|
2008-05-27 23:48:23 +01:00
|
|
|
#include "egldriver.h"
|
2005-04-22 22:09:39 +01:00
|
|
|
#include "eglglobals.h"
|
2009-08-10 07:16:32 +01:00
|
|
|
#include "eglmutex.h"
|
2009-08-10 08:13:42 +01:00
|
|
|
#include "egllog.h"
|
2009-08-10 07:16:32 +01:00
|
|
|
|
2011-08-09 13:23:18 +01:00
|
|
|
/* Includes for _eglNativePlatformDetectNativeDisplay */
|
|
|
|
#ifdef HAVE_MINCORE
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_WAYLAND_PLATFORM
|
|
|
|
#include <wayland-client.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_DRM_PLATFORM
|
|
|
|
#include <gbm.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_FBDEV_PLATFORM
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#endif
|
|
|
|
|
2009-08-10 07:16:32 +01:00
|
|
|
|
2010-06-17 16:45:41 +01:00
|
|
|
/**
|
|
|
|
* Return the native platform by parsing EGL_PLATFORM.
|
|
|
|
*/
|
|
|
|
static _EGLPlatformType
|
|
|
|
_eglGetNativePlatformFromEnv(void)
|
|
|
|
{
|
|
|
|
/* map --with-egl-platforms names to platform types */
|
|
|
|
static const struct {
|
|
|
|
_EGLPlatformType platform;
|
|
|
|
const char *name;
|
|
|
|
} egl_platforms[_EGL_NUM_PLATFORMS] = {
|
|
|
|
{ _EGL_PLATFORM_WINDOWS, "gdi" },
|
|
|
|
{ _EGL_PLATFORM_X11, "x11" },
|
2011-02-04 11:22:58 +00:00
|
|
|
{ _EGL_PLATFORM_WAYLAND, "wayland" },
|
2010-09-19 09:54:39 +01:00
|
|
|
{ _EGL_PLATFORM_DRM, "drm" },
|
2010-06-17 16:45:41 +01:00
|
|
|
{ _EGL_PLATFORM_FBDEV, "fbdev" }
|
|
|
|
};
|
|
|
|
_EGLPlatformType plat = _EGL_INVALID_PLATFORM;
|
|
|
|
const char *plat_name;
|
|
|
|
EGLint i;
|
|
|
|
|
|
|
|
plat_name = getenv("EGL_PLATFORM");
|
|
|
|
/* try deprecated env variable */
|
|
|
|
if (!plat_name || !plat_name[0])
|
|
|
|
plat_name = getenv("EGL_DISPLAY");
|
|
|
|
if (!plat_name || !plat_name[0])
|
|
|
|
return _EGL_INVALID_PLATFORM;
|
|
|
|
|
|
|
|
for (i = 0; i < _EGL_NUM_PLATFORMS; i++) {
|
|
|
|
if (strcmp(egl_platforms[i].name, plat_name) == 0) {
|
|
|
|
plat = egl_platforms[i].platform;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return plat;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-08-09 13:23:18 +01:00
|
|
|
/**
|
|
|
|
* Perform validity checks on a generic pointer.
|
|
|
|
*/
|
|
|
|
static EGLBoolean
|
|
|
|
_eglPointerIsDereferencable(void *p)
|
|
|
|
{
|
|
|
|
#ifdef HAVE_MINCORE
|
|
|
|
uintptr_t addr = (uintptr_t) p;
|
|
|
|
unsigned char valid = 0;
|
|
|
|
const long page_size = getpagesize();
|
|
|
|
|
|
|
|
if (p == NULL)
|
|
|
|
return EGL_FALSE;
|
|
|
|
|
|
|
|
/* align addr to page_size */
|
|
|
|
addr &= ~(page_size - 1);
|
|
|
|
|
|
|
|
if (mincore((void *) addr, page_size, &valid) < 0) {
|
|
|
|
_eglLog(_EGL_DEBUG, "mincore failed: %m");
|
|
|
|
return EGL_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (valid & 0x01) == 0x01;
|
|
|
|
#else
|
|
|
|
return p != NULL;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Try detecting native platform with the help of native display characteristcs.
|
|
|
|
*/
|
|
|
|
static _EGLPlatformType
|
|
|
|
_eglNativePlatformDetectNativeDisplay(EGLNativeDisplayType nativeDisplay)
|
|
|
|
{
|
|
|
|
#ifdef HAVE_FBDEV_PLATFORM
|
|
|
|
struct stat buf;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (nativeDisplay == EGL_DEFAULT_DISPLAY)
|
|
|
|
return _EGL_INVALID_PLATFORM;
|
|
|
|
|
|
|
|
#ifdef HAVE_FBDEV_PLATFORM
|
|
|
|
/* fbdev is the only platform that can be a file descriptor. */
|
|
|
|
if (fstat((intptr_t) nativeDisplay, &buf) == 0 && S_ISCHR(buf.st_mode))
|
|
|
|
return _EGL_PLATFORM_FBDEV;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (_eglPointerIsDereferencable(nativeDisplay)) {
|
|
|
|
void *first_pointer = *(void **) nativeDisplay;
|
|
|
|
|
|
|
|
#ifdef HAVE_WAYLAND_PLATFORM
|
|
|
|
/* wl_display is a wl_proxy, which is a wl_object.
|
|
|
|
* wl_object's first element points to the interfacetype. */
|
|
|
|
if (first_pointer == &wl_display_interface)
|
|
|
|
return _EGL_PLATFORM_WAYLAND;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_DRM_PLATFORM
|
|
|
|
/* gbm has a pointer to its constructor as first element. */
|
|
|
|
if (first_pointer == gbm_create_device)
|
|
|
|
return _EGL_PLATFORM_DRM;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_X11_PLATFORM
|
|
|
|
/* If not matched to any other platform, fallback to x11. */
|
|
|
|
return _EGL_PLATFORM_X11;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return _EGL_INVALID_PLATFORM;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-06-17 16:45:41 +01:00
|
|
|
/**
|
|
|
|
* Return the native platform. It is the platform of the EGL native types.
|
|
|
|
*/
|
|
|
|
_EGLPlatformType
|
2011-08-09 13:23:18 +01:00
|
|
|
_eglGetNativePlatform(EGLNativeDisplayType nativeDisplay)
|
2010-06-17 16:45:41 +01:00
|
|
|
{
|
|
|
|
static _EGLPlatformType native_platform = _EGL_INVALID_PLATFORM;
|
|
|
|
|
|
|
|
if (native_platform == _EGL_INVALID_PLATFORM) {
|
|
|
|
native_platform = _eglGetNativePlatformFromEnv();
|
2011-08-09 13:23:18 +01:00
|
|
|
if (native_platform == _EGL_INVALID_PLATFORM) {
|
|
|
|
native_platform = _eglNativePlatformDetectNativeDisplay(nativeDisplay);
|
|
|
|
if (native_platform == _EGL_INVALID_PLATFORM)
|
|
|
|
native_platform = _EGL_NATIVE_PLATFORM;
|
|
|
|
}
|
2010-06-17 16:45:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return native_platform;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-08-10 07:16:32 +01:00
|
|
|
/**
|
|
|
|
* Finish display management.
|
|
|
|
*/
|
2009-08-14 10:47:00 +01:00
|
|
|
void
|
2009-08-10 07:16:32 +01:00
|
|
|
_eglFiniDisplay(void)
|
|
|
|
{
|
2009-08-14 10:47:00 +01:00
|
|
|
_EGLDisplay *dpyList, *dpy;
|
2009-08-10 07:16:32 +01:00
|
|
|
|
2009-08-14 10:47:00 +01:00
|
|
|
/* atexit function is called with global mutex locked */
|
|
|
|
dpyList = _eglGlobal.DisplayList;
|
|
|
|
while (dpyList) {
|
2010-01-24 12:32:34 +00:00
|
|
|
EGLint i;
|
|
|
|
|
2009-08-14 10:47:00 +01:00
|
|
|
/* pop list head */
|
|
|
|
dpy = dpyList;
|
|
|
|
dpyList = dpyList->Next;
|
2009-08-10 07:16:32 +01:00
|
|
|
|
2010-01-24 12:32:34 +00:00
|
|
|
for (i = 0; i < _EGL_NUM_RESOURCES; i++) {
|
|
|
|
if (dpy->ResourceLists[i]) {
|
|
|
|
_eglLog(_EGL_DEBUG, "Display %p is destroyed with resources", dpy);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2009-08-10 07:16:32 +01:00
|
|
|
|
2009-08-14 10:47:00 +01:00
|
|
|
free(dpy);
|
2009-08-10 07:16:32 +01:00
|
|
|
}
|
2009-08-14 10:47:00 +01:00
|
|
|
_eglGlobal.DisplayList = NULL;
|
2009-08-10 07:16:32 +01:00
|
|
|
}
|
2005-04-22 22:09:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
2010-02-17 10:39:27 +00:00
|
|
|
* Find the display corresponding to the specified native display, or create a
|
|
|
|
* new one.
|
2009-07-17 05:21:56 +01:00
|
|
|
*/
|
|
|
|
_EGLDisplay *
|
2010-06-17 10:14:03 +01:00
|
|
|
_eglFindDisplay(_EGLPlatformType plat, void *plat_dpy)
|
2009-07-17 05:21:56 +01:00
|
|
|
{
|
2009-08-14 10:47:00 +01:00
|
|
|
_EGLDisplay *dpy;
|
2009-08-10 07:16:32 +01:00
|
|
|
|
2010-06-17 10:14:03 +01:00
|
|
|
if (plat == _EGL_INVALID_PLATFORM)
|
|
|
|
return NULL;
|
|
|
|
|
2009-08-14 10:47:00 +01:00
|
|
|
_eglLockMutex(_eglGlobal.Mutex);
|
2009-07-17 05:21:56 +01:00
|
|
|
|
2010-02-17 10:39:27 +00:00
|
|
|
/* search the display list first */
|
2009-08-14 10:47:00 +01:00
|
|
|
dpy = _eglGlobal.DisplayList;
|
|
|
|
while (dpy) {
|
2010-06-17 10:14:03 +01:00
|
|
|
if (dpy->Platform == plat && dpy->PlatformDisplay == plat_dpy)
|
2010-02-17 10:39:27 +00:00
|
|
|
break;
|
2009-08-14 10:47:00 +01:00
|
|
|
dpy = dpy->Next;
|
2009-07-17 05:21:56 +01:00
|
|
|
}
|
|
|
|
|
2010-02-17 10:39:27 +00:00
|
|
|
/* create a new display */
|
|
|
|
if (!dpy) {
|
|
|
|
dpy = (_EGLDisplay *) calloc(1, sizeof(_EGLDisplay));
|
|
|
|
if (dpy) {
|
|
|
|
_eglInitMutex(&dpy->Mutex);
|
2010-06-17 10:14:03 +01:00
|
|
|
dpy->Platform = plat;
|
|
|
|
dpy->PlatformDisplay = plat_dpy;
|
2010-02-17 10:39:27 +00:00
|
|
|
|
|
|
|
/* add to the display list */
|
|
|
|
dpy->Next = _eglGlobal.DisplayList;
|
|
|
|
_eglGlobal.DisplayList = dpy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-14 10:47:00 +01:00
|
|
|
_eglUnlockMutex(_eglGlobal.Mutex);
|
|
|
|
|
2010-02-17 10:39:27 +00:00
|
|
|
return dpy;
|
2009-07-17 05:21:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-07-17 05:21:57 +01:00
|
|
|
/**
|
|
|
|
* Destroy the contexts and surfaces that are linked to the display.
|
|
|
|
*/
|
|
|
|
void
|
2009-08-11 10:09:39 +01:00
|
|
|
_eglReleaseDisplayResources(_EGLDriver *drv, _EGLDisplay *display)
|
2009-07-17 05:21:57 +01:00
|
|
|
{
|
2010-01-24 12:32:34 +00:00
|
|
|
_EGLResource *list;
|
2009-07-17 05:21:57 +01:00
|
|
|
|
2010-01-24 12:32:34 +00:00
|
|
|
list = display->ResourceLists[_EGL_RESOURCE_CONTEXT];
|
|
|
|
while (list) {
|
|
|
|
_EGLContext *ctx = (_EGLContext *) list;
|
|
|
|
list = list->Next;
|
2009-08-11 10:09:39 +01:00
|
|
|
|
|
|
|
_eglUnlinkContext(ctx);
|
|
|
|
drv->API.DestroyContext(drv, display, ctx);
|
2009-07-17 05:21:57 +01:00
|
|
|
}
|
2010-01-24 12:32:34 +00:00
|
|
|
assert(!display->ResourceLists[_EGL_RESOURCE_CONTEXT]);
|
2009-07-17 05:21:57 +01:00
|
|
|
|
2010-01-24 12:32:34 +00:00
|
|
|
list = display->ResourceLists[_EGL_RESOURCE_SURFACE];
|
|
|
|
while (list) {
|
|
|
|
_EGLSurface *surf = (_EGLSurface *) list;
|
|
|
|
list = list->Next;
|
2009-08-11 10:09:39 +01:00
|
|
|
|
|
|
|
_eglUnlinkSurface(surf);
|
|
|
|
drv->API.DestroySurface(drv, display, surf);
|
2009-07-17 05:21:57 +01:00
|
|
|
}
|
2010-01-24 12:32:34 +00:00
|
|
|
assert(!display->ResourceLists[_EGL_RESOURCE_SURFACE]);
|
2009-07-17 05:21:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-06-04 18:34:10 +01:00
|
|
|
/**
|
|
|
|
* Free all the data hanging of an _EGLDisplay object, but not
|
|
|
|
* the object itself.
|
|
|
|
*/
|
2005-04-22 22:09:39 +01:00
|
|
|
void
|
2005-05-13 19:31:35 +01:00
|
|
|
_eglCleanupDisplay(_EGLDisplay *disp)
|
2005-04-22 22:09:39 +01:00
|
|
|
{
|
2009-08-13 06:39:51 +01:00
|
|
|
if (disp->Configs) {
|
2010-06-30 10:10:10 +01:00
|
|
|
_eglDestroyArray(disp->Configs, free);
|
2009-08-13 06:39:51 +01:00
|
|
|
disp->Configs = NULL;
|
2008-06-04 18:34:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* XXX incomplete */
|
2005-04-22 22:09:39 +01:00
|
|
|
}
|
2009-07-17 18:48:27 +01:00
|
|
|
|
|
|
|
|
2009-08-14 11:02:38 +01:00
|
|
|
/**
|
|
|
|
* Return EGL_TRUE if the given handle is a valid handle to a display.
|
|
|
|
*/
|
|
|
|
EGLBoolean
|
|
|
|
_eglCheckDisplayHandle(EGLDisplay dpy)
|
|
|
|
{
|
|
|
|
_EGLDisplay *cur;
|
|
|
|
|
|
|
|
_eglLockMutex(_eglGlobal.Mutex);
|
|
|
|
cur = _eglGlobal.DisplayList;
|
|
|
|
while (cur) {
|
|
|
|
if (cur == (_EGLDisplay *) dpy)
|
|
|
|
break;
|
|
|
|
cur = cur->Next;
|
|
|
|
}
|
|
|
|
_eglUnlockMutex(_eglGlobal.Mutex);
|
|
|
|
return (cur != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-01-24 12:30:04 +00:00
|
|
|
/**
|
|
|
|
* Return EGL_TRUE if the given resource is valid. That is, the display does
|
|
|
|
* own the resource.
|
|
|
|
*/
|
|
|
|
EGLBoolean
|
2010-01-25 03:39:44 +00:00
|
|
|
_eglCheckResource(void *res, _EGLResourceType type, _EGLDisplay *dpy)
|
2010-01-24 12:30:04 +00:00
|
|
|
{
|
|
|
|
_EGLResource *list = dpy->ResourceLists[type];
|
|
|
|
|
2010-01-25 03:39:44 +00:00
|
|
|
if (!res)
|
|
|
|
return EGL_FALSE;
|
|
|
|
|
2010-01-24 12:30:04 +00:00
|
|
|
while (list) {
|
2010-01-25 03:39:44 +00:00
|
|
|
if (res == (void *) list) {
|
2010-01-24 12:30:04 +00:00
|
|
|
assert(list->Display == dpy);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
list = list->Next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (list != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-10-23 04:59:03 +01:00
|
|
|
/**
|
|
|
|
* Initialize a display resource.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
_eglInitResource(_EGLResource *res, EGLint size, _EGLDisplay *dpy)
|
|
|
|
{
|
|
|
|
memset(res, 0, size);
|
|
|
|
res->Display = dpy;
|
|
|
|
res->RefCount = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Increment reference count for the resource.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
_eglGetResource(_EGLResource *res)
|
|
|
|
{
|
|
|
|
assert(res && res->RefCount > 0);
|
|
|
|
/* hopefully a resource is always manipulated with its display locked */
|
|
|
|
res->RefCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decrement reference count for the resource.
|
|
|
|
*/
|
|
|
|
EGLBoolean
|
|
|
|
_eglPutResource(_EGLResource *res)
|
|
|
|
{
|
|
|
|
assert(res && res->RefCount > 0);
|
|
|
|
res->RefCount--;
|
|
|
|
return (!res->RefCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-01-24 12:30:04 +00:00
|
|
|
/**
|
2010-10-22 17:37:19 +01:00
|
|
|
* Link a resource to its display.
|
2010-01-24 12:30:04 +00:00
|
|
|
*/
|
|
|
|
void
|
2010-10-22 17:37:19 +01:00
|
|
|
_eglLinkResource(_EGLResource *res, _EGLResourceType type)
|
2010-01-24 12:30:04 +00:00
|
|
|
{
|
2010-10-22 17:37:19 +01:00
|
|
|
assert(res->Display);
|
2010-01-26 07:16:49 +00:00
|
|
|
|
|
|
|
res->IsLinked = EGL_TRUE;
|
2010-10-22 17:37:19 +01:00
|
|
|
res->Next = res->Display->ResourceLists[type];
|
|
|
|
res->Display->ResourceLists[type] = res;
|
2010-10-23 04:59:03 +01:00
|
|
|
_eglGetResource(res);
|
2010-01-24 12:30:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unlink a linked resource from its display.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
_eglUnlinkResource(_EGLResource *res, _EGLResourceType type)
|
|
|
|
{
|
|
|
|
_EGLResource *prev;
|
|
|
|
|
|
|
|
prev = res->Display->ResourceLists[type];
|
|
|
|
if (prev != res) {
|
|
|
|
while (prev) {
|
|
|
|
if (prev->Next == res)
|
|
|
|
break;
|
|
|
|
prev = prev->Next;
|
|
|
|
}
|
|
|
|
assert(prev);
|
|
|
|
prev->Next = res->Next;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
res->Display->ResourceLists[type] = res->Next;
|
|
|
|
}
|
|
|
|
|
|
|
|
res->Next = NULL;
|
2010-01-26 07:16:49 +00:00
|
|
|
res->IsLinked = EGL_FALSE;
|
2010-10-23 04:59:03 +01:00
|
|
|
_eglPutResource(res);
|
|
|
|
|
|
|
|
/* We always unlink before destroy. The driver still owns a reference */
|
|
|
|
assert(res->RefCount);
|
2010-01-24 12:30:04 +00:00
|
|
|
}
|