#include <stdlib.h>

#include <X11/Xatom.h>

#include "WINGsP.h"

#define MAX_PROPERTY_SIZE 8*1024


char *WMSelectionOwnerDidChangeNotification = "WMSelectionOwnerDidChange";


typedef struct SelectionHandler {
    WMView *view;
    Atom selection;
    Time timestamp;
    WMSelectionProcs procs;
    void *data;

    struct {
        unsigned delete_pending:1;
        unsigned done_pending:1;
    } flags;
} SelectionHandler;


typedef struct SelectionCallback {
    WMView *view;
    Atom selection;
    Atom target;
    Time timestamp;
    WMSelectionCallback *callback;
    void *data;

    struct {
        unsigned delete_pending:1;
        unsigned done_pending:1;
    } flags;
} SelectionCallback;



static WMArray *selCallbacks = NULL;

static WMArray *selHandlers = NULL;

static Bool gotXError = False;




void
WMDeleteSelectionHandler(WMView *view, Atom selection, Time timestamp)
{
    SelectionHandler *handler;
    Display *dpy = W_VIEW_SCREEN(view)->display;
    Window win = W_VIEW_DRAWABLE(view);
    WMArrayIterator iter;

    if (!selHandlers)
        return;

    /*//printf("deleting selection handler for %d", win);*/

    WM_ITERATE_ARRAY(selHandlers, handler, iter) {
        if (handler->view == view
            && (handler->selection == selection || selection == None)
            && (handler->timestamp == timestamp || timestamp == CurrentTime)) {

            if (handler->flags.done_pending) {
                handler->flags.delete_pending = 1;
                /*//puts(": postponed because still pending");*/
                return;
            }
            /*//printf(": found & removed");*/
            WMRemoveFromArray(selHandlers, handler);
            break;
        }
    }

    /*//printf("\n");*/

    XGrabServer(dpy);
    if (XGetSelectionOwner(dpy, selection) == win) {
        XSetSelectionOwner(dpy, selection, None, timestamp);
    }
    XUngrabServer(dpy);
}



void
WMDeleteSelectionCallback(WMView *view, Atom selection, Time timestamp)
{
    SelectionCallback *handler;
    WMArrayIterator iter;

    if (!selCallbacks)
        return;

    WM_ITERATE_ARRAY(selCallbacks, handler, iter) {
        if (handler->view == view
            && (handler->selection == selection || selection == None)
            && (handler->timestamp == timestamp || timestamp == CurrentTime)) {

            if (handler->flags.done_pending) {
                handler->flags.delete_pending = 1;
                return;
            }
            WMRemoveFromArray(selCallbacks, handler);
            break;
        }
    }
}


static int
handleXError(Display *dpy, XErrorEvent *ev)
{
    gotXError = True;

    return 1;
}


static Bool
writeSelection(Display *dpy, Window requestor, Atom property, Atom type,
               WMData *data)
{
    static void *oldHandler;
    int format, bpi;

    format = WMGetDataFormat(data);
    if (format == 0)
        format = 8;

    bpi = format/8;

    /* printf("write to %x: %s\n", requestor, XGetAtomName(dpy, property)); */

    oldHandler = XSetErrorHandler(handleXError);

    gotXError = False;

    XChangeProperty(dpy, requestor, property, type, format, PropModeReplace,
                    WMDataBytes(data), WMGetDataLength(data)/bpi);

    XFlush(dpy);

    XSetErrorHandler(oldHandler);

    return !gotXError;
}


static void
notifySelection(XEvent *event, Atom prop)
{
    XEvent ev;

    /* printf("event to %x\n", event->xselectionrequest.requestor); */

    ev.xselection.type = SelectionNotify;
    ev.xselection.serial = 0;
    ev.xselection.send_event = True;
    ev.xselection.display = event->xselectionrequest.display;
    ev.xselection.requestor = event->xselectionrequest.requestor;
    ev.xselection.target = event->xselectionrequest.target;
    ev.xselection.selection = event->xselectionrequest.selection;
    ev.xselection.property = prop;
    ev.xselection.time = event->xselectionrequest.time;

    XSendEvent(event->xany.display, event->xselectionrequest.requestor,
               False, 0, &ev);
    XFlush(event->xany.display);
}


static void
handleRequestEvent(XEvent *event)
{
    SelectionHandler *handler;
    WMArrayIterator iter;
    WMArray *copy;
    Bool handledRequest;

    WM_ITERATE_ARRAY(selHandlers, handler, iter) {

        switch (event->type) {
        case SelectionClear:
            if (W_VIEW_DRAWABLE(handler->view)
                != event->xselectionclear.window) {
                break;
            }

            handler->flags.done_pending = 1;
            if (handler->procs.selectionLost)
                handler->procs.selectionLost(handler->view,
                                             handler->selection,
                                             handler->data);
            handler->flags.done_pending = 0;
            handler->flags.delete_pending = 1;
            break;

        case SelectionRequest:
            if (W_VIEW_DRAWABLE(handler->view)!=event->xselectionrequest.owner) {
                break;
            }

            if (handler->procs.convertSelection != NULL
                && handler->selection == event->xselectionrequest.selection) {
                Atom atom;
                WMData *data;
                Atom prop;

                /* they're requesting for something old.. maybe another handler
                 * can handle it */
                if (event->xselectionrequest.time < handler->timestamp
                    && event->xselectionrequest.time != CurrentTime) {
                    break;
                }

                handledRequest = False;

                handler->flags.done_pending = 1;

                data = handler->procs.convertSelection(handler->view,
                                                       handler->selection,
                                                       event->xselectionrequest.target,
                                                       handler->data,
                                                       &atom);

                prop = event->xselectionrequest.property;
                /* obsolete clients that don't set the property field */
                if (prop == None)
                    prop = event->xselectionrequest.target;

                if (data) {
                    if (writeSelection(event->xselectionrequest.display,
                                       event->xselectionrequest.requestor,
                                       prop, atom, data)) {
                        handledRequest = True;
                    }
                    WMReleaseData(data);
                }

                notifySelection(event, (handledRequest==True ? prop : None));

                if (handler->procs.selectionDone != NULL) {
                    handler->procs.selectionDone(handler->view,
                                                 handler->selection,
                                                 event->xselectionrequest.target,
                                                 handler->data);
                }

                handler->flags.done_pending = 0;
            }
            break;
        }
    }

    /* delete handlers */
    copy = WMDuplicateArray(selHandlers);
    WM_ITERATE_ARRAY(copy, handler, iter) {
        if (handler && handler->flags.delete_pending) {
            WMDeleteSelectionHandler(handler->view, handler->selection,
                                     handler->timestamp);
        }
    }
    WMFreeArray(copy);
}


