/*
 * Copyright 2007 Kim woelders
 *
 * 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 the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS 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.
 */
#include <X11/Xatom.h>
#include <X11/Xlib.h>

#include "clientwin.h"

static Atom atom_wm_state = None;

/*
 * Check if window has given property
 */
static Bool
Window_Has_Property(Display * dpy, Window win, Atom atom)
{
    Atom type_ret;
    int format_ret;
    unsigned char *prop_ret;
    unsigned long bytes_after, num_ret;

    type_ret = None;
    prop_ret = NULL;
    XGetWindowProperty(dpy, win, atom, 0, 0, False, AnyPropertyType,
                       &type_ret, &format_ret, &num_ret,
                       &bytes_after, &prop_ret);
    if (prop_ret)
        XFree(prop_ret);

    return (type_ret != None) ? True : False;
}

/*
 * Check if window is viewable
 */
static Bool
Window_Is_Viewable(Display * dpy, Window win)
{
    Bool ok;
    XWindowAttributes xwa;

    XGetWindowAttributes(dpy, win, &xwa);

    ok = (xwa.class == InputOutput) && (xwa.map_state == IsViewable);

    return ok;
}

/*
 * Find a window that has WM_STATE set in the window tree below win.
 * Unmapped/unviewable windows are not considered valid matches.
 * Children are searched in top-down stacking order.
 * The first matching window is returned, None if no match is found.
 */
static Window
Find_Client_In_Children(Display * dpy, Window win)
{
    Window root, parent;
    Window *children;
    unsigned int n_children;
    int i;

    if (!XQueryTree(dpy, win, &root, &parent, &children, &n_children))
        return None;
    if (!children)
        return None;

    /* Check each child for WM_STATE and other validity */
    win = None;
    for (i = (int) n_children - 1; i >= 0; i--) {
        if (!Window_Is_Viewable(dpy, children[i])) {
            children[i] = None; /* Don't bother descending into this one */
            continue;
        }
        if (!Window_Has_Property(dpy, children[i], atom_wm_state))
            continue;

        /* Got one */
        win = children[i];
        goto done;
    }

    /* No children matched, now descend into each child */
    for (i = (int) n_children - 1; i >= 0; i--) {
        if (children[i] == None)
            continue;
        win = Find_Client_In_Children(dpy, children[i]);
        if (win != None)
            break;
    }

  done:
    XFree(children);

    return win;
}

/*
 * Find virtual roots (_NET_VIRTUAL_ROOTS)
 */
static unsigned long *
Find_Roots(Display * dpy, Window root, unsigned long *num)
{
    Atom type_ret;
    int format_ret;
    unsigned char *prop_ret;
    unsigned long bytes_after, num_ret;
    Atom atom;

    *num = 0;
    atom = XInternAtom(dpy, "_NET_VIRTUAL_ROOTS", False);
    if (!atom)
        return NULL;

    type_ret = None;
    prop_ret = NULL;
    if (XGetWindowProperty(dpy, root, atom, 0, 0x7fffffff, False,
                           XA_WINDOW, &type_ret, &format_ret, &num_ret,
                           &bytes_after, &prop_ret) != Success)
        return NULL;

    if (prop_ret && type_ret == XA_WINDOW && format_ret == 32) {
        *num = num_ret;
        return ((unsigned long *) prop_ret);
    }
    if (prop_ret)
        XFree(prop_ret);

    return NULL;
}

/*
 * Find child window at pointer location
 */
static Window
Find_Child_At_Pointer(Display * dpy, Window win)
{
    Window root_return, child_return;
    int dummyi;
    unsigned int dummyu;

    XQueryPointer(dpy, win, &root_return, &child_return,
                  &dummyi, &dummyi, &dummyi, &dummyi, &dummyu);

    return child_return;
}

/*
 * Find client window at pointer location
 *
 * root   is the root window.
 * subwin is the subwindow reported by a ButtonPress event on root.
 *
 * If the WM uses virtual roots subwin may be a virtual root.
 * If so, we descend the window stack at the pointer location and assume the
 * child is the client or one of its WM frame windows.
 * This will of course work only if the virtual roots are children of the real
 * root.
 */
Window
Find_Client(Display * dpy, Window root, Window subwin)
{
    unsigned long *roots;
    unsigned long i, n_roots;
    Window win;

    /* Check if subwin is a virtual root */
    roots = Find_Roots(dpy, root, &n_roots);
    for (i = 0; i < n_roots; i++) {
        if (subwin != roots[i])
            continue;
        win = Find_Child_At_Pointer(dpy, subwin);
        if (win == None)
            return subwin;      /* No child - Return virtual root. */
        subwin = win;
        break;
    }
    if (roots)
        XFree(roots);

    if (atom_wm_state == None) {
        atom_wm_state = XInternAtom(dpy, "WM_STATE", False);
        if (!atom_wm_state)
            return subwin;
    }

    /* Check if subwin has WM_STATE */
    if (Window_Has_Property(dpy, subwin, atom_wm_state))
        return subwin;

    /* Attempt to find a client window in subwin's children */
    win = Find_Client_In_Children(dpy, subwin);
    if (win != None)
        return win;             /* Found a client */

    /* Did not find a client */
    return subwin;
}
