2006-11-26 11:13:41 -07:00
|
|
|
/*
|
|
|
|
|
|
|
|
Copyright 1993, 1998 The Open Group
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice 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 OPEN GROUP 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.
|
|
|
|
|
|
|
|
Except as contained in this notice, the name of The Open Group shall
|
|
|
|
not be used in advertising or otherwise to promote the sale, use or
|
|
|
|
other dealings in this Software without prior written authorization
|
|
|
|
from The Open Group.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_DIX_CONFIG_H
|
|
|
|
#include <dix-config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <X11/X.h>
|
|
|
|
#include "scrnintstr.h"
|
2019-12-11 23:05:17 -07:00
|
|
|
#include "mi.h"
|
2006-11-26 11:13:41 -07:00
|
|
|
#include "misc.h"
|
|
|
|
#include "os.h"
|
|
|
|
#include "windowstr.h"
|
|
|
|
#include "resource.h"
|
|
|
|
#include "dixstruct.h"
|
|
|
|
#include "gcstruct.h"
|
|
|
|
#include "servermd.h"
|
|
|
|
#include "site.h"
|
2016-05-29 06:02:34 -06:00
|
|
|
#include "X11/extensions/render.h"
|
|
|
|
#include "picturestr.h"
|
|
|
|
#include "randrstr.h"
|
2006-11-26 11:13:41 -07:00
|
|
|
/*
|
|
|
|
* Scratch pixmap management and device independent pixmap allocation
|
|
|
|
* function.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* callable by ddx */
|
2010-07-27 13:02:24 -06:00
|
|
|
PixmapPtr
|
2012-06-10 07:21:05 -06:00
|
|
|
GetScratchPixmapHeader(ScreenPtr pScreen, int width, int height, int depth,
|
2014-09-27 11:52:59 -06:00
|
|
|
int bitsPerPixel, int devKind, void *pPixData)
|
2006-11-26 11:13:41 -07:00
|
|
|
{
|
|
|
|
PixmapPtr pPixmap = pScreen->pScratchPixmap;
|
|
|
|
|
|
|
|
if (pPixmap)
|
2012-06-10 07:21:05 -06:00
|
|
|
pScreen->pScratchPixmap = NULL;
|
2006-11-26 11:13:41 -07:00
|
|
|
else
|
2012-06-10 07:21:05 -06:00
|
|
|
/* width and height of 0 means don't allocate any pixmap data */
|
|
|
|
pPixmap = (*pScreen->CreatePixmap) (pScreen, 0, 0, depth, 0);
|
2006-11-26 11:13:41 -07:00
|
|
|
|
|
|
|
if (pPixmap) {
|
2012-06-10 07:21:05 -06:00
|
|
|
if ((*pScreen->ModifyPixmapHeader) (pPixmap, width, height, depth,
|
|
|
|
bitsPerPixel, devKind, pPixData))
|
|
|
|
return pPixmap;
|
|
|
|
(*pScreen->DestroyPixmap) (pPixmap);
|
2006-11-26 11:13:41 -07:00
|
|
|
}
|
|
|
|
return NullPixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* callable by ddx */
|
2010-07-27 13:02:24 -06:00
|
|
|
void
|
2006-11-26 11:13:41 -07:00
|
|
|
FreeScratchPixmapHeader(PixmapPtr pPixmap)
|
|
|
|
{
|
2012-06-10 07:21:05 -06:00
|
|
|
if (pPixmap) {
|
|
|
|
ScreenPtr pScreen = pPixmap->drawable.pScreen;
|
|
|
|
|
|
|
|
pPixmap->devPrivate.ptr = NULL; /* lest ddx chases bad ptr */
|
|
|
|
if (pScreen->pScratchPixmap)
|
|
|
|
(*pScreen->DestroyPixmap) (pPixmap);
|
|
|
|
else
|
|
|
|
pScreen->pScratchPixmap = pPixmap;
|
2006-11-26 11:13:41 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Bool
|
2013-06-07 11:28:45 -06:00
|
|
|
CreateScratchPixmapsForScreen(ScreenPtr pScreen)
|
2006-11-26 11:13:41 -07:00
|
|
|
{
|
2012-06-10 07:21:05 -06:00
|
|
|
unsigned int pixmap_size;
|
2010-12-05 08:36:02 -07:00
|
|
|
|
2013-06-07 11:28:45 -06:00
|
|
|
pixmap_size = sizeof(PixmapRec) + dixScreenSpecificPrivatesSize(pScreen, PRIVATE_PIXMAP);
|
|
|
|
pScreen->totalPixmapSize =
|
2012-06-10 07:21:05 -06:00
|
|
|
BitmapBytePad(pixmap_size * 8);
|
2010-12-05 08:36:02 -07:00
|
|
|
|
2006-11-26 11:13:41 -07:00
|
|
|
/* let it be created on first use */
|
2013-06-07 11:28:45 -06:00
|
|
|
pScreen->pScratchPixmap = NULL;
|
2006-11-26 11:13:41 -07:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2013-06-07 11:28:45 -06:00
|
|
|
FreeScratchPixmapsForScreen(ScreenPtr pScreen)
|
2006-11-26 11:13:41 -07:00
|
|
|
{
|
2013-06-07 11:28:45 -06:00
|
|
|
FreeScratchPixmapHeader(pScreen->pScratchPixmap);
|
2006-11-26 11:13:41 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* callable by ddx */
|
2010-07-27 13:02:24 -06:00
|
|
|
PixmapPtr
|
2006-11-26 11:13:41 -07:00
|
|
|
AllocatePixmap(ScreenPtr pScreen, int pixDataSize)
|
|
|
|
{
|
|
|
|
PixmapPtr pPixmap;
|
|
|
|
|
2010-12-05 08:36:02 -07:00
|
|
|
assert(pScreen->totalPixmapSize > 0);
|
|
|
|
|
2012-06-10 07:21:05 -06:00
|
|
|
if (pScreen->totalPixmapSize > ((size_t) - 1) - pixDataSize)
|
|
|
|
return NullPixmap;
|
|
|
|
|
2020-07-31 08:00:21 -06:00
|
|
|
pPixmap = calloc(1, pScreen->totalPixmapSize + pixDataSize);
|
2006-11-26 11:13:41 -07:00
|
|
|
if (!pPixmap)
|
2012-06-10 07:21:05 -06:00
|
|
|
return NullPixmap;
|
2006-11-26 11:13:41 -07:00
|
|
|
|
2013-06-07 11:28:45 -06:00
|
|
|
dixInitScreenPrivates(pScreen, pPixmap, pPixmap + 1, PRIVATE_PIXMAP);
|
2006-11-26 11:13:41 -07:00
|
|
|
return pPixmap;
|
|
|
|
}
|
2010-12-05 08:36:02 -07:00
|
|
|
|
|
|
|
/* callable by ddx */
|
|
|
|
void
|
|
|
|
FreePixmap(PixmapPtr pPixmap)
|
|
|
|
{
|
|
|
|
dixFiniPrivates(pPixmap, PRIVATE_PIXMAP);
|
|
|
|
free(pPixmap);
|
|
|
|
}
|
2013-06-07 11:28:45 -06:00
|
|
|
|
2017-12-08 08:01:59 -07:00
|
|
|
void PixmapUnshareSlavePixmap(PixmapPtr slave_pixmap)
|
|
|
|
{
|
|
|
|
int ihandle = -1;
|
|
|
|
ScreenPtr pScreen = slave_pixmap->drawable.pScreen;
|
|
|
|
pScreen->SetSharedPixmapBacking(slave_pixmap, ((void *)(long)ihandle));
|
|
|
|
}
|
|
|
|
|
2013-06-07 11:28:45 -06:00
|
|
|
PixmapPtr PixmapShareToSlave(PixmapPtr pixmap, ScreenPtr slave)
|
|
|
|
{
|
|
|
|
PixmapPtr spix;
|
|
|
|
int ret;
|
|
|
|
void *handle;
|
|
|
|
ScreenPtr master = pixmap->drawable.pScreen;
|
|
|
|
int depth = pixmap->drawable.depth;
|
|
|
|
|
|
|
|
ret = master->SharePixmapBacking(pixmap, slave, &handle);
|
|
|
|
if (ret == FALSE)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
spix = slave->CreatePixmap(slave, 0, 0, depth,
|
|
|
|
CREATE_PIXMAP_USAGE_SHARED);
|
|
|
|
slave->ModifyPixmapHeader(spix, pixmap->drawable.width,
|
|
|
|
pixmap->drawable.height, depth, 0,
|
|
|
|
pixmap->devKind, NULL);
|
|
|
|
|
|
|
|
/* have the slave pixmap take a reference on the master pixmap
|
|
|
|
later we destroy them both at the same time */
|
|
|
|
pixmap->refcnt++;
|
|
|
|
|
|
|
|
spix->master_pixmap = pixmap;
|
|
|
|
|
|
|
|
ret = slave->SetSharedPixmapBacking(spix, handle);
|
|
|
|
if (ret == FALSE) {
|
|
|
|
slave->DestroyPixmap(spix);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return spix;
|
|
|
|
}
|
|
|
|
|
2017-12-08 08:01:59 -07:00
|
|
|
static void
|
|
|
|
PixmapDirtyDamageDestroy(DamagePtr damage, void *closure)
|
|
|
|
{
|
|
|
|
PixmapDirtyUpdatePtr dirty = closure;
|
|
|
|
|
|
|
|
dirty->damage = NULL;
|
|
|
|
}
|
|
|
|
|
2013-06-07 11:28:45 -06:00
|
|
|
Bool
|
2019-07-27 01:57:06 -06:00
|
|
|
PixmapStartDirtyTracking(DrawablePtr src,
|
2016-05-29 06:02:34 -06:00
|
|
|
PixmapPtr slave_dst,
|
|
|
|
int x, int y, int dst_x, int dst_y,
|
|
|
|
Rotation rotation)
|
2013-06-07 11:28:45 -06:00
|
|
|
{
|
2019-07-27 01:57:06 -06:00
|
|
|
ScreenPtr screen = src->pScreen;
|
2013-06-07 11:28:45 -06:00
|
|
|
PixmapDirtyUpdatePtr dirty_update;
|
2016-05-29 06:02:34 -06:00
|
|
|
RegionPtr damageregion;
|
|
|
|
RegionRec dstregion;
|
|
|
|
BoxRec box;
|
2013-06-07 11:28:45 -06:00
|
|
|
|
|
|
|
dirty_update = calloc(1, sizeof(PixmapDirtyUpdateRec));
|
|
|
|
if (!dirty_update)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
dirty_update->src = src;
|
|
|
|
dirty_update->slave_dst = slave_dst;
|
|
|
|
dirty_update->x = x;
|
|
|
|
dirty_update->y = y;
|
2015-09-16 13:10:19 -06:00
|
|
|
dirty_update->dst_x = dst_x;
|
|
|
|
dirty_update->dst_y = dst_y;
|
2016-05-29 06:02:34 -06:00
|
|
|
dirty_update->rotation = rotation;
|
2017-12-08 08:01:59 -07:00
|
|
|
dirty_update->damage = DamageCreate(NULL, PixmapDirtyDamageDestroy,
|
2019-07-27 01:57:06 -06:00
|
|
|
DamageReportNone, TRUE, screen,
|
2017-12-08 08:01:59 -07:00
|
|
|
dirty_update);
|
2016-05-29 06:02:34 -06:00
|
|
|
|
|
|
|
if (rotation != RR_Rotate_0) {
|
|
|
|
RRTransformCompute(x, y,
|
|
|
|
slave_dst->drawable.width,
|
|
|
|
slave_dst->drawable.height,
|
|
|
|
rotation,
|
|
|
|
NULL,
|
|
|
|
&dirty_update->transform,
|
|
|
|
&dirty_update->f_transform,
|
|
|
|
&dirty_update->f_inverse);
|
|
|
|
}
|
2013-06-07 11:28:45 -06:00
|
|
|
if (!dirty_update->damage) {
|
|
|
|
free(dirty_update);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2016-05-29 06:02:34 -06:00
|
|
|
/* Damage destination rectangle so that the destination pixmap contents
|
|
|
|
* will get fully initialized
|
|
|
|
*/
|
|
|
|
box.x1 = dirty_update->x;
|
|
|
|
box.y1 = dirty_update->y;
|
|
|
|
if (dirty_update->rotation == RR_Rotate_90 ||
|
|
|
|
dirty_update->rotation == RR_Rotate_270) {
|
|
|
|
box.x2 = dirty_update->x + slave_dst->drawable.height;
|
|
|
|
box.y2 = dirty_update->y + slave_dst->drawable.width;
|
|
|
|
} else {
|
|
|
|
box.x2 = dirty_update->x + slave_dst->drawable.width;
|
|
|
|
box.y2 = dirty_update->y + slave_dst->drawable.height;
|
|
|
|
}
|
|
|
|
RegionInit(&dstregion, &box, 1);
|
|
|
|
damageregion = DamageRegion(dirty_update->damage);
|
|
|
|
RegionUnion(damageregion, damageregion, &dstregion);
|
|
|
|
RegionUninit(&dstregion);
|
|
|
|
|
2019-07-27 01:57:06 -06:00
|
|
|
DamageRegister(src, dirty_update->damage);
|
2013-06-07 11:28:45 -06:00
|
|
|
xorg_list_add(&dirty_update->ent, &screen->pixmap_dirty_list);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
Bool
|
2019-07-27 01:57:06 -06:00
|
|
|
PixmapStopDirtyTracking(DrawablePtr src, PixmapPtr slave_dst)
|
2013-06-07 11:28:45 -06:00
|
|
|
{
|
2019-07-27 01:57:06 -06:00
|
|
|
ScreenPtr screen = src->pScreen;
|
2013-06-07 11:28:45 -06:00
|
|
|
PixmapDirtyUpdatePtr ent, safe;
|
|
|
|
|
|
|
|
xorg_list_for_each_entry_safe(ent, safe, &screen->pixmap_dirty_list, ent) {
|
|
|
|
if (ent->src == src && ent->slave_dst == slave_dst) {
|
2017-12-08 08:01:59 -07:00
|
|
|
if (ent->damage)
|
|
|
|
DamageDestroy(ent->damage);
|
2013-06-07 11:28:45 -06:00
|
|
|
xorg_list_del(&ent->ent);
|
|
|
|
free(ent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2016-05-29 06:02:34 -06:00
|
|
|
static void
|
|
|
|
PixmapDirtyCopyArea(PixmapPtr dst,
|
|
|
|
PixmapDirtyUpdatePtr dirty,
|
|
|
|
RegionPtr dirty_region)
|
2013-06-07 11:28:45 -06:00
|
|
|
{
|
2019-07-27 01:57:06 -06:00
|
|
|
DrawablePtr src = dirty->src;
|
|
|
|
ScreenPtr pScreen = src->pScreen;
|
2013-06-07 11:28:45 -06:00
|
|
|
int n;
|
|
|
|
BoxPtr b;
|
|
|
|
GCPtr pGC;
|
|
|
|
|
|
|
|
n = RegionNumRects(dirty_region);
|
|
|
|
b = RegionRects(dirty_region);
|
|
|
|
|
2019-07-27 01:57:06 -06:00
|
|
|
pGC = GetScratchGC(src->depth, pScreen);
|
|
|
|
if (pScreen->root) {
|
|
|
|
ChangeGCVal subWindowMode;
|
|
|
|
|
|
|
|
subWindowMode.val = IncludeInferiors;
|
|
|
|
ChangeGC(NullClient, pGC, GCSubwindowMode, &subWindowMode);
|
|
|
|
}
|
2013-06-07 11:28:45 -06:00
|
|
|
ValidateGC(&dst->drawable, pGC);
|
|
|
|
|
|
|
|
while (n--) {
|
|
|
|
BoxRec dst_box;
|
|
|
|
int w, h;
|
|
|
|
|
|
|
|
dst_box = *b;
|
|
|
|
w = dst_box.x2 - dst_box.x1;
|
|
|
|
h = dst_box.y2 - dst_box.y1;
|
|
|
|
|
2019-07-27 01:57:06 -06:00
|
|
|
pGC->ops->CopyArea(src, &dst->drawable, pGC,
|
2016-05-29 06:02:34 -06:00
|
|
|
dirty->x + dst_box.x1, dirty->y + dst_box.y1, w, h,
|
|
|
|
dirty->dst_x + dst_box.x1,
|
|
|
|
dirty->dst_y + dst_box.y1);
|
2013-06-07 11:28:45 -06:00
|
|
|
b++;
|
|
|
|
}
|
|
|
|
FreeScratchGC(pGC);
|
2016-05-29 06:02:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
PixmapDirtyCompositeRotate(PixmapPtr dst_pixmap,
|
|
|
|
PixmapDirtyUpdatePtr dirty,
|
|
|
|
RegionPtr dirty_region)
|
|
|
|
{
|
2019-07-27 01:57:06 -06:00
|
|
|
ScreenPtr pScreen = dirty->src->pScreen;
|
2016-05-29 06:02:34 -06:00
|
|
|
PictFormatPtr format = PictureWindowFormat(pScreen->root);
|
|
|
|
PicturePtr src, dst;
|
|
|
|
XID include_inferiors = IncludeInferiors;
|
|
|
|
int n = RegionNumRects(dirty_region);
|
|
|
|
BoxPtr b = RegionRects(dirty_region);
|
|
|
|
int error;
|
|
|
|
|
|
|
|
src = CreatePicture(None,
|
2019-07-27 01:57:06 -06:00
|
|
|
dirty->src,
|
2016-05-29 06:02:34 -06:00
|
|
|
format,
|
|
|
|
CPSubwindowMode,
|
|
|
|
&include_inferiors, serverClient, &error);
|
|
|
|
if (!src)
|
|
|
|
return;
|
|
|
|
|
|
|
|
dst = CreatePicture(None,
|
|
|
|
&dst_pixmap->drawable,
|
|
|
|
format, 0L, NULL, serverClient, &error);
|
|
|
|
if (!dst)
|
|
|
|
return;
|
|
|
|
|
|
|
|
error = SetPictureTransform(src, &dirty->transform);
|
|
|
|
if (error)
|
|
|
|
return;
|
|
|
|
while (n--) {
|
|
|
|
BoxRec dst_box;
|
2013-06-07 11:28:45 -06:00
|
|
|
|
2016-05-29 06:02:34 -06:00
|
|
|
dst_box = *b;
|
|
|
|
dst_box.x1 += dirty->x;
|
|
|
|
dst_box.x2 += dirty->x;
|
|
|
|
dst_box.y1 += dirty->y;
|
|
|
|
dst_box.y2 += dirty->y;
|
|
|
|
pixman_f_transform_bounds(&dirty->f_inverse, &dst_box);
|
|
|
|
|
|
|
|
CompositePicture(PictOpSrc,
|
|
|
|
src, NULL, dst,
|
|
|
|
dst_box.x1,
|
|
|
|
dst_box.y1,
|
|
|
|
0, 0,
|
|
|
|
dst_box.x1,
|
|
|
|
dst_box.y1,
|
|
|
|
dst_box.x2 - dst_box.x1,
|
|
|
|
dst_box.y2 - dst_box.y1);
|
|
|
|
b++;
|
|
|
|
}
|
|
|
|
|
|
|
|
FreePicture(src, None);
|
|
|
|
FreePicture(dst, None);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this function can possibly be improved and optimised, by clipping
|
|
|
|
* instead of iterating
|
|
|
|
* Drivers are free to implement their own version of this.
|
|
|
|
*/
|
|
|
|
Bool PixmapSyncDirtyHelper(PixmapDirtyUpdatePtr dirty)
|
|
|
|
{
|
2019-07-27 01:57:06 -06:00
|
|
|
ScreenPtr pScreen = dirty->src->pScreen;
|
2016-05-29 06:02:34 -06:00
|
|
|
RegionPtr region = DamageRegion(dirty->damage);
|
|
|
|
PixmapPtr dst;
|
|
|
|
SourceValidateProcPtr SourceValidate;
|
|
|
|
RegionRec pixregion;
|
|
|
|
BoxRec box;
|
|
|
|
|
|
|
|
dst = dirty->slave_dst->master_pixmap;
|
|
|
|
if (!dst)
|
|
|
|
dst = dirty->slave_dst;
|
|
|
|
|
|
|
|
box.x1 = 0;
|
|
|
|
box.y1 = 0;
|
|
|
|
if (dirty->rotation == RR_Rotate_90 ||
|
|
|
|
dirty->rotation == RR_Rotate_270) {
|
|
|
|
box.x2 = dst->drawable.height;
|
|
|
|
box.y2 = dst->drawable.width;
|
|
|
|
} else {
|
|
|
|
box.x2 = dst->drawable.width;
|
|
|
|
box.y2 = dst->drawable.height;
|
|
|
|
}
|
|
|
|
RegionInit(&pixregion, &box, 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* SourceValidate is used by the software cursor code
|
|
|
|
* to pull the cursor off of the screen when reading
|
|
|
|
* bits from the frame buffer. Bypassing this function
|
|
|
|
* leaves the software cursor in place
|
|
|
|
*/
|
|
|
|
SourceValidate = pScreen->SourceValidate;
|
2019-12-11 23:05:17 -07:00
|
|
|
pScreen->SourceValidate = miSourceValidate;
|
2016-05-29 06:02:34 -06:00
|
|
|
|
|
|
|
RegionTranslate(&pixregion, dirty->x, dirty->y);
|
|
|
|
RegionIntersect(&pixregion, &pixregion, region);
|
|
|
|
|
|
|
|
if (RegionNil(&pixregion)) {
|
|
|
|
RegionUninit(&pixregion);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
RegionTranslate(&pixregion, -dirty->x, -dirty->y);
|
|
|
|
|
|
|
|
if (!pScreen->root || dirty->rotation == RR_Rotate_0)
|
|
|
|
PixmapDirtyCopyArea(dst, dirty, &pixregion);
|
|
|
|
else
|
|
|
|
PixmapDirtyCompositeRotate(dst, dirty, &pixregion);
|
2013-06-07 11:28:45 -06:00
|
|
|
pScreen->SourceValidate = SourceValidate;
|
|
|
|
return TRUE;
|
|
|
|
}
|