/*
 * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#ifdef HEADLESS
    #error This file should not be included in headless library
#endif

#include "awt_mgrsel.h"

static Atom XA_MANAGER = None;

/*
 * Structures that describes the manager selection AWT listens to with
 * callabacks to the subsytems interested in the selection.  (We only
 * listen to a couple of selections, so linear search is enough).
 */
struct AwtMgrsel {
    char *selname;              /* base name of selection atoms */
    Atom *per_scr_atoms;        /* per-screen selection atoms (ICCCM 1.2.6) */
    Atom *per_scr_owners;       /* windows currently owning the selection */
    long extra_mask;            /* extra events to listen to on owners */
    void *cookie;
    void (*callback_event)(int, XEvent *, void *); /* extra_mask events */
    void (*callback_owner)(int, Window, long *, void *); /* owner changes */
    struct AwtMgrsel *next;
};

static struct AwtMgrsel *mgrsel_list = NULL;


static int awt_mgrsel_screen(Window w);
static Window awt_mgrsel_select_per_screen(Atom, long);
static int awt_mgrsel_managed(XClientMessageEvent *mgrown);
static int awt_mgrsel_unmanaged(XDestroyWindowEvent *ev);

#ifdef DEBUG
static void awt_mgrsel_dtraceManaged(XClientMessageEvent *mgrown);
#endif



/*
 * Find which screen the window W is the root of.
 * Returns the screen number, or -1 if W is not a root.
 */
static int
awt_mgrsel_screen(Window w)
{
    Display *dpy = awt_display;
    int scr;

    for (scr = 0; scr < ScreenCount(dpy); ++scr) {
        if (w == RootWindow(dpy, scr)) {
            return (scr);
        }
    }

    return (-1);
}


/************************************************************************
 * For every one that asketh receiveth; and he that seeketh findeth;
 * and to him that knocketh it shall be opened.  (Luke 11:10).
 */


/*
 * A method for a subsytem to express its interest in a certain
 * manager selection.
 *
 * If owner changes, the callback_owner will be called with the screen
 * number and the new owning window when onwership is established, or
 * None if the owner is gone.
 *
 * Events in extra_mask are selected for on owning windows (exsiting
 * ones and on new owners when established) and callback_event will be
 * called with the screen number and an event.
 *
 * The function returns an array of current owners.  The size of the
 * array is ScreenCount(awt_display).  The array is "owned" by this
 * module and should be considered by the caller as read-only.
 */
const Window *
awt_mgrsel_select(const char *selname, long extra_mask,
                  void *cookie,
                  void (*callback_event)(int, XEvent *, void *),
                  void (*callback_owner)(int, Window, long *, void *))
{
    Display *dpy = awt_display;
    struct AwtMgrsel *mgrsel;
    Atom *per_scr_atoms;
    Window *per_scr_owners;
    char *namesbuf;
    char **names;
    int per_scr_sz;
    int nscreens = ScreenCount(dpy);
    int scr;
    Status status;

    DASSERT(selname != NULL);
    DTRACE_PRINTLN1("MG: select: %s", selname);

    /* buffer size for one per-screen atom name */
    per_scr_sz = strlen(selname) + /* "_S" */ 2 + /* %2d */ + 2 /* '\0' */+ 1;

    namesbuf = malloc(per_scr_sz * nscreens);  /* actual storage for names */
    names = malloc(sizeof(char *) * nscreens); /* pointers to names */
    per_scr_atoms = malloc(sizeof(Atom) * nscreens);
    per_scr_owners = malloc(sizeof(Window) * nscreens);
    mgrsel = malloc(sizeof(struct AwtMgrsel));

    if (namesbuf == NULL || names == NULL || per_scr_atoms == NULL
        || per_scr_owners == NULL || mgrsel == NULL)
    {
        DTRACE_PRINTLN("MG: select: unable to allocate memory");
        if (namesbuf != NULL) free(namesbuf);
        if (names != NULL) free(names);
        if (per_scr_atoms != NULL) free(per_scr_atoms);
        if (per_scr_owners != NULL) free(per_scr_owners);
        if (mgrsel != NULL) free(mgrsel);
        return (NULL);
    }


    for (scr = 0; scr < nscreens; ++scr) {
        size_t sz;

        names[scr] = &namesbuf[per_scr_sz * scr];
        sz = snprintf(names[scr], per_scr_sz, "%s_S%-d", selname, scr);
        DASSERT(sz < per_scr_sz);
    }

    status = XInternAtoms(dpy, names, nscreens, False, per_scr_atoms);

    free(names);
    free(namesbuf);

    if (status == 0) {
        DTRACE_PRINTLN("MG: select: XInternAtoms failed");
        free(per_scr_atoms);
        free(per_scr_owners);
        return (NULL);
    }

    mgrsel->selname = strdup(selname);
    mgrsel->per_scr_atoms = per_scr_atoms;
    mgrsel->per_scr_owners = per_scr_owners;
    mgrsel->extra_mask = extra_mask;
    mgrsel->cookie = cookie;
    mgrsel->callback_event = callback_event;
    mgrsel->callback_owner = callback_owner;

    for (scr = 0; scr < nscreens; ++scr) {
        Window owner;

        owner = awt_mgrsel_select_per_screen(per_scr_atoms[scr], extra_mask);
        mgrsel->per_scr_owners[scr] = owner;
#ifdef DEBUG
        if (owner == None) {
            DTRACE_PRINTLN1("MG:   screen %d - None", scr);
        } else {
            DTRACE_PRINTLN2("MG:   screen %d - 0x%08lx", scr, owner);
        }
#endif
    }

    mgrsel->next = mgrsel_list;
    mgrsel_list = mgrsel;

    return (per_scr_owners);
}


