/*
 * Copyright © 2002 Keith Packard
 *
 * 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 Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD 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.
 */

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include "xfixesint.h"
#include "xace.h"

static RESTYPE		SelectionClientType, SelectionWindowType;
static Bool		SelectionCallbackRegistered = FALSE;

/*
 * There is a global list of windows selecting for selection events
 * on every selection.  This should be plenty efficient for the
 * expected usage, if it does become a problem, it should be easily
 * replaced with a hash table of some kind keyed off the selection atom
 */

typedef struct _SelectionEvent *SelectionEventPtr;

typedef struct _SelectionEvent {
    SelectionEventPtr	next;
    Atom		selection;
    CARD32		eventMask;
    ClientPtr		pClient;
    WindowPtr		pWindow;
    XID			clientResource;
} SelectionEventRec;

static SelectionEventPtr	selectionEvents;

static void
XFixesSelectionCallback (CallbackListPtr *callbacks, pointer data, pointer args)
{
    SelectionEventPtr	e;
    SelectionInfoRec	*info = (SelectionInfoRec *) args;
    Selection		*selection = info->selection;
    int			subtype;
    CARD32		eventMask;
    
    switch (info->kind) {
    case SelectionSetOwner:
	subtype = XFixesSetSelectionOwnerNotify;
	eventMask = XFixesSetSelectionOwnerNotifyMask;
	break;
    case SelectionWindowDestroy:
	subtype = XFixesSelectionWindowDestroyNotify;
	eventMask = XFixesSelectionWindowDestroyNotifyMask;
	break;
    case SelectionClientClose:
	subtype = XFixesSelectionClientCloseNotify;
	eventMask = XFixesSelectionClientCloseNotifyMask;
	break;
    default:
	return;
    }
    for (e = selectionEvents; e; e = e->next)
    {
	if (e->selection == selection->selection && 
	    (e->eventMask & eventMask))
	{
	    xXFixesSelectionNotifyEvent	ev;

	    memset(&ev, 0, sizeof(xXFixesSelectionNotifyEvent));
	    ev.type = XFixesEventBase + XFixesSelectionNotify;
	    ev.subtype = subtype;
	    ev.window = e->pWindow->drawable.id;
	    if (subtype == XFixesSetSelectionOwnerNotify)
		ev.owner = selection->window;
	    else
		ev.owner = 0;
	    ev.selection = e->selection;
	    ev.timestamp = currentTime.milliseconds;
	    ev.selectionTimestamp = selection->lastTimeChanged.milliseconds;
	    WriteEventsToClient (e->pClient, 1, (xEvent *) &ev);
	}
    }
}

static Bool
CheckSelectionCallback (void)
{
    if (selectionEvents)
    {
	if (!SelectionCallbackRegistered)
	{
	    if (!AddCallback (&SelectionCallback, XFixesSelectionCallback, NULL))
		return FALSE;
	    SelectionCallbackRegistered = TRUE;
	}
    }
    else
    {
	if (SelectionCallbackRegistered)
	{
	    DeleteCallback (&SelectionCallback, XFixesSelectionCallback, NULL);
	    SelectionCallbackRegistered = FALSE;
	}
    }
    return TRUE;
}

#define SelectionAllEvents (XFixesSetSelectionOwnerNotifyMask |\
			    XFixesSelectionWindowDestroyNotifyMask |\
			    XFixesSelectionClientCloseNotifyMask)