static WMData*
getSelectionData(Display *dpy, Window win, Atom where)
{
    WMData *wdata;
    unsigned char *data;
    Atom rtype;
    unsigned bits, bpi;
    unsigned long len, bytes;


    if (XGetWindowProperty(dpy, win, where, 0, MAX_PROPERTY_SIZE,
                           False, AnyPropertyType, &rtype, &bits, &len,
                           &bytes, &data)!=Success) {
        return NULL;
    }

    bpi = bits/8;

    wdata = WMCreateDataWithBytesNoCopy(data, len*bpi, (WMFreeDataProc*)XFree);
    WMSetDataFormat(wdata, bits);

    return wdata;
}


static void
handleNotifyEvent(XEvent *event)
{
    SelectionCallback *handler;
    WMArrayIterator iter;
    WMArray *copy;
    WMData *data;

    WM_ITERATE_ARRAY(selCallbacks, handler, iter) {

        if (W_VIEW_DRAWABLE(handler->view) != event->xselection.requestor
            || handler->selection != event->xselection.selection) {
            continue;
        }
        handler->flags.done_pending = 1;

        if (event->xselection.property == None) {
            data = NULL;
        } else {
            data = getSelectionData(event->xselection.display,
                                    event->xselection.requestor,
                                    event->xselection.property);
        }

        (*handler->callback)(handler->view, handler->selection,
                             handler->target, handler->timestamp,
                             handler->data, data);

        if (data != NULL) {
            WMReleaseData(data);
        }
        handler->flags.done_pending = 0;
        handler->flags.delete_pending = 1;
    }

    /* delete callbacks */
    copy = WMDuplicateArray(selCallbacks);
    WM_ITERATE_ARRAY(copy, handler, iter) {
        if (handler && handler->flags.delete_pending) {
            WMDeleteSelectionCallback(handler->view, handler->selection,
                                      handler->timestamp);
        }
    }
    WMFreeArray(copy);
}



void
W_HandleSelectionEvent(XEvent *event)
{
    /*//printf("%d received selection ", event->xany.window);*/
    /*//switch(event->type) {
     case SelectionNotify:
     puts("notify"); break;
     case SelectionRequest:
     puts("request"); break;
     case SelectionClear:
     puts("clear"); break;
     default:
     puts("unknown"); break;
     }*/

    if (event->type == SelectionNotify) {
        handleNotifyEvent(event);
    } else {
        handleRequestEvent(event);
    }
}



Bool
WMCreateSelectionHandler(WMView *view, Atom selection, Time timestamp,
                         WMSelectionProcs *procs, void *cdata)
{
    SelectionHandler *handler;
    Display *dpy = W_VIEW_SCREEN(view)->display;

    XSetSelectionOwner(dpy, selection, W_VIEW_DRAWABLE(view), timestamp);
    if (XGetSelectionOwner(dpy, selection) != W_VIEW_DRAWABLE(view)) {
        return False;
    }

    WMPostNotificationName(WMSelectionOwnerDidChangeNotification,
                           (void*)selection, (void*)view);

    /*//printf("created selection handler for %d\n", W_VIEW_DRAWABLE(view));*/

    handler = wmalloc(sizeof(SelectionHandler));

    handler->view = view;
    handler->selection = selection;
    handler->timestamp = timestamp;
    handler->procs = *procs;
    handler->data = cdata;
    memset(&handler->flags, 0, sizeof(handler->flags));

    if (selHandlers == NULL) {
        selHandlers = WMCreateArrayWithDestructor(4, wfree);
    }

    WMAddToArray(selHandlers, handler);

    return True;
}



Bool
WMRequestSelection(WMView *view, Atom selection, Atom target, Time timestamp,
                   WMSelectionCallback *callback, void *cdata)
{
    SelectionCallback *handler;

    if (XGetSelectionOwner(W_VIEW_SCREEN(view)->display, selection) == None)
        return False;

    if (!XConvertSelection(W_VIEW_SCREEN(view)->display, selection, target,
                           W_VIEW_SCREEN(view)->clipboardAtom,
                           W_VIEW_DRAWABLE(view), timestamp)) {
        return False;
    }

    handler = wmalloc(sizeof(SelectionCallback));

    handler->view = view;
    handler->selection = selection;
    handler->target = target;
    handler->timestamp = timestamp;
    handler->callback = callback;
    handler->data = cdata;
    memset(&handler->flags, 0, sizeof(handler->flags));

    if (selCallbacks == NULL) {
        selCallbacks = WMCreateArrayWithDestructor(4, wfree);
    }

    WMAddToArray(selCallbacks, handler);

    return True;
}



syntax highlighted by Code2HTML, v. 0.9.1