static Window
awt_mgrsel_select_per_screen(Atom selection, long extra_mask)
{
    Display *dpy = awt_display;
    Window owner;

    XGrabServer(dpy);

    owner = XGetSelectionOwner(dpy, selection);
    if (owner == None) {
        /* we'll get notified later if one arrives */
        XUngrabServer(dpy);
        /* Workaround for bug 5039226 */
        XSync(dpy, False);
        return (None);
    }

    /*
     * Select for StructureNotifyMask to get DestroyNotify when owner
     * is gone.  Also select for any additional events caller is
     * interested in (e.g. PropertyChangeMask).  Caller will be
     * notifed of these events via ... XXX ...
     */
    XSelectInput(dpy, owner, StructureNotifyMask | extra_mask);

    XUngrabServer(dpy);
    /* Workaround for bug 5039226 */
    XSync(dpy, False);
    return (owner);
}


/************************************************************************
 * And so I saw the wicked buried, who had come and gone from the
 * place of the holy, and they were forgotten in the city where they
 * had so done: this is also vanity.  (Eccl 8:10)
 */

#ifdef DEBUG
/*
 * Print the message from the new manager that announces it acquired
 * ownership.
 */
static void
awt_mgrsel_dtraceManaged(XClientMessageEvent *mgrown)
{
    Display *dpy = awt_display;
    Atom selection;
    char *selname, *print_selname;
    int scr;

    scr = awt_mgrsel_screen(mgrown->window);

    selection = mgrown->data.l[1];
    print_selname = selname = XGetAtomName(dpy, selection);
    if (selname == NULL) {
        if (selection == None) {
            print_selname = "<None>";
        } else {
            print_selname = "<Unknown>";
        }
    }

    DTRACE_PRINTLN4("MG: new MANAGER for %s: screen %d, owner 0x%08lx (@%lu)",
                   print_selname, scr,
                   mgrown->data.l[2],  /* the window owning the selection */
                   mgrown->data.l[0]); /* timestamp */
    DTRACE_PRINTLN4("MG:   %ld %ld / 0x%lx 0x%lx", /* extra data */
                    mgrown->data.l[3], mgrown->data.l[4],
                    mgrown->data.l[3], mgrown->data.l[4]);

    if (selname != NULL) {
        XFree(selname);
    }
}
#endif /* DEBUG */


