1706f2543Smrg/*
2706f2543Smrg *
3706f2543Smrg * Copyright © 2002 Keith Packard, member of The XFree86 Project, Inc.
4706f2543Smrg *
5706f2543Smrg * Permission to use, copy, modify, distribute, and sell this software and its
6706f2543Smrg * documentation for any purpose is hereby granted without fee, provided that
7706f2543Smrg * the above copyright notice appear in all copies and that both that
8706f2543Smrg * copyright notice and this permission notice appear in supporting
9706f2543Smrg * documentation, and that the name of Keith Packard not be used in
10706f2543Smrg * advertising or publicity pertaining to distribution of the software without
11706f2543Smrg * specific, written prior permission.  Keith Packard makes no
12706f2543Smrg * representations about the suitability of this software for any purpose.  It
13706f2543Smrg * is provided "as is" without express or implied warranty.
14706f2543Smrg *
15706f2543Smrg * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16706f2543Smrg * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17706f2543Smrg * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18706f2543Smrg * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
19706f2543Smrg * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20706f2543Smrg * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21706f2543Smrg * PERFORMANCE OF THIS SOFTWARE.
22706f2543Smrg */
23706f2543Smrg
24706f2543Smrg/*
25706f2543Smrg * Animated cursors for X.  Not specific to Render in any way, but
26706f2543Smrg * stuck there because Render has the other cool cursor extension.
27706f2543Smrg * Besides, everyone has Render.
28706f2543Smrg *
29706f2543Smrg * Implemented as a simple layer over the core cursor code; it
30706f2543Smrg * creates composite cursors out of a set of static cursors and
31706f2543Smrg * delta times between each image.
32706f2543Smrg */
33706f2543Smrg
34706f2543Smrg#ifdef HAVE_DIX_CONFIG_H
35706f2543Smrg#include <dix-config.h>
36706f2543Smrg#endif
37706f2543Smrg
38706f2543Smrg#include <X11/X.h>
39706f2543Smrg#include <X11/Xmd.h>
40706f2543Smrg#include "servermd.h"
41706f2543Smrg#include "scrnintstr.h"
42706f2543Smrg#include "dixstruct.h"
43706f2543Smrg#include "cursorstr.h"
44706f2543Smrg#include "dixfontstr.h"
45706f2543Smrg#include "opaque.h"
46706f2543Smrg#include "picturestr.h"
47706f2543Smrg#include "inputstr.h"
48706f2543Smrg#include "xace.h"
49706f2543Smrg
50706f2543Smrgtypedef struct _AnimCurElt {
51706f2543Smrg    CursorPtr	pCursor;    /* cursor to show */
52706f2543Smrg    CARD32	delay;	    /* in ms */
53706f2543Smrg} AnimCurElt;
54706f2543Smrg
55706f2543Smrgtypedef struct _AnimCur {
56706f2543Smrg    int		nelt;	    /* number of elements in the elts array */
57706f2543Smrg    AnimCurElt	*elts;	    /* actually allocated right after the structure */
58706f2543Smrg} AnimCurRec, *AnimCurPtr;
59706f2543Smrg
60706f2543Smrgtypedef struct _AnimScrPriv {
61706f2543Smrg    CloseScreenProcPtr		CloseScreen;
62706f2543Smrg
63706f2543Smrg    ScreenBlockHandlerProcPtr	BlockHandler;
64706f2543Smrg
65706f2543Smrg    CursorLimitsProcPtr		CursorLimits;
66706f2543Smrg    DisplayCursorProcPtr	DisplayCursor;
67706f2543Smrg    SetCursorPositionProcPtr	SetCursorPosition;
68706f2543Smrg    RealizeCursorProcPtr	RealizeCursor;
69706f2543Smrg    UnrealizeCursorProcPtr	UnrealizeCursor;
70706f2543Smrg    RecolorCursorProcPtr	RecolorCursor;
71706f2543Smrg} AnimCurScreenRec, *AnimCurScreenPtr;
72706f2543Smrg
73706f2543Smrgstatic unsigned char empty[4];
74706f2543Smrg
75706f2543Smrgstatic CursorBits   animCursorBits = {
76706f2543Smrg    empty, empty, 2, 1, 1, 0, 0, 1
77706f2543Smrg};
78706f2543Smrg
79706f2543Smrgstatic DevPrivateKeyRec AnimCurScreenPrivateKeyRec;
80706f2543Smrg#define AnimCurScreenPrivateKey (&AnimCurScreenPrivateKeyRec)
81706f2543Smrg
82706f2543Smrg#define IsAnimCur(c)	    ((c) && ((c)->bits == &animCursorBits))
83706f2543Smrg#define GetAnimCur(c)	    ((AnimCurPtr) ((((char *)(c) + CURSOR_REC_SIZE))))
84706f2543Smrg#define GetAnimCurScreen(s) ((AnimCurScreenPtr)dixLookupPrivate(&(s)->devPrivates, AnimCurScreenPrivateKey))
85706f2543Smrg#define SetAnimCurScreen(s,p) dixSetPrivate(&(s)->devPrivates, AnimCurScreenPrivateKey, p)
86706f2543Smrg
87706f2543Smrg#define Wrap(as,s,elt,func) (((as)->elt = (s)->elt), (s)->elt = func)
88706f2543Smrg#define Unwrap(as,s,elt)    ((s)->elt = (as)->elt)
89706f2543Smrg
90706f2543Smrg
91706f2543Smrgstatic Bool
92706f2543SmrgAnimCurCloseScreen (int index, ScreenPtr pScreen)
93706f2543Smrg{
94706f2543Smrg    AnimCurScreenPtr    as = GetAnimCurScreen(pScreen);
95706f2543Smrg    Bool                ret;
96706f2543Smrg
97706f2543Smrg    Unwrap(as, pScreen, CloseScreen);
98706f2543Smrg
99706f2543Smrg    Unwrap(as, pScreen, CursorLimits);
100706f2543Smrg    Unwrap(as, pScreen, DisplayCursor);
101706f2543Smrg    Unwrap(as, pScreen, SetCursorPosition);
102706f2543Smrg    Unwrap(as, pScreen, RealizeCursor);
103706f2543Smrg    Unwrap(as, pScreen, UnrealizeCursor);
104706f2543Smrg    Unwrap(as, pScreen, RecolorCursor);
105706f2543Smrg    SetAnimCurScreen(pScreen,0);
106706f2543Smrg    ret = (*pScreen->CloseScreen) (index, pScreen);
107706f2543Smrg    free(as);
108706f2543Smrg    return ret;
109706f2543Smrg}
110706f2543Smrg
111706f2543Smrgstatic void
112706f2543SmrgAnimCurCursorLimits (DeviceIntPtr pDev,
113706f2543Smrg                     ScreenPtr pScreen,
114706f2543Smrg		     CursorPtr pCursor,
115706f2543Smrg		     BoxPtr pHotBox,
116706f2543Smrg		     BoxPtr pTopLeftBox)
117706f2543Smrg{
118706f2543Smrg    AnimCurScreenPtr    as = GetAnimCurScreen(pScreen);
119706f2543Smrg
120706f2543Smrg    Unwrap (as, pScreen, CursorLimits);
121706f2543Smrg    if (IsAnimCur(pCursor))
122706f2543Smrg    {
123706f2543Smrg	AnimCurPtr	ac = GetAnimCur(pCursor);
124706f2543Smrg
125706f2543Smrg        (*pScreen->CursorLimits) (pDev, pScreen, ac->elts[0].pCursor,
126706f2543Smrg                                  pHotBox, pTopLeftBox);
127706f2543Smrg    }
128706f2543Smrg    else
129706f2543Smrg    {
130706f2543Smrg        (*pScreen->CursorLimits) (pDev, pScreen, pCursor,
131706f2543Smrg                                  pHotBox, pTopLeftBox);
132706f2543Smrg    }
133706f2543Smrg    Wrap (as, pScreen, CursorLimits, AnimCurCursorLimits);
134706f2543Smrg}
135706f2543Smrg
136706f2543Smrg/*
137706f2543Smrg * This has to be a screen block handler instead of a generic
138706f2543Smrg * block handler so that it is well ordered with respect to the DRI
139706f2543Smrg * block handler responsible for releasing the hardware to DRI clients
140706f2543Smrg */
141706f2543Smrg
142706f2543Smrgstatic void
143706f2543SmrgAnimCurScreenBlockHandler (int screenNum,
144706f2543Smrg			   pointer blockData,
145706f2543Smrg			   pointer pTimeout,
146706f2543Smrg			   pointer pReadmask)
147706f2543Smrg{
148706f2543Smrg    ScreenPtr		pScreen = screenInfo.screens[screenNum];
149706f2543Smrg    AnimCurScreenPtr    as = GetAnimCurScreen(pScreen);
150706f2543Smrg    DeviceIntPtr        dev;
151706f2543Smrg    Bool                activeDevice = FALSE;
152706f2543Smrg    CARD32              now = 0,
153706f2543Smrg                        soonest = ~0; /* earliest time to wakeup again */
154706f2543Smrg
155706f2543Smrg    for (dev = inputInfo.devices; dev; dev = dev->next)
156706f2543Smrg    {
157706f2543Smrg	if (IsPointerDevice(dev) && pScreen == dev->spriteInfo->anim.pScreen)
158706f2543Smrg	{
159706f2543Smrg	    if (!activeDevice) {
160706f2543Smrg                now = GetTimeInMillis ();
161706f2543Smrg                activeDevice = TRUE;
162706f2543Smrg            }
163706f2543Smrg
164706f2543Smrg	    if ((INT32) (now - dev->spriteInfo->anim.time) >= 0)
165706f2543Smrg	    {
166706f2543Smrg		AnimCurPtr ac  = GetAnimCur(dev->spriteInfo->anim.pCursor);
167706f2543Smrg		int        elt = (dev->spriteInfo->anim.elt + 1) % ac->nelt;
168706f2543Smrg		DisplayCursorProcPtr DisplayCursor;
169706f2543Smrg
170706f2543Smrg		/*
171706f2543Smrg		 * Not a simple Unwrap/Wrap as this
172706f2543Smrg		 * isn't called along the DisplayCursor
173706f2543Smrg		 * wrapper chain.
174706f2543Smrg		 */
175706f2543Smrg		DisplayCursor = pScreen->DisplayCursor;
176706f2543Smrg		pScreen->DisplayCursor = as->DisplayCursor;
177706f2543Smrg		(void) (*pScreen->DisplayCursor) (dev,
178706f2543Smrg						  pScreen,
179706f2543Smrg						  ac->elts[elt].pCursor);
180706f2543Smrg		as->DisplayCursor = pScreen->DisplayCursor;
181706f2543Smrg		pScreen->DisplayCursor = DisplayCursor;
182706f2543Smrg
183706f2543Smrg		dev->spriteInfo->anim.elt = elt;
184706f2543Smrg		dev->spriteInfo->anim.time = now + ac->elts[elt].delay;
185706f2543Smrg	    }
186706f2543Smrg
187706f2543Smrg	    if (soonest > dev->spriteInfo->anim.time)
188706f2543Smrg		soonest = dev->spriteInfo->anim.time;
189706f2543Smrg	}
190706f2543Smrg    }
191706f2543Smrg
192706f2543Smrg    if (activeDevice)
193706f2543Smrg        AdjustWaitForDelay (pTimeout, soonest - now);
194706f2543Smrg
195706f2543Smrg    Unwrap (as, pScreen, BlockHandler);
196706f2543Smrg    (*pScreen->BlockHandler) (screenNum, blockData, pTimeout, pReadmask);
197706f2543Smrg    if (activeDevice)
198706f2543Smrg        Wrap (as, pScreen, BlockHandler, AnimCurScreenBlockHandler);
199706f2543Smrg    else
200706f2543Smrg        as->BlockHandler = NULL;
201706f2543Smrg}
202706f2543Smrg
203706f2543Smrgstatic Bool
204706f2543SmrgAnimCurDisplayCursor (DeviceIntPtr pDev,
205706f2543Smrg                      ScreenPtr pScreen,
206706f2543Smrg		      CursorPtr pCursor)
207706f2543Smrg{
208706f2543Smrg    AnimCurScreenPtr    as = GetAnimCurScreen(pScreen);
209706f2543Smrg    Bool		ret;
210706f2543Smrg
211706f2543Smrg    Unwrap (as, pScreen, DisplayCursor);
212706f2543Smrg    if (IsAnimCur(pCursor))
213706f2543Smrg    {
214706f2543Smrg	if (pCursor != pDev->spriteInfo->anim.pCursor)
215706f2543Smrg	{
216706f2543Smrg	    AnimCurPtr		ac = GetAnimCur(pCursor);
217706f2543Smrg
218706f2543Smrg	    ret = (*pScreen->DisplayCursor)
219706f2543Smrg                (pDev, pScreen, ac->elts[0].pCursor);
220706f2543Smrg	    if (ret)
221706f2543Smrg	    {
222706f2543Smrg		pDev->spriteInfo->anim.elt = 0;
223706f2543Smrg		pDev->spriteInfo->anim.time = GetTimeInMillis () + ac->elts[0].delay;
224706f2543Smrg		pDev->spriteInfo->anim.pCursor = pCursor;
225706f2543Smrg		pDev->spriteInfo->anim.pScreen = pScreen;
226706f2543Smrg
227706f2543Smrg		if (!as->BlockHandler)
228706f2543Smrg		    Wrap(as, pScreen, BlockHandler, AnimCurScreenBlockHandler);
229706f2543Smrg	    }
230706f2543Smrg	}
231706f2543Smrg	else
232706f2543Smrg	    ret = TRUE;
233706f2543Smrg    }
234706f2543Smrg    else
235706f2543Smrg    {
236706f2543Smrg	pDev->spriteInfo->anim.pCursor = 0;
237706f2543Smrg	pDev->spriteInfo->anim.pScreen = 0;
238706f2543Smrg	ret = (*pScreen->DisplayCursor) (pDev, pScreen, pCursor);
239706f2543Smrg    }
240706f2543Smrg    Wrap (as, pScreen, DisplayCursor, AnimCurDisplayCursor);
241706f2543Smrg    return ret;
242706f2543Smrg}
243706f2543Smrg
244706f2543Smrgstatic Bool
245706f2543SmrgAnimCurSetCursorPosition (DeviceIntPtr pDev,
246706f2543Smrg                          ScreenPtr pScreen,
247706f2543Smrg			  int x,
248706f2543Smrg			  int y,
249706f2543Smrg			  Bool generateEvent)
250706f2543Smrg{
251706f2543Smrg    AnimCurScreenPtr    as = GetAnimCurScreen(pScreen);
252706f2543Smrg    Bool		ret;
253706f2543Smrg
254706f2543Smrg    Unwrap (as, pScreen, SetCursorPosition);
255706f2543Smrg    if (pDev->spriteInfo->anim.pCursor) {
256706f2543Smrg	pDev->spriteInfo->anim.pScreen = pScreen;
257706f2543Smrg
258706f2543Smrg	if (!as->BlockHandler)
259706f2543Smrg	    Wrap(as, pScreen, BlockHandler, AnimCurScreenBlockHandler);
260706f2543Smrg    }
261706f2543Smrg    ret = (*pScreen->SetCursorPosition) (pDev, pScreen, x, y, generateEvent);
262706f2543Smrg    Wrap (as, pScreen, SetCursorPosition, AnimCurSetCursorPosition);
263706f2543Smrg    return ret;
264706f2543Smrg}
265706f2543Smrg
266706f2543Smrgstatic Bool
267706f2543SmrgAnimCurRealizeCursor (DeviceIntPtr pDev,
268706f2543Smrg                      ScreenPtr pScreen,
269706f2543Smrg		      CursorPtr pCursor)
270706f2543Smrg{
271706f2543Smrg    AnimCurScreenPtr    as = GetAnimCurScreen(pScreen);
272706f2543Smrg    Bool		ret;
273706f2543Smrg
274706f2543Smrg    Unwrap (as, pScreen, RealizeCursor);
275706f2543Smrg    if (IsAnimCur(pCursor))
276706f2543Smrg	ret = TRUE;
277706f2543Smrg    else
278706f2543Smrg	ret = (*pScreen->RealizeCursor) (pDev, pScreen, pCursor);
279706f2543Smrg    Wrap (as, pScreen, RealizeCursor, AnimCurRealizeCursor);
280706f2543Smrg    return ret;
281706f2543Smrg}
282706f2543Smrg
283706f2543Smrgstatic Bool
284706f2543SmrgAnimCurUnrealizeCursor (DeviceIntPtr pDev,
285706f2543Smrg                        ScreenPtr pScreen,
286706f2543Smrg			CursorPtr pCursor)
287706f2543Smrg{
288706f2543Smrg    AnimCurScreenPtr    as = GetAnimCurScreen(pScreen);
289706f2543Smrg    Bool		ret;
290706f2543Smrg
291706f2543Smrg    Unwrap (as, pScreen, UnrealizeCursor);
292706f2543Smrg    if (IsAnimCur(pCursor))
293706f2543Smrg    {
294706f2543Smrg        AnimCurPtr  ac = GetAnimCur(pCursor);
295706f2543Smrg	int	    i;
296706f2543Smrg
297706f2543Smrg	if (pScreen->myNum == 0)
298706f2543Smrg	    for (i = 0; i < ac->nelt; i++)
299706f2543Smrg		FreeCursor (ac->elts[i].pCursor, 0);
300706f2543Smrg	ret = TRUE;
301706f2543Smrg    }
302706f2543Smrg    else
303706f2543Smrg	ret = (*pScreen->UnrealizeCursor) (pDev, pScreen, pCursor);
304706f2543Smrg    Wrap (as, pScreen, UnrealizeCursor, AnimCurUnrealizeCursor);
305706f2543Smrg    return ret;
306706f2543Smrg}
307706f2543Smrg
308706f2543Smrgstatic void
309706f2543SmrgAnimCurRecolorCursor (DeviceIntPtr pDev,
310706f2543Smrg                      ScreenPtr pScreen,
311706f2543Smrg		      CursorPtr pCursor,
312706f2543Smrg		      Bool displayed)
313706f2543Smrg{
314706f2543Smrg    AnimCurScreenPtr    as = GetAnimCurScreen(pScreen);
315706f2543Smrg
316706f2543Smrg    Unwrap (as, pScreen, RecolorCursor);
317706f2543Smrg    if (IsAnimCur(pCursor))
318706f2543Smrg    {
319706f2543Smrg        AnimCurPtr  ac = GetAnimCur(pCursor);
320706f2543Smrg	int	    i;
321706f2543Smrg
322706f2543Smrg        for (i = 0; i < ac->nelt; i++)
323706f2543Smrg	    (*pScreen->RecolorCursor) (pDev, pScreen, ac->elts[i].pCursor,
324706f2543Smrg				       displayed &&
325706f2543Smrg				       pDev->spriteInfo->anim.elt == i);
326706f2543Smrg    }
327706f2543Smrg    else
328706f2543Smrg	(*pScreen->RecolorCursor) (pDev, pScreen, pCursor, displayed);
329706f2543Smrg    Wrap (as, pScreen, RecolorCursor, AnimCurRecolorCursor);
330706f2543Smrg}
331706f2543Smrg
332706f2543SmrgBool
333706f2543SmrgAnimCurInit (ScreenPtr pScreen)
334706f2543Smrg{
335706f2543Smrg    AnimCurScreenPtr    as;
336706f2543Smrg
337706f2543Smrg    if (!dixRegisterPrivateKey(&AnimCurScreenPrivateKeyRec, PRIVATE_SCREEN, 0))
338706f2543Smrg	return FALSE;
339706f2543Smrg
340706f2543Smrg    as = (AnimCurScreenPtr) malloc(sizeof (AnimCurScreenRec));
341706f2543Smrg    if (!as)
342706f2543Smrg	return FALSE;
343706f2543Smrg    Wrap(as, pScreen, CloseScreen, AnimCurCloseScreen);
344706f2543Smrg
345706f2543Smrg    as->BlockHandler = NULL;
346706f2543Smrg
347706f2543Smrg    Wrap(as, pScreen, CursorLimits, AnimCurCursorLimits);
348706f2543Smrg    Wrap(as, pScreen, DisplayCursor, AnimCurDisplayCursor);
349706f2543Smrg    Wrap(as, pScreen, SetCursorPosition, AnimCurSetCursorPosition);
350706f2543Smrg    Wrap(as, pScreen, RealizeCursor, AnimCurRealizeCursor);
351706f2543Smrg    Wrap(as, pScreen, UnrealizeCursor, AnimCurUnrealizeCursor);
352706f2543Smrg    Wrap(as, pScreen, RecolorCursor, AnimCurRecolorCursor);
353706f2543Smrg    SetAnimCurScreen(pScreen,as);
354706f2543Smrg    return TRUE;
355706f2543Smrg}
356706f2543Smrg
357706f2543Smrgint
358706f2543SmrgAnimCursorCreate (CursorPtr *cursors, CARD32 *deltas, int ncursor, CursorPtr *ppCursor, ClientPtr client, XID cid)
359706f2543Smrg{
360706f2543Smrg    CursorPtr	pCursor;
361706f2543Smrg    int		rc, i;
362706f2543Smrg    AnimCurPtr	ac;
363706f2543Smrg
364706f2543Smrg    for (i = 0; i < screenInfo.numScreens; i++)
365706f2543Smrg	if (!GetAnimCurScreen (screenInfo.screens[i]))
366706f2543Smrg	    return BadImplementation;
367706f2543Smrg
368706f2543Smrg    for (i = 0; i < ncursor; i++)
369706f2543Smrg	if (IsAnimCur (cursors[i]))
370706f2543Smrg	    return BadMatch;
371706f2543Smrg
372706f2543Smrg    pCursor = (CursorPtr) calloc(CURSOR_REC_SIZE +
373706f2543Smrg				 sizeof (AnimCurRec) +
374706f2543Smrg				 ncursor * sizeof (AnimCurElt), 1);
375706f2543Smrg    if (!pCursor)
376706f2543Smrg	return BadAlloc;
377706f2543Smrg    dixInitPrivates(pCursor, pCursor + 1, PRIVATE_CURSOR);
378706f2543Smrg    pCursor->bits = &animCursorBits;
379706f2543Smrg    pCursor->refcnt = 1;
380706f2543Smrg
381706f2543Smrg    pCursor->foreRed = cursors[0]->foreRed;
382706f2543Smrg    pCursor->foreGreen = cursors[0]->foreGreen;
383706f2543Smrg    pCursor->foreBlue = cursors[0]->foreBlue;
384706f2543Smrg
385706f2543Smrg    pCursor->backRed = cursors[0]->backRed;
386706f2543Smrg    pCursor->backGreen = cursors[0]->backGreen;
387706f2543Smrg    pCursor->backBlue = cursors[0]->backBlue;
388706f2543Smrg
389706f2543Smrg    pCursor->id = cid;
390706f2543Smrg
391706f2543Smrg    /* security creation/labeling check */
392706f2543Smrg    rc = XaceHook(XACE_RESOURCE_ACCESS, client, cid, RT_CURSOR, pCursor,
393706f2543Smrg		  RT_NONE, NULL, DixCreateAccess);
394706f2543Smrg    if (rc != Success) {
395706f2543Smrg	dixFiniPrivates(pCursor, PRIVATE_CURSOR);
396706f2543Smrg	free(pCursor);
397706f2543Smrg	return rc;
398706f2543Smrg    }
399706f2543Smrg
400706f2543Smrg    /*
401706f2543Smrg     * Fill in the AnimCurRec
402706f2543Smrg     */
403706f2543Smrg    animCursorBits.refcnt++;
404706f2543Smrg    ac = GetAnimCur (pCursor);
405706f2543Smrg    ac->nelt = ncursor;
406706f2543Smrg    ac->elts = (AnimCurElt *) (ac + 1);
407706f2543Smrg
408706f2543Smrg    for (i = 0; i < ncursor; i++)
409706f2543Smrg    {
410706f2543Smrg	cursors[i]->refcnt++;
411706f2543Smrg	ac->elts[i].pCursor = cursors[i];
412706f2543Smrg	ac->elts[i].delay = deltas[i];
413706f2543Smrg    }
414706f2543Smrg
415706f2543Smrg    *ppCursor = pCursor;
416706f2543Smrg    return Success;
417706f2543Smrg}
418