static int
XFixesSelectSelectionInput (ClientPtr	pClient,
			    Atom	selection,
			    WindowPtr	pWindow,
			    CARD32	eventMask)
{
    pointer val;
    int rc;
    SelectionEventPtr	*prev, e;

    rc = XaceHook(XACE_SELECTION_ACCESS, pClient, selection, DixGetAttrAccess);
    if (rc != Success)
	return rc;

    for (prev = &selectionEvents; (e = *prev); prev = &e->next)
    {
	if (e->selection == selection &&
	    e->pClient == pClient &&
	    e->pWindow == pWindow)
	{
	    break;
	}
    }
    if (!eventMask)
    {
	if (e)
	{
	    FreeResource (e->clientResource, 0);
	}
	return Success;
    }
    if (!e)
    {
	e = (SelectionEventPtr) malloc(sizeof (SelectionEventRec));
	if (!e)
	    return BadAlloc;

	e->next = 0;
	e->selection = selection;
	e->pClient = pClient;
	e->pWindow = pWindow;
	e->clientResource = FakeClientID(pClient->index);

	/*
	 * Add a resource hanging from the window to
	 * catch window destroy
	 */
	rc = dixLookupResourceByType (&val, pWindow->drawable.id,
				      SelectionWindowType, serverClient,
				      DixGetAttrAccess);
	if (rc != Success)
	    if (!AddResource (pWindow->drawable.id, SelectionWindowType,
			      (pointer) pWindow))
	    {
		free(e);
		return BadAlloc;
	    }

	if (!AddResource (e->clientResource, SelectionClientType, (pointer) e))
	    return BadAlloc;

	*prev = e;
	if (!CheckSelectionCallback ())
	{
	    FreeResource (e->clientResource, 0);
	    return BadAlloc;
	}
    }
    e->eventMask = eventMask;
    return Success;
}

int
ProcXFixesSelectSelectionInput (ClientPtr client)
{
    REQUEST (xXFixesSelectSelectionInputReq);
    WindowPtr	pWin;
    int		rc;

    REQUEST_SIZE_MATCH (xXFixesSelectSelectionInputReq);
    rc = dixLookupWindow(&pWin, stuff->window, client, DixGetAttrAccess);
    if (rc != Success)
        return rc;
    if (stuff->eventMask & ~SelectionAllEvents)
    {
	client->errorValue = stuff->eventMask;
	return BadValue;
    }
    return XFixesSelectSelectionInput (client, stuff->selection,
				       pWin, stuff->eventMask);
}

int
SProcXFixesSelectSelectionInput (ClientPtr client)
{
    register int n;
    REQUEST(xXFixesSelectSelectionInputReq);

    swaps(&stuff->length, n);
    swapl(&stuff->window, n);
    swapl(&stuff->selection, n);
    swapl(&stuff->eventMask, n);
    return (*ProcXFixesVector[stuff->xfixesReqType])(client);
}
    
void
SXFixesSelectionNotifyEvent (xXFixesSelectionNotifyEvent *from,
			     xXFixesSelectionNotifyEvent *to)
{
    to->type = from->type;
    cpswaps (from->sequenceNumber, to->sequenceNumber);
    cpswapl (from->window, to->window);
    cpswapl (from->owner, to->owner);
    cpswapl (from->selection, to->selection);
    cpswapl (from->timestamp, to->timestamp);
    cpswapl (from->selectionTimestamp, to->selectionTimestamp);
}

static int
SelectionFreeClient (pointer data, XID id)
{
    SelectionEventPtr	old = (SelectionEventPtr) data;
    SelectionEventPtr	*prev, e;
    
    for (prev = &selectionEvents; (e = *prev); prev = &e->next)
    {
	if (e == old)
	{
	    *prev = e->next;
	    free(e);
	    CheckSelectionCallback ();
	    break;
	}
    }
    return 1;
}

static int
SelectionFreeWindow (pointer data, XID id)
{
    WindowPtr		pWindow = (WindowPtr) data;
    SelectionEventPtr	e, next;

    for (e = selectionEvents; e; e = next)
    {
	next = e->next;
	if (e->pWindow == pWindow)
	{
	    FreeResource (e->clientResource, 0);
	}
    }
    return 1;
}

Bool
XFixesSelectionInit (void)
{
    SelectionClientType = CreateNewResourceType(SelectionFreeClient,
						"XFixesSelectionClient");
    SelectionWindowType = CreateNewResourceType(SelectionFreeWindow,
						"XFixesSelectionWindow");
    return SelectionClientType && SelectionWindowType;
}