static int
awt_mgrsel_managed(XClientMessageEvent *mgrown)
{
    Display *dpy = awt_display;
    struct AwtMgrsel *mgrsel;
    int scr;

    long timestamp;
    Atom selection;
    Window owner;
    long *data;

    if (mgrown->message_type != XA_MANAGER) {
        DTRACE_PRINTLN("MG: ClientMessage type != MANAGER, ignoring");
        return (0);
    }

    scr = awt_mgrsel_screen(mgrown->window);

#ifdef DEBUG
    awt_mgrsel_dtraceManaged(mgrown);
#endif

    if (scr < 0) {
        DTRACE_PRINTLN("MG: MANAGER ClientMessage with a non-root window!");
        return (0);
    }

    timestamp = mgrown->data.l[0];
    selection = mgrown->data.l[1];
    owner     = mgrown->data.l[2];
    data      = &mgrown->data.l[3]; /* long[2], selection specific */

    /* is this a selection we are intrested in? */
    for (mgrsel = mgrsel_list; mgrsel != NULL; mgrsel = mgrsel->next) {
        if (selection == mgrsel->per_scr_atoms[scr])
            break;
    }

    if (mgrsel == NULL) {
        DTRACE_PRINTLN("MG: not interested in this selection, ignoring");
        return (0);
    }


    mgrsel->per_scr_owners[scr] = owner;

    XSelectInput(dpy, owner, StructureNotifyMask | mgrsel->extra_mask);

    /* notify the listener */
    if (mgrsel->callback_owner != NULL) {
        (*mgrsel->callback_owner)(scr, owner, data, mgrsel->cookie);
    }

    return (1);
}


static int
awt_mgrsel_unmanaged(XDestroyWindowEvent *ev)
{
    Display *dpy = awt_display;
    struct AwtMgrsel *mgrsel;
    Window exowner;
    int scr;

    exowner = ev->window;       /* selection owner that's gone */

    /* is this a selection we are intrested in? */
    for (mgrsel = mgrsel_list; mgrsel != NULL; mgrsel = mgrsel->next) {
        for (scr = 0; scr < ScreenCount(dpy); ++scr) {
            if (exowner == mgrsel->per_scr_owners[scr]) {
                /* can one window own selections for more than one screen? */
                goto out;       /* XXX??? */
            }
        }
    }
  out:
    if (mgrsel == NULL) {
        DTRACE_PRINTLN1("MG: DestroyNotify for 0x%08lx ignored", exowner);
        return (0);
    }

    DTRACE_PRINTLN3("MG: DestroyNotify for 0x%08lx, owner of %s at screen %d",
                    exowner, mgrsel->selname, scr);

    /* notify the listener (pass exowner as data???) */
    if (mgrsel->callback_owner != NULL) {
        (*mgrsel->callback_owner)(scr, None, NULL, mgrsel->cookie);
    }

    return (1);
}


/*
 * Hook to be called from toolkit event loop.
 */
int
awt_mgrsel_processEvent(XEvent *ev)
{
    Display *dpy = awt_display;
    struct AwtMgrsel *mgrsel;
    int scr;

    if (ev->type == ClientMessage) { /* new manager announces ownership? */
        if (awt_mgrsel_managed(&ev->xclient))
            return (1);
    }

    if (ev->type == DestroyNotify) { /* manager gives up selection? */
        if (awt_mgrsel_unmanaged(&ev->xdestroywindow))
            return (1);
    }

    /* is this an event selected on one of selection owners? */
    for (mgrsel = mgrsel_list; mgrsel != NULL; mgrsel = mgrsel->next) {
        for (scr = 0; scr < ScreenCount(dpy); ++scr) {
            if (ev->xany.window == mgrsel->per_scr_owners[scr]) {
                /* can one window own selections for more than one screen? */
                goto out;       /* XXX??? */
            }
        }
    }
  out:
    DTRACE_PRINT2("MG: screen %d, event %d ...  ",
                  scr, ev->xany.type);
    if (mgrsel == NULL) {
        DTRACE_PRINTLN("ignored");
        return (0);             /* not interested */
    }

    DTRACE_PRINT1("%s ...  ", mgrsel->selname);
    if (mgrsel->callback_event != NULL) {
        DTRACE_PRINTLN("dispatching");
        (*mgrsel->callback_event)(scr, ev, mgrsel->cookie);
    }
#ifdef DEBUG
    else {
        DTRACE_PRINTLN("no callback");
    }
#endif

    return (1);
}


void
awt_mgrsel_init(void)
{
    static Boolean inited = False;

    Display *dpy = awt_display;
    int scr;

    if (inited) {
        return;
    }

    XA_MANAGER = XInternAtom(dpy, "MANAGER", False);
    DASSERT(XA_MANAGER != None);


    /*
     * Listen for ClientMessage's on each screen's root.  We hook into
     * the message loop in the toolkit (with awt_mgrsel_processEvent)
     * to get the events processed.  We need this for notifications of
     * new manager acquiring ownership of the manager selection.
     */
    for (scr = 0; scr < ScreenCount(dpy); ++scr) {
        XSelectInput(dpy, RootWindow(dpy, scr), StructureNotifyMask);
    }

    inited = True;
}
