xserver-multidpi/hw/kdrive/ephyr/ephyrvideo.c

1141 lines
37 KiB
C
Raw Normal View History

/*
2007-07-21 12:08:39 +02:00
* Xephyr - A kdrive X server thats runs in a host X window.
* Authored by Matthew Allum <mallum@openedhand.com>
*
* Copyright © 2007 OpenedHand Ltd
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of OpenedHand Ltd not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. OpenedHand Ltd makes no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* OpenedHand Ltd DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL OpenedHand Ltd BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* Authors:
* Dodji Seketeli <dodji@openedhand.com>
*/
#ifdef HAVE_CONFIG_H
#include <kdrive-config.h>
#endif
#include <string.h>
#include <X11/extensions/Xv.h>
#include "ephyrlog.h"
#include "kdrive.h"
#include "kxv.h"
#include "ephyr.h"
#include "hostx.h"
#include "ephyrhostvideo.h"
struct _EphyrXVPriv {
xcb_xv_query_adaptors_reply_t *host_adaptors;
KdVideoAdaptorPtr adaptors;
int num_adaptors;
2007-07-21 12:08:39 +02:00
};
typedef struct _EphyrXVPriv EphyrXVPriv;
2007-07-21 12:08:39 +02:00
struct _EphyrPortPriv {
int port_number;
KdVideoAdaptorPtr current_adaptor;
EphyrXVPriv *xv_priv;
unsigned char *image_buf;
int image_buf_size;
int image_id;
int drw_x, drw_y, drw_w, drw_h;
int src_x, src_y, src_w, src_h;
int image_width, image_height;
};
typedef struct _EphyrPortPriv EphyrPortPriv;
static Bool DoSimpleClip(BoxPtr a_dst_drw, BoxPtr a_clipper, BoxPtr a_result);
static Bool ephyrLocalAtomToHost(int a_local_atom, int *a_host_atom);
static EphyrXVPriv *ephyrXVPrivNew(void);
static void ephyrXVPrivDelete(EphyrXVPriv * a_this);
static Bool ephyrXVPrivQueryHostAdaptors(EphyrXVPriv * a_this);
static Bool ephyrXVPrivSetAdaptorsHooks(EphyrXVPriv * a_this);
static Bool ephyrXVPrivRegisterAdaptors(EphyrXVPriv * a_this,
ScreenPtr a_screen);
static Bool ephyrXVPrivIsAttrValueValid(KdAttributePtr a_attrs,
int a_attrs_len,
const char *a_attr_name,
int a_attr_value, Bool *a_is_valid);
static Bool ephyrXVPrivGetImageBufSize(int a_port_id,
int a_image_id,
unsigned short a_width,
unsigned short a_height, int *a_size);
static Bool ephyrXVPrivSaveImageToPortPriv(EphyrPortPriv * a_port_priv,
const unsigned char *a_image,
int a_image_len);
static void ephyrStopVideo(KdScreenInfo * a_info,
pointer a_xv_priv, Bool a_exit);
static int ephyrSetPortAttribute(KdScreenInfo * a_info,
Atom a_attr_name,
int a_attr_value, pointer a_port_priv);
static int ephyrGetPortAttribute(KdScreenInfo * a_screen_info,
Atom a_attr_name,
int *a_attr_value, pointer a_port_priv);
static void ephyrQueryBestSize(KdScreenInfo * a_info,
Bool a_motion,
short a_src_w,
short a_src_h,
short a_drw_w,
short a_drw_h,
unsigned int *a_prefered_w,
unsigned int *a_prefered_h, pointer a_port_priv);
static int ephyrPutImage(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_src_x,
short a_src_y,
short a_drw_x,
short a_drw_y,
short a_src_w,
short a_src_h,
short a_drw_w,
short a_drw_h,
int a_id,
unsigned char *a_buf,
short a_width,
short a_height,
Bool a_sync,
RegionPtr a_clipping_region, pointer a_port_priv);
static int ephyrReputImage(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_drw_x,
short a_drw_y,
RegionPtr a_clipping_region, pointer a_port_priv);
static int ephyrPutVideo(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_vid_x, short a_vid_y,
short a_drw_x, short a_drw_y,
short a_vid_w, short a_vid_h,
short a_drw_w, short a_drw_h,
RegionPtr a_clip_region, pointer a_port_priv);
static int ephyrGetVideo(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_vid_x, short a_vid_y,
short a_drw_x, short a_drw_y,
short a_vid_w, short a_vid_h,
short a_drw_w, short a_drw_h,
RegionPtr a_clip_region, pointer a_port_priv);
static int ephyrPutStill(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_vid_x, short a_vid_y,
short a_drw_x, short a_drw_y,
short a_vid_w, short a_vid_h,
short a_drw_w, short a_drw_h,
RegionPtr a_clip_region, pointer a_port_priv);
static int ephyrGetStill(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_vid_x, short a_vid_y,
short a_drw_x, short a_drw_y,
short a_vid_w, short a_vid_h,
short a_drw_w, short a_drw_h,
RegionPtr a_clip_region, pointer a_port_priv);
static int ephyrQueryImageAttributes(KdScreenInfo * a_info,
int a_id,
unsigned short *a_w,
unsigned short *a_h,
int *a_pitches, int *a_offsets);
static int s_base_port_id;
/**************
* <helpers>
* ************/
static Bool
adaptor_has_flags(const xcb_xv_adaptor_info_t *adaptor, uint32_t flags)
{
return (adaptor->type & flags) == flags;
}
static Bool
DoSimpleClip(BoxPtr a_dst_box, BoxPtr a_clipper, BoxPtr a_result)
{
BoxRec dstClippedBox;
EPHYR_RETURN_VAL_IF_FAIL(a_dst_box && a_clipper && a_result, FALSE);
/*
* setup the clipbox inside the destination.
*/
dstClippedBox.x1 = a_dst_box->x1;
dstClippedBox.x2 = a_dst_box->x2;
dstClippedBox.y1 = a_dst_box->y1;
dstClippedBox.y2 = a_dst_box->y2;
/*
* if the cliper leftmost edge is inside
* the destination area then the leftmost edge of the resulting
* clipped box is the leftmost edge of the cliper.
*/
if (a_clipper->x1 > dstClippedBox.x1)
dstClippedBox.x1 = a_clipper->x1;
/*
* if the cliper top edge is inside the destination area
* then the bottom horizontal edge of the resulting clipped box
* is the bottom edge of the cliper
*/
if (a_clipper->y1 > dstClippedBox.y1)
dstClippedBox.y1 = a_clipper->y1;
/*ditto for right edge */
if (a_clipper->x2 < dstClippedBox.x2)
dstClippedBox.x2 = a_clipper->x2;
/*ditto for bottom edge */
if (a_clipper->y2 < dstClippedBox.y2)
dstClippedBox.y2 = a_clipper->y2;
memcpy(a_result, &dstClippedBox, sizeof(dstClippedBox));
return TRUE;
}
static Bool
ephyrLocalAtomToHost(int a_local_atom, int *a_host_atom)
{
const char *atom_name = NULL;
int host_atom = None;
EPHYR_RETURN_VAL_IF_FAIL(a_host_atom, FALSE);
if (!ValidAtom(a_local_atom))
return FALSE;
atom_name = NameForAtom(a_local_atom);
if (!atom_name)
return FALSE;
if (!ephyrHostGetAtom(atom_name, FALSE, &host_atom) || host_atom == None) {
EPHYR_LOG_ERROR("no atom for string %s defined in host X\n", atom_name);
return FALSE;
}
*a_host_atom = host_atom;
return TRUE;
}
/**************
*</helpers>
* ************/
2007-07-21 12:08:39 +02:00
Bool
ephyrInitVideo(ScreenPtr pScreen)
2007-07-21 12:08:39 +02:00
{
Bool is_ok = FALSE;
2007-07-21 12:08:39 +02:00
KdScreenPriv(pScreen);
KdScreenInfo *screen = pScreenPriv->screen;
static EphyrXVPriv *xv_priv;
2007-07-21 12:08:39 +02:00
EPHYR_LOG("enter\n");
2007-07-21 12:08:39 +02:00
if (screen->fb.bitsPerPixel == 8) {
EPHYR_LOG_ERROR("8 bits depth not supported\n");
return FALSE;
2007-07-21 12:08:39 +02:00
}
if (!xv_priv) {
xv_priv = ephyrXVPrivNew();
}
2007-07-21 12:08:39 +02:00
if (!xv_priv) {
EPHYR_LOG_ERROR("failed to create xv_priv\n");
goto out;
2007-07-21 12:08:39 +02:00
}
if (!ephyrXVPrivRegisterAdaptors(xv_priv, pScreen)) {
EPHYR_LOG_ERROR("failed to register adaptors\n");
goto out;
2007-07-21 12:08:39 +02:00
}
is_ok = TRUE;
out:
return is_ok;
2007-07-21 12:08:39 +02:00
}
static EphyrXVPriv *
ephyrXVPrivNew(void)
2007-07-21 12:08:39 +02:00
{
EphyrXVPriv *xv_priv = NULL;
2007-07-21 12:08:39 +02:00
EPHYR_LOG("enter\n");
2007-07-21 12:08:39 +02:00
xv_priv = calloc(1, sizeof(EphyrXVPriv));
2007-07-21 12:08:39 +02:00
if (!xv_priv) {
EPHYR_LOG_ERROR("failed to create EphyrXVPriv\n");
goto error;
2007-07-21 12:08:39 +02:00
}
if (!ephyrXVPrivQueryHostAdaptors(xv_priv)) {
EPHYR_LOG_ERROR("failed to query the host x for xv properties\n");
goto error;
2007-07-21 12:08:39 +02:00
}
if (!ephyrXVPrivSetAdaptorsHooks(xv_priv)) {
EPHYR_LOG_ERROR("failed to set xv_priv hooks\n");
goto error;
2007-07-21 12:08:39 +02:00
}
EPHYR_LOG("leave\n");
return xv_priv;
2007-07-21 12:08:39 +02:00
error:
2007-07-21 12:08:39 +02:00
if (xv_priv) {
ephyrXVPrivDelete(xv_priv);
xv_priv = NULL;
2007-07-21 12:08:39 +02:00
}
return NULL;
2007-07-21 12:08:39 +02:00
}
static void
ephyrXVPrivDelete(EphyrXVPriv * a_this)
2007-07-21 12:08:39 +02:00
{
EPHYR_LOG("enter\n");
2007-07-21 12:08:39 +02:00
if (!a_this)
return;
2007-07-21 12:08:39 +02:00
if (a_this->host_adaptors) {
free(a_this->host_adaptors);
a_this->host_adaptors = NULL;
2007-07-21 12:08:39 +02:00
}
free(a_this->adaptors);
a_this->adaptors = NULL;
free(a_this);
EPHYR_LOG("leave\n");
2007-07-21 12:08:39 +02:00
}
static KdVideoEncodingPtr
videoEncodingDup(EphyrHostEncoding * a_encodings, int a_num_encodings)
2007-07-21 12:08:39 +02:00
{
KdVideoEncodingPtr result = NULL;
int i = 0;
EPHYR_RETURN_VAL_IF_FAIL(a_encodings && a_num_encodings, NULL);
result = calloc(a_num_encodings, sizeof(KdVideoEncodingRec));
for (i = 0; i < a_num_encodings; i++) {
result[i].id = a_encodings[i].id;
result[i].name = strdup(a_encodings[i].name);
result[i].width = a_encodings[i].width;
result[i].height = a_encodings[i].height;
result[i].rate.numerator = a_encodings[i].rate.numerator;
result[i].rate.denominator = a_encodings[i].rate.denominator;
2007-07-21 12:08:39 +02:00
}
return result;
2007-07-21 12:08:39 +02:00
}
static KdAttributePtr
portAttributesDup(const xcb_xv_query_port_attributes_reply_t *a_encodings)
2007-07-21 12:08:39 +02:00
{
int i = 0;
KdAttributePtr result = NULL;
xcb_xv_attribute_info_iterator_t it;
2007-07-21 12:08:39 +02:00
EPHYR_RETURN_VAL_IF_FAIL(a_encodings, NULL);
2007-07-21 12:08:39 +02:00
result = calloc(a_encodings->num_attributes, sizeof(KdAttributeRec));
2007-07-21 12:08:39 +02:00
if (!result) {
EPHYR_LOG_ERROR("failed to allocate attributes\n");
return NULL;
2007-07-21 12:08:39 +02:00
}
it = xcb_xv_query_port_attributes_attributes_iterator(a_encodings);
for (i = 0;
i < a_encodings->num_attributes;
xcb_xv_attribute_info_next(&it), i++) {
result[i].flags = it.data->flags;
result[i].min_value = it.data->min;
result[i].max_value = it.data->max;
result[i].name = malloc(it.data->size + 1);
memcpy (result[i].name, xcb_xv_attribute_info_name(it.data), it.data->size);
result[i].name[it.data->size] = '\0';
2007-07-21 12:08:39 +02:00
}
return result;
2007-07-21 12:08:39 +02:00
}
static Bool
ephyrXVPrivQueryHostAdaptors(EphyrXVPriv * a_this)
2007-07-21 12:08:39 +02:00
{
xcb_xv_adaptor_info_t *cur_host_adaptor = NULL;
EphyrHostVideoFormat *video_formats = NULL;
EphyrHostEncoding *encodings = NULL;
xcb_xv_query_port_attributes_reply_t *attributes = NULL;
EphyrHostImageFormat *image_formats = NULL;
int num_video_formats = 0, base_port_id = 0,
num_formats = 0, i = 0, port_priv_offset = 0;
unsigned num_encodings = 0;
Bool is_ok = FALSE;
EPHYR_RETURN_VAL_IF_FAIL(a_this, FALSE);
EPHYR_LOG("enter\n");
if (!ephyrHostXVQueryAdaptors(&a_this->host_adaptors)) {
EPHYR_LOG_ERROR("failed to query host adaptors\n");
goto out;
2007-07-21 12:08:39 +02:00
}
if (a_this->host_adaptors)
a_this->num_adaptors = a_this->host_adaptors->num_adaptors;
2007-07-21 12:08:39 +02:00
if (a_this->num_adaptors < 0) {
EPHYR_LOG_ERROR("failed to get number of host adaptors\n");
goto out;
2007-07-21 12:08:39 +02:00
}
EPHYR_LOG("host has %d adaptors\n", a_this->num_adaptors);
2007-07-21 12:08:39 +02:00
/*
* copy what we can from adaptors into a_this->adaptors
*/
if (a_this->num_adaptors) {
a_this->adaptors = calloc(a_this->num_adaptors,
sizeof(KdVideoAdaptorRec));
if (!a_this->adaptors) {
EPHYR_LOG_ERROR("failed to create internal adaptors\n");
goto out;
}
2007-07-21 12:08:39 +02:00
}
for (i = 0; i < a_this->num_adaptors; i++) {
int j = 0;
cur_host_adaptor = ephyrHostXVAdaptorArrayAt(a_this->host_adaptors, i);
2007-07-21 12:08:39 +02:00
if (!cur_host_adaptor)
continue;
a_this->adaptors[i].nPorts = cur_host_adaptor->num_ports;
if (a_this->adaptors[i].nPorts <= 0) {
EPHYR_LOG_ERROR("Could not find any port of adaptor %d\n", i);
continue;
}
a_this->adaptors[i].type = ephyrHostXVAdaptorGetType(cur_host_adaptor);
a_this->adaptors[i].type |= XvWindowMask;
a_this->adaptors[i].flags =
VIDEO_OVERLAID_IMAGES | VIDEO_CLIP_TO_VIEWPORT;
a_this->adaptors[i].name = ephyrHostXVAdaptorGetName(cur_host_adaptor);
if (!a_this->adaptors[i].name)
a_this->adaptors[i].name = strdup("Xephyr Video Overlay");
base_port_id = cur_host_adaptor->base_id;
2007-07-21 12:08:39 +02:00
if (base_port_id < 0) {
EPHYR_LOG_ERROR("failed to get port id for adaptor %d\n", i);
continue;
2007-07-21 12:08:39 +02:00
}
if (!s_base_port_id)
s_base_port_id = base_port_id;
if (!ephyrHostXVQueryEncodings(base_port_id,
&encodings, &num_encodings)) {
EPHYR_LOG_ERROR("failed to get encodings for port port id %d,"
" adaptors %d\n", base_port_id, i);
continue;
2007-07-21 12:08:39 +02:00
}
a_this->adaptors[i].nEncodings = num_encodings;
a_this->adaptors[i].pEncodings =
videoEncodingDup(encodings, num_encodings);
video_formats = (EphyrHostVideoFormat *)
ephyrHostXVAdaptorGetVideoFormats(cur_host_adaptor,
&num_video_formats);
a_this->adaptors[i].pFormats = (KdVideoFormatPtr) video_formats;
a_this->adaptors[i].nFormats = num_video_formats;
a_this->adaptors[i].pPortPrivates =
calloc(a_this->adaptors[i].nPorts,
sizeof(DevUnion) + sizeof(EphyrPortPriv));
port_priv_offset = a_this->adaptors[i].nPorts;
for (j = 0; j < a_this->adaptors[i].nPorts; j++) {
EphyrPortPriv *port_privs_base =
(EphyrPortPriv *) &a_this->adaptors[i].
pPortPrivates[port_priv_offset];
EphyrPortPriv *port_priv = &port_privs_base[j];
port_priv->port_number = base_port_id + j;
port_priv->current_adaptor = &a_this->adaptors[i];
port_priv->xv_priv = a_this;
a_this->adaptors[i].pPortPrivates[j].ptr = port_priv;
2007-07-21 12:08:39 +02:00
}
if (!ephyrHostXVQueryPortAttributes(base_port_id, &attributes)) {
EPHYR_LOG_ERROR("failed to get port attribute "
"for adaptor %d\n", i);
continue;
2007-07-21 12:08:39 +02:00
}
a_this->adaptors[i].pAttributes =
portAttributesDup(attributes);
a_this->adaptors[i].nAttributes = attributes->num_attributes;
/*make sure atoms of attrs names are created in xephyr */
for (j = 0; j < a_this->adaptors[i].nAttributes; j++) {
if (a_this->adaptors[i].pAttributes[j].name)
MakeAtom(a_this->adaptors[i].pAttributes[j].name,
strlen(a_this->adaptors[i].pAttributes[j].name), TRUE);
}
if (!ephyrHostXVQueryImageFormats(base_port_id,
&image_formats, &num_formats)) {
EPHYR_LOG_ERROR("failed to get image formats "
"for adaptor %d\n", i);
continue;
2007-07-21 12:08:39 +02:00
}
a_this->adaptors[i].pImages = (KdImagePtr) image_formats;
a_this->adaptors[i].nImages = num_formats;
2007-07-21 12:08:39 +02:00
}
is_ok = TRUE;
2007-07-21 12:08:39 +02:00
out:
2007-07-21 12:08:39 +02:00
if (encodings) {
ephyrHostEncodingsDelete(encodings, num_encodings);
encodings = NULL;
2007-07-21 12:08:39 +02:00
}
if (attributes) {
ephyrHostAttributesDelete(attributes);
attributes = NULL;
2007-07-21 12:08:39 +02:00
}
EPHYR_LOG("leave\n");
return is_ok;
2007-07-21 12:08:39 +02:00
}
static Bool
ephyrXVPrivSetAdaptorsHooks(EphyrXVPriv * a_this)
2007-07-21 12:08:39 +02:00
{
int i = 0;
xcb_xv_adaptor_info_t *cur_host_adaptor = NULL;
2007-07-21 12:08:39 +02:00
EPHYR_RETURN_VAL_IF_FAIL(a_this, FALSE);
2007-07-21 12:08:39 +02:00
EPHYR_LOG("enter\n");
2007-07-21 17:55:12 +02:00
for (i = 0; i < a_this->num_adaptors; i++) {
a_this->adaptors[i].ReputImage = ephyrReputImage;
a_this->adaptors[i].StopVideo = ephyrStopVideo;
a_this->adaptors[i].SetPortAttribute = ephyrSetPortAttribute;
a_this->adaptors[i].GetPortAttribute = ephyrGetPortAttribute;
a_this->adaptors[i].QueryBestSize = ephyrQueryBestSize;
a_this->adaptors[i].QueryImageAttributes = ephyrQueryImageAttributes;
cur_host_adaptor = ephyrHostXVAdaptorArrayAt(a_this->host_adaptors, i);
if (!cur_host_adaptor) {
EPHYR_LOG_ERROR("failed to get host adaptor at index %d\n", i);
continue;
}
if (adaptor_has_flags(cur_host_adaptor,
XCB_XV_TYPE_IMAGE_MASK | XCB_XV_TYPE_INPUT_MASK))
a_this->adaptors[i].PutImage = ephyrPutImage;
if (adaptor_has_flags(cur_host_adaptor,
XCB_XV_TYPE_VIDEO_MASK | XCB_XV_TYPE_INPUT_MASK))
a_this->adaptors[i].PutVideo = ephyrPutVideo;
if (adaptor_has_flags(cur_host_adaptor,
XCB_XV_TYPE_VIDEO_MASK | XCB_XV_TYPE_OUTPUT_MASK))
a_this->adaptors[i].GetVideo = ephyrGetVideo;
if (adaptor_has_flags(cur_host_adaptor,
XCB_XV_TYPE_STILL_MASK | XCB_XV_TYPE_INPUT_MASK))
a_this->adaptors[i].PutStill = ephyrPutStill;
if (adaptor_has_flags(cur_host_adaptor,
XCB_XV_TYPE_STILL_MASK | XCB_XV_TYPE_OUTPUT_MASK))
a_this->adaptors[i].GetStill = ephyrGetStill;
2007-07-21 12:08:39 +02:00
}
EPHYR_LOG("leave\n");
return TRUE;
2007-07-21 12:08:39 +02:00
}
2007-07-21 17:55:12 +02:00
static Bool
ephyrXVPrivRegisterAdaptors(EphyrXVPriv * a_this, ScreenPtr a_screen)
2007-07-21 17:55:12 +02:00
{
KdScreenPriv(a_screen);
KdScreenInfo *screen = pScreenPriv->screen;
Bool is_ok = FALSE;
KdVideoAdaptorPtr *adaptors = NULL, *registered_adaptors = NULL;
int num_registered_adaptors = 0, i = 0, num_adaptors = 0;
2007-07-21 17:55:12 +02:00
EPHYR_RETURN_VAL_IF_FAIL(a_this && a_screen, FALSE);
2007-07-21 17:55:12 +02:00
EPHYR_LOG("enter\n");
2007-07-21 17:55:12 +02:00
if (!a_this->num_adaptors)
goto out;
num_registered_adaptors =
KdXVListGenericAdaptors(screen, &registered_adaptors);
num_adaptors = num_registered_adaptors + a_this->num_adaptors;
adaptors = calloc(num_adaptors, sizeof(KdVideoAdaptorPtr));
2007-07-21 17:55:12 +02:00
if (!adaptors) {
EPHYR_LOG_ERROR("failed to allocate adaptors tab\n");
goto out;
2007-07-21 17:55:12 +02:00
}
memmove(adaptors, registered_adaptors, num_registered_adaptors);
for (i = 0; i < a_this->num_adaptors; i++) {
*(adaptors + num_registered_adaptors + i) = &a_this->adaptors[i];
2007-07-21 17:55:12 +02:00
}
if (!KdXVScreenInit(a_screen, adaptors, num_adaptors)) {
EPHYR_LOG_ERROR("failed to register adaptors\n");
goto out;
}
EPHYR_LOG("there are %d registered adaptors\n", num_adaptors);
is_ok = TRUE;
2007-07-21 17:55:12 +02:00
out:
free(registered_adaptors);
registered_adaptors = NULL;
free(adaptors);
adaptors = NULL;
EPHYR_LOG("leave\n");
return is_ok;
2007-07-21 17:55:12 +02:00
}
static Bool
ephyrXVPrivIsAttrValueValid(KdAttributePtr a_attrs,
int a_attrs_len,
const char *a_attr_name,
int a_attr_value, Bool *a_is_valid)
{
int i = 0;
EPHYR_RETURN_VAL_IF_FAIL(a_attrs && a_attr_name && a_is_valid, FALSE);
for (i = 0; i < a_attrs_len; i++) {
if (a_attrs[i].name && strcmp(a_attrs[i].name, a_attr_name))
continue;
if (a_attrs[i].min_value > a_attr_value ||
a_attrs[i].max_value < a_attr_value) {
*a_is_valid = FALSE;
EPHYR_LOG_ERROR("attribute was not valid\n"
"value:%d. min:%d. max:%d\n",
a_attr_value,
a_attrs[i].min_value, a_attrs[i].max_value);
}
else {
*a_is_valid = TRUE;
}
return TRUE;
}
return FALSE;
}
static Bool
ephyrXVPrivGetImageBufSize(int a_port_id,
int a_image_id,
unsigned short a_width,
unsigned short a_height, int *a_size)
{
Bool is_ok = FALSE;
unsigned short width = a_width, height = a_height;
EPHYR_RETURN_VAL_IF_FAIL(a_size, FALSE);
EPHYR_LOG("enter\n");
if (!ephyrHostXVQueryImageAttributes(a_port_id, a_image_id,
&width, &height, a_size, NULL, NULL)) {
EPHYR_LOG_ERROR("failed to get image attributes\n");
goto out;
}
is_ok = TRUE;
out:
EPHYR_LOG("leave\n");
return is_ok;
}
static Bool
ephyrXVPrivSaveImageToPortPriv(EphyrPortPriv * a_port_priv,
const unsigned char *a_image_buf,
int a_image_len)
{
Bool is_ok = FALSE;
EPHYR_LOG("enter\n");
if (a_port_priv->image_buf_size < a_image_len) {
unsigned char *buf = NULL;
buf = realloc(a_port_priv->image_buf, a_image_len);
if (!buf) {
EPHYR_LOG_ERROR("failed to realloc image buffer\n");
goto out;
}
a_port_priv->image_buf = buf;
a_port_priv->image_buf_size = a_image_len;
}
memmove(a_port_priv->image_buf, a_image_buf, a_image_len);
is_ok = TRUE;
out:
return is_ok;
EPHYR_LOG("leave\n");
}
2007-07-21 12:08:39 +02:00
static void
ephyrStopVideo(KdScreenInfo * a_info, pointer a_port_priv, Bool a_exit)
2007-07-21 12:08:39 +02:00
{
xcb_connection_t *conn = hostx_get_xcbconn();
EphyrPortPriv *port_priv = a_port_priv;
EphyrScrPriv *scrpriv = a_info->driver;
EPHYR_RETURN_IF_FAIL(port_priv);
EPHYR_LOG("enter\n");
xcb_xv_stop_video(conn, port_priv->port_number, scrpriv->win);
EPHYR_LOG("leave\n");
2007-07-21 12:08:39 +02:00
}
static int
ephyrSetPortAttribute(KdScreenInfo * a_info,
Atom a_attr_name, int a_attr_value, pointer a_port_priv)
2007-07-21 12:08:39 +02:00
{
int res = Success, host_atom = 0;
EphyrPortPriv *port_priv = a_port_priv;
Bool is_attr_valid = FALSE;
EPHYR_RETURN_VAL_IF_FAIL(port_priv, BadMatch);
EPHYR_RETURN_VAL_IF_FAIL(port_priv->current_adaptor, BadMatch);
EPHYR_RETURN_VAL_IF_FAIL(port_priv->current_adaptor->pAttributes, BadMatch);
EPHYR_RETURN_VAL_IF_FAIL(port_priv->current_adaptor->nAttributes, BadMatch);
EPHYR_RETURN_VAL_IF_FAIL(ValidAtom(a_attr_name), BadMatch);
EPHYR_LOG("enter, portnum:%d, atomid:%d, attr_name:%s, attr_val:%d\n",
port_priv->port_number,
(int) a_attr_name, NameForAtom(a_attr_name), a_attr_value);
if (!ephyrLocalAtomToHost(a_attr_name, &host_atom)) {
EPHYR_LOG_ERROR("failed to convert local atom to host atom\n");
res = BadMatch;
goto out;
}
if (!ephyrXVPrivIsAttrValueValid(port_priv->current_adaptor->pAttributes,
port_priv->current_adaptor->nAttributes,
NameForAtom(a_attr_name),
a_attr_value, &is_attr_valid)) {
EPHYR_LOG_ERROR("failed to validate attribute %s\n",
NameForAtom(a_attr_name));
Xephyr: port XV/GL stuff of the new multiscreen architecture We can now launch GL or XV apps in any of the Xephyr screens we want. * hw/kdrive/ephyr/hostx.c,h: (hostx_get_window): (hostx_create_window): make these functions be screen number aware. * hw/kdrive/ephyr/XF86dri.c : fix some compiler warnings. * hw/kdrive/ephyr/ephyrdri.c: (ephyrDRIQueryDirectRenderingCapable), (ephyrDRIOpenConnection), (ephyrDRIAuthConnection), (ephyrDRICloseConnection), (ephyrDRIGetClientDriverName), (ephyrDRICreateContext), (ephyrDRIDestroyContext), (ephyrDRICreateDrawable), (ephyrDRIGetDrawableInfo), (ephyrDRIGetDeviceInfo): in all those functions, don't forward the screen number we receive - from the client - to the host X. We (Xephyr) are always targetting the same X display screen, which is the one Xephyr got launched against. So we enforce that in the code. * hw/kdrive/ephyr/ephyrdriext.c: (EphyrMirrorHostVisuals): make this duplicate the visuals of the host X default screen into a given Xephyr screen. This way we have a chance to update the visuals of all Xephyr screen to make them mirror those of the host X. (many other places): specify screen number where required by the api change in hostx.h. * hw/kdrive/ephyr/ephyrglxext.c: specify screen number where required by the api change in hostx.h * hw/kdrive/ephyr/ephyrhostglx.c: don't forward the screen number we receive - from the client - to the host X. We (Xephyr) are always targetting the same X display screen, which is the one Xephyr got launched against. So we enforce that in the code. * hw/kdrive/ephyr/ephyrhostvideo.c,h: take in account the screen number received from the client app. This is useful to know on which Xephyr screen we need to display video stuff. * hw/kdrive/ephyr/ephyrvideo.c: update this to reflect the API change in hw/kdrive/ephyr/ephyrhostvideo.h. (ephyrSetPortAttribute): when parameters are not valid - they exceed their validity range - send them to the host anyway and do not return an error to clients. Some host expose buggy validity range, so rejecting client for that is too harsh.
2007-10-03 13:03:34 +02:00
/*
res = BadMatch ;
goto out ;
Xephyr: port XV/GL stuff of the new multiscreen architecture We can now launch GL or XV apps in any of the Xephyr screens we want. * hw/kdrive/ephyr/hostx.c,h: (hostx_get_window): (hostx_create_window): make these functions be screen number aware. * hw/kdrive/ephyr/XF86dri.c : fix some compiler warnings. * hw/kdrive/ephyr/ephyrdri.c: (ephyrDRIQueryDirectRenderingCapable), (ephyrDRIOpenConnection), (ephyrDRIAuthConnection), (ephyrDRICloseConnection), (ephyrDRIGetClientDriverName), (ephyrDRICreateContext), (ephyrDRIDestroyContext), (ephyrDRICreateDrawable), (ephyrDRIGetDrawableInfo), (ephyrDRIGetDeviceInfo): in all those functions, don't forward the screen number we receive - from the client - to the host X. We (Xephyr) are always targetting the same X display screen, which is the one Xephyr got launched against. So we enforce that in the code. * hw/kdrive/ephyr/ephyrdriext.c: (EphyrMirrorHostVisuals): make this duplicate the visuals of the host X default screen into a given Xephyr screen. This way we have a chance to update the visuals of all Xephyr screen to make them mirror those of the host X. (many other places): specify screen number where required by the api change in hostx.h. * hw/kdrive/ephyr/ephyrglxext.c: specify screen number where required by the api change in hostx.h * hw/kdrive/ephyr/ephyrhostglx.c: don't forward the screen number we receive - from the client - to the host X. We (Xephyr) are always targetting the same X display screen, which is the one Xephyr got launched against. So we enforce that in the code. * hw/kdrive/ephyr/ephyrhostvideo.c,h: take in account the screen number received from the client app. This is useful to know on which Xephyr screen we need to display video stuff. * hw/kdrive/ephyr/ephyrvideo.c: update this to reflect the API change in hw/kdrive/ephyr/ephyrhostvideo.h. (ephyrSetPortAttribute): when parameters are not valid - they exceed their validity range - send them to the host anyway and do not return an error to clients. Some host expose buggy validity range, so rejecting client for that is too harsh.
2007-10-03 13:03:34 +02:00
*/
}
if (!is_attr_valid) {
EPHYR_LOG_ERROR("attribute %s is not valid\n",
NameForAtom(a_attr_name));
Xephyr: port XV/GL stuff of the new multiscreen architecture We can now launch GL or XV apps in any of the Xephyr screens we want. * hw/kdrive/ephyr/hostx.c,h: (hostx_get_window): (hostx_create_window): make these functions be screen number aware. * hw/kdrive/ephyr/XF86dri.c : fix some compiler warnings. * hw/kdrive/ephyr/ephyrdri.c: (ephyrDRIQueryDirectRenderingCapable), (ephyrDRIOpenConnection), (ephyrDRIAuthConnection), (ephyrDRICloseConnection), (ephyrDRIGetClientDriverName), (ephyrDRICreateContext), (ephyrDRIDestroyContext), (ephyrDRICreateDrawable), (ephyrDRIGetDrawableInfo), (ephyrDRIGetDeviceInfo): in all those functions, don't forward the screen number we receive - from the client - to the host X. We (Xephyr) are always targetting the same X display screen, which is the one Xephyr got launched against. So we enforce that in the code. * hw/kdrive/ephyr/ephyrdriext.c: (EphyrMirrorHostVisuals): make this duplicate the visuals of the host X default screen into a given Xephyr screen. This way we have a chance to update the visuals of all Xephyr screen to make them mirror those of the host X. (many other places): specify screen number where required by the api change in hostx.h. * hw/kdrive/ephyr/ephyrglxext.c: specify screen number where required by the api change in hostx.h * hw/kdrive/ephyr/ephyrhostglx.c: don't forward the screen number we receive - from the client - to the host X. We (Xephyr) are always targetting the same X display screen, which is the one Xephyr got launched against. So we enforce that in the code. * hw/kdrive/ephyr/ephyrhostvideo.c,h: take in account the screen number received from the client app. This is useful to know on which Xephyr screen we need to display video stuff. * hw/kdrive/ephyr/ephyrvideo.c: update this to reflect the API change in hw/kdrive/ephyr/ephyrhostvideo.h. (ephyrSetPortAttribute): when parameters are not valid - they exceed their validity range - send them to the host anyway and do not return an error to clients. Some host expose buggy validity range, so rejecting client for that is too harsh.
2007-10-03 13:03:34 +02:00
/*
res = BadMatch ;
goto out ;
*/
}
if (!ephyrHostXVSetPortAttribute(port_priv->port_number,
host_atom, a_attr_value)) {
EPHYR_LOG_ERROR("failed to set port attribute\n");
res = BadMatch;
goto out;
}
res = Success;
out:
EPHYR_LOG("leave\n");
return res;
2007-07-21 12:08:39 +02:00
}
static int
ephyrGetPortAttribute(KdScreenInfo * a_screen_info,
Atom a_attr_name, int *a_attr_value, pointer a_port_priv)
2007-07-21 12:08:39 +02:00
{
int res = Success, host_atom = 0;
EphyrPortPriv *port_priv = a_port_priv;
EPHYR_RETURN_VAL_IF_FAIL(port_priv, BadMatch);
EPHYR_RETURN_VAL_IF_FAIL(ValidAtom(a_attr_name), BadMatch);
EPHYR_LOG("enter, portnum:%d, atomid:%d, attr_name:%s\n",
port_priv->port_number,
(int) a_attr_name, NameForAtom(a_attr_name));
if (!ephyrLocalAtomToHost(a_attr_name, &host_atom)) {
EPHYR_LOG_ERROR("failed to convert local atom to host atom\n");
res = BadMatch;
goto out;
}
if (!ephyrHostXVGetPortAttribute(port_priv->port_number,
host_atom, a_attr_value)) {
EPHYR_LOG_ERROR("failed to get port attribute\n");
res = BadMatch;
goto out;
}
res = Success;
out:
EPHYR_LOG("leave\n");
return res;
2007-07-21 12:08:39 +02:00
}
static void
ephyrQueryBestSize(KdScreenInfo * a_info,
Bool a_motion,
short a_src_w,
short a_src_h,
short a_drw_w,
short a_drw_h,
unsigned int *a_prefered_w,
unsigned int *a_prefered_h, pointer a_port_priv)
2007-07-21 12:08:39 +02:00
{
int res = 0;
EphyrPortPriv *port_priv = a_port_priv;
EPHYR_RETURN_IF_FAIL(port_priv);
EPHYR_LOG("enter\n");
res = ephyrHostXVQueryBestSize(port_priv->port_number,
a_motion,
a_src_w, a_src_h,
a_drw_w, a_drw_h,
a_prefered_w, a_prefered_h);
if (!res) {
EPHYR_LOG_ERROR("Failed to query best size\n");
}
EPHYR_LOG("leave\n");
2007-07-21 12:08:39 +02:00
}
static int
ephyrPutImage(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_src_x,
short a_src_y,
short a_drw_x,
short a_drw_y,
short a_src_w,
short a_src_h,
short a_drw_w,
short a_drw_h,
int a_id,
unsigned char *a_buf,
short a_width,
short a_height,
Bool a_sync, RegionPtr a_clipping_region, pointer a_port_priv)
{
EphyrPortPriv *port_priv = a_port_priv;
Bool is_ok = FALSE;
int result = BadImplementation, image_size = 0;
EPHYR_RETURN_VAL_IF_FAIL(a_info && a_info->pScreen, BadValue);
EPHYR_RETURN_VAL_IF_FAIL(a_drawable, BadValue);
EPHYR_LOG("enter\n");
if (!ephyrHostXVPutImage(a_info->pScreen->myNum,
port_priv->port_number,
a_id,
a_drw_x, a_drw_y, a_drw_w, a_drw_h,
a_src_x, a_src_y, a_src_w, a_src_h,
a_width, a_height, a_buf,
(EphyrHostBox *) RegionRects(a_clipping_region),
RegionNumRects(a_clipping_region))) {
EPHYR_LOG_ERROR("EphyrHostXVPutImage() failed\n");
goto out;
}
/*
* Now save the image so that we can resend it to host it
* later, in ReputImage.
*/
if (!ephyrXVPrivGetImageBufSize(port_priv->port_number,
a_id, a_width, a_height, &image_size)) {
EPHYR_LOG_ERROR("failed to get image size\n");
/*this is a minor error so we won't get bail out abruptly */
is_ok = FALSE;
}
else {
is_ok = TRUE;
}
if (is_ok) {
if (!ephyrXVPrivSaveImageToPortPriv(port_priv, a_buf, image_size)) {
is_ok = FALSE;
}
else {
port_priv->image_id = a_id;
port_priv->drw_x = a_drw_x;
port_priv->drw_y = a_drw_y;
port_priv->drw_w = a_drw_w;
port_priv->drw_h = a_drw_h;
port_priv->src_x = a_src_x;
port_priv->src_y = a_src_y;
port_priv->src_w = a_src_w;
port_priv->src_h = a_src_h;
port_priv->image_width = a_width;
port_priv->image_height = a_height;
}
}
if (!is_ok) {
if (port_priv->image_buf) {
free(port_priv->image_buf);
port_priv->image_buf = NULL;
port_priv->image_buf_size = 0;
}
}
result = Success;
out:
EPHYR_LOG("leave\n");
return result;
}
static int
ephyrReputImage(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_drw_x,
short a_drw_y, RegionPtr a_clipping_region, pointer a_port_priv)
{
EphyrPortPriv *port_priv = a_port_priv;
int result = BadImplementation;
EPHYR_RETURN_VAL_IF_FAIL(a_info->pScreen, FALSE);
EPHYR_RETURN_VAL_IF_FAIL(a_drawable && port_priv, BadValue);
EPHYR_LOG("enter\n");
if (!port_priv->image_buf_size || !port_priv->image_buf) {
EPHYR_LOG_ERROR("has null image buf in cache\n");
goto out;
}
if (!ephyrHostXVPutImage(a_info->pScreen->myNum,
port_priv->port_number,
port_priv->image_id,
a_drw_x, a_drw_y,
port_priv->drw_w, port_priv->drw_h,
port_priv->src_x, port_priv->src_y,
port_priv->src_w, port_priv->src_h,
port_priv->image_width, port_priv->image_height,
port_priv->image_buf,
(EphyrHostBox *) RegionRects(a_clipping_region),
RegionNumRects(a_clipping_region))) {
EPHYR_LOG_ERROR("ephyrHostXVPutImage() failed\n");
goto out;
}
result = Success;
out:
EPHYR_LOG("leave\n");
return result;
}
static int
ephyrPutVideo(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_vid_x, short a_vid_y,
short a_drw_x, short a_drw_y,
short a_vid_w, short a_vid_h,
short a_drw_w, short a_drw_h,
RegionPtr a_clipping_region, pointer a_port_priv)
{
EphyrPortPriv *port_priv = a_port_priv;
BoxRec clipped_area, dst_box;
int result = BadImplementation;
EPHYR_RETURN_VAL_IF_FAIL(a_info->pScreen, BadValue);
EPHYR_RETURN_VAL_IF_FAIL(a_drawable && port_priv, BadValue);
EPHYR_LOG("enter\n");
dst_box.x1 = a_drw_x;
dst_box.x2 = a_drw_x + a_drw_w;
dst_box.y1 = a_drw_y;
dst_box.y2 = a_drw_y + a_drw_h;
if (!DoSimpleClip(&dst_box,
RegionExtents(a_clipping_region), &clipped_area)) {
EPHYR_LOG_ERROR("failed to simple clip\n");
goto out;
}
if (!ephyrHostXVPutVideo(a_info->pScreen->myNum,
port_priv->port_number,
a_vid_x, a_vid_y, a_vid_w, a_vid_h,
a_drw_x, a_drw_y, a_drw_w, a_drw_h)) {
EPHYR_LOG_ERROR("ephyrHostXVPutVideo() failed\n");
goto out;
}
result = Success;
out:
EPHYR_LOG("leave\n");
return result;
}
static int
ephyrGetVideo(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_vid_x, short a_vid_y,
short a_drw_x, short a_drw_y,
short a_vid_w, short a_vid_h,
short a_drw_w, short a_drw_h,
RegionPtr a_clipping_region, pointer a_port_priv)
{
EphyrPortPriv *port_priv = a_port_priv;
BoxRec clipped_area, dst_box;
int result = BadImplementation;
EPHYR_RETURN_VAL_IF_FAIL(a_info && a_info->pScreen, BadValue);
EPHYR_RETURN_VAL_IF_FAIL(a_drawable && port_priv, BadValue);
EPHYR_LOG("enter\n");
dst_box.x1 = a_drw_x;
dst_box.x2 = a_drw_x + a_drw_w;
dst_box.y1 = a_drw_y;
dst_box.y2 = a_drw_y + a_drw_h;
if (!DoSimpleClip(&dst_box,
RegionExtents(a_clipping_region), &clipped_area)) {
EPHYR_LOG_ERROR("failed to simple clip\n");
goto out;
}
if (!ephyrHostXVGetVideo(a_info->pScreen->myNum,
port_priv->port_number,
a_vid_x, a_vid_y, a_vid_w, a_vid_h,
a_drw_x, a_drw_y, a_drw_w, a_drw_h)) {
EPHYR_LOG_ERROR("ephyrHostXVGetVideo() failed\n");
goto out;
}
result = Success;
out:
EPHYR_LOG("leave\n");
return result;
}
2007-07-21 12:08:39 +02:00
static int
ephyrPutStill(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_vid_x, short a_vid_y,
short a_drw_x, short a_drw_y,
short a_vid_w, short a_vid_h,
short a_drw_w, short a_drw_h,
RegionPtr a_clipping_region, pointer a_port_priv)
2007-07-21 12:08:39 +02:00
{
EphyrPortPriv *port_priv = a_port_priv;
BoxRec clipped_area, dst_box;
int result = BadImplementation;
EPHYR_RETURN_VAL_IF_FAIL(a_info && a_info->pScreen, BadValue);
EPHYR_RETURN_VAL_IF_FAIL(a_drawable && port_priv, BadValue);
EPHYR_LOG("enter\n");
dst_box.x1 = a_drw_x;
dst_box.x2 = a_drw_x + a_drw_w;
dst_box.y1 = a_drw_y;
dst_box.y2 = a_drw_y + a_drw_h;
if (!DoSimpleClip(&dst_box,
RegionExtents(a_clipping_region), &clipped_area)) {
EPHYR_LOG_ERROR("failed to simple clip\n");
goto out;
}
if (!ephyrHostXVPutStill(a_info->pScreen->myNum,
port_priv->port_number,
a_vid_x, a_vid_y, a_vid_w, a_vid_h,
a_drw_x, a_drw_y, a_drw_w, a_drw_h)) {
EPHYR_LOG_ERROR("ephyrHostXVPutStill() failed\n");
goto out;
}
result = Success;
out:
EPHYR_LOG("leave\n");
return result;
}
static int
ephyrGetStill(KdScreenInfo * a_info,
DrawablePtr a_drawable,
short a_vid_x, short a_vid_y,
short a_drw_x, short a_drw_y,
short a_vid_w, short a_vid_h,
short a_drw_w, short a_drw_h,
RegionPtr a_clipping_region, pointer a_port_priv)
{
EphyrPortPriv *port_priv = a_port_priv;
BoxRec clipped_area, dst_box;
int result = BadImplementation;
EPHYR_RETURN_VAL_IF_FAIL(a_info && a_info->pScreen, BadValue);
EPHYR_RETURN_VAL_IF_FAIL(a_drawable && port_priv, BadValue);
EPHYR_LOG("enter\n");
dst_box.x1 = a_drw_x;
dst_box.x2 = a_drw_x + a_drw_w;
dst_box.y1 = a_drw_y;
dst_box.y2 = a_drw_y + a_drw_h;
if (!DoSimpleClip(&dst_box,
RegionExtents(a_clipping_region), &clipped_area)) {
EPHYR_LOG_ERROR("failed to simple clip\n");
goto out;
}
if (!ephyrHostXVGetStill(a_info->pScreen->myNum,
port_priv->port_number,
a_vid_x, a_vid_y, a_vid_w, a_vid_h,
a_drw_x, a_drw_y, a_drw_w, a_drw_h)) {
EPHYR_LOG_ERROR("ephyrHostXVGetStill() failed\n");
goto out;
}
result = Success;
out:
EPHYR_LOG("leave\n");
return result;
2007-07-21 12:08:39 +02:00
}
static int
ephyrQueryImageAttributes(KdScreenInfo * a_info,
int a_id,
unsigned short *a_w,
unsigned short *a_h, int *a_pitches, int *a_offsets)
2007-07-21 12:08:39 +02:00
{
int image_size = 0;
EPHYR_RETURN_VAL_IF_FAIL(a_w && a_h, FALSE);
EPHYR_LOG("enter: dim (%dx%d), pitches: %p, offsets: %p\n",
*a_w, *a_h, a_pitches, a_offsets);
if (!ephyrHostXVQueryImageAttributes(s_base_port_id,
a_id,
a_w, a_h,
&image_size, a_pitches, a_offsets)) {
EPHYR_LOG_ERROR("EphyrHostXVQueryImageAttributes() failed\n");
goto out;
}
EPHYR_LOG("image size: %d, dim (%dx%d)\n", image_size, *a_w, *a_h);
out:
EPHYR_LOG("leave\n");
return image_size;
2007-07-21 12:08:39 +02:00
}