/* ************************************
 * vim:tabstop=4:shiftwidth=4:cindent:fen:fdm=syntax
 * tray.c
 * Tue, 07 Mar 2006 10:36:10 +0600
 * ************************************
 * tray functions
 * ************************************/

#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xmd.h>
#include <X11/Xutil.h>

#ifdef _ST_WITH_XPM
#include <X11/xpm.h>
#endif

#include <unistd.h>

#include "common.h"
#include "embed.h"
#include "icons.h"
#include "image.h"
#include "settings.h"
#include "tray.h"
#include "wmh.h"
#include "xutils.h"

#ifdef _ST_WITH_NATIVE_KDE
#endif

#include "debug.h"

void tray_init()
{
    tray_data.tray = None;
    tray_data.hint_win = None;
    tray_data.dpy = NULL;
    tray_data.terminated = False;
    tray_data.bg_pmap = None;
    tray_data.xa_xrootpmap_id = None;
    tray_data.xa_xsetroot_id = None;
    tray_data.kde_tray_old_mode = 0;

#ifdef _ST_WITH_XINERAMA
    tray_data.xinerama_active = False;
    tray_data.n_monitors = 0;
    tray_data.monitors = NULL;
#endif

#ifdef _ST_WITH_XRANDR
    tray_data.randr_event_base = -1;
#endif

    scrollbars_init();
}

#ifdef _ST_WITH_XPM
int tray_init_pixmap_bg()
{
    XpmAttributes xpma;
    Pixmap mask = None;
    int rc;
    xpma.valuemask = 0;
    rc = XpmReadFileToPixmap(tray_data.dpy, tray_data.tray,
        settings.bg_pmap_path, &tray_data.bg_pmap, &mask, &xpma);
    if (rc != XpmSuccess) {
        DIE(("Could not read background pixmap from %s.\n",
            settings.bg_pmap_path));
        return FAILURE;
    }
    /* Ignore the mask */
    if (mask != None) XFreePixmap(tray_data.dpy, mask);
    tray_data.bg_pmap_dims.x = xpma.height;
    tray_data.bg_pmap_dims.y = xpma.width;
    LOG_TRACE(
        ("created background pixmap (%dx%d)\n", xpma.width, xpma.height));
    return SUCCESS;
}
#endif

/* Mostly from FVWM:fvwm/colorset.c:172 */
Pixmap tray_get_root_pixmap(Atom prop)
{
    Atom type;
    int format;
    unsigned long length, after;
    unsigned char *reteval = NULL;
    int ret;
    Pixmap pix = None;
    Window root_window =
        XRootWindow(tray_data.dpy, DefaultScreen(tray_data.dpy));
    ret = XGetWindowProperty(tray_data.dpy, root_window, prop, 0L, 1L, False,
        XA_PIXMAP, &type, &format, &length, &after, &reteval);
    if (x11_ok() && ret == Success && type == XA_PIXMAP && format == 32
        && length == 1 && after == 0)
        pix = (Pixmap)(*(long *)reteval);
    if (reteval) XFree(reteval);
    return pix;
}

/* Originally from FVWM:fvwm/colorset.c:195 */
int tray_update_root_bg_pmap(Pixmap *pmap, int *width, int *height)
{
    unsigned int w = 0, h = 0;
    int rc = 0;
    XID dummy;
    Pixmap pix = None;
    /* Retrive root image pixmap */
    /* Try _XROOTPMAP_ID first */
    if (tray_data.xa_xrootpmap_id != None)
        pix = tray_get_root_pixmap(tray_data.xa_xrootpmap_id);
    /* Else try _XSETROOT_ID */
    if (!pix && tray_data.xa_xsetroot_id != None)
        pix = tray_get_root_pixmap(tray_data.xa_xsetroot_id);
    /* Get root pixmap size */
    if (pix)
        rc = XGetGeometry(tray_data.dpy, pix, &dummy, (int *)&dummy,
            (int *)&dummy, &w, &h, (unsigned int *)&dummy,
            (unsigned int *)&dummy);
    if (!x11_ok() || !pix || !rc) {
        LOG_TRACE(("could not update root background pixmap\n"));
        return FAILURE;
    }
    *pmap = pix;
    if (width != NULL) *width = w;
    if (height != NULL) *height = h;
    LOG_TRACE(("got new root pixmap: 0x%lx (%ix%i)\n", pix, w, h));
    return SUCCESS;
}

/* XXX: most of Pixmaps are not really needed. Use XImages instead.
 * GCs, XImages and Pixmaps stay allocated during cleanup, valgrind
 * complains. Who cares?
 * TODO: move pixmaps/GCs into tray_data and free them during the exit. */
int tray_update_bg(int update_pixmap)
{
    static Pixmap root_pmap = None;
    static Pixmap bg_pmap = None;
    static Pixmap final_pmap = None;
    static GC root_gc = None;
    static GC bg_gc = None;
    static GC final_gc = None;
    static int old_width = -1, old_height = -1;
    struct Rect clr; /* clipping rectangle */
    struct Rect clr_loc; /* clipping rectangle in local coords */
    XImage *bg_img;
    int bg_pmap_updated = False;
#define make_gc(pmap, gc) \
    do { \
        XGCValues gcv; \
        if (gc != None) XFreeGC(tray_data.dpy, gc); \
        gcv.graphics_exposures = False; \
        gc = XCreateGC(tray_data.dpy, pmap, GCGraphicsExposures, &gcv); \
    } while (0)
#define make_pmap(pmap) \
    do { \
        if (pmap != None) XFreePixmap(tray_data.dpy, pmap); \
        pmap = XCreatePixmap(tray_data.dpy, tray_data.tray, \
            tray_data.xsh.width, tray_data.xsh.height, \
            DefaultDepth(tray_data.dpy, DefaultScreen(tray_data.dpy))); \
    } while (0)
#define recreate_pixmap(pmap, gc) \
    do { \
        make_pmap(pmap); \
        make_gc(pmap, gc); \
    } while (0)
    /* No need to update background if it is a color one */
    if (!settings.transparent && !settings.pixmap_bg) return SUCCESS;
    /* Calculate clipping rect */
    clr.x = cutoff(tray_data.xsh.x, 0, tray_data.root_wnd.width);
    clr.y = cutoff(tray_data.xsh.y, 0, tray_data.root_wnd.height);
    clr.w = cutoff(tray_data.xsh.x + tray_data.xsh.width, 0,
                tray_data.root_wnd.width)
        - clr.x;
    clr.h = cutoff(tray_data.xsh.y + tray_data.xsh.height, 0,
                tray_data.root_wnd.height)
        - clr.y;
    /* There's no need to update background if tray is not visible */
    /* TODO: visibility is better to be tracked using some events */
    if (clr.w == 0 || clr.h == 0) return SUCCESS;
    /* Calculate local clipping rect */
    clr_loc.x = clr.x - tray_data.xsh.x;
    clr_loc.y = clr.y - tray_data.xsh.y;
    clr_loc.w = clr.w;
    clr_loc.h = clr.h;
    if (old_width != tray_data.xsh.width || old_height != tray_data.xsh.height
        || final_pmap == None)
        recreate_pixmap(final_pmap, final_gc);
    /* Update root pixmap if asked and necessary */
    if ((root_pmap == None || update_pixmap)
        && (settings.transparent || settings.fuzzy_edges)) {
        if (!tray_update_root_bg_pmap(&root_pmap, NULL, NULL)) {
            /* More gracefull solution */
            LOG_TRACE(("still waiting for root pixmap\n"));
            return SUCCESS;
        } else
            make_gc(root_pmap, root_gc);
    }
    /* We don't have to update background if only window position has
     * changed unless we depend on root pixmap */
    if (tray_data.xsh.width == old_width && tray_data.xsh.width == old_height
        && !settings.transparent && !settings.fuzzy_edges) {
        return SUCCESS;
    }
    /* If pixmap bg is used, bg_pmap holds tinted and tiled background pixmap,
     * so there's no need to update it unless window size has changed */
    if (settings.pixmap_bg
        && (bg_pmap == None || old_width != tray_data.xsh.width
            || old_height != tray_data.xsh.height)) {
        int i, j;
        recreate_pixmap(bg_pmap, bg_gc);
        for (i = 0; i < tray_data.xsh.width / tray_data.bg_pmap_dims.x + 1;
             i++)
            for (j = 0;
                 j < tray_data.xsh.height / tray_data.bg_pmap_dims.y + 1;
                 j++) {
                XCopyArea(tray_data.dpy, tray_data.bg_pmap, bg_pmap, bg_gc, 0,
                    0, tray_data.bg_pmap_dims.x, tray_data.bg_pmap_dims.y,
                    i * tray_data.bg_pmap_dims.x,
                    j * tray_data.bg_pmap_dims.y);
            }
        bg_pmap_updated = True;
    } else
        /* If root transparency is enabled, it is neccessary to copy portion of
         * root pixmap under the window (root_pmap) to bg_pmap */
        /* XXX: must correctly work around situations when bg pixmap is smaller
           than root window (but how?) */
        if (settings.transparent) {
        recreate_pixmap(bg_pmap, bg_gc);
        XCopyArea(tray_data.dpy, root_pmap, bg_pmap, bg_gc, tray_data.xsh.x,
            tray_data.xsh.y, tray_data.xsh.width, tray_data.xsh.height, 0, 0);
    }
    /* Create an XImage from bg_pmap */
    bg_img = XGetImage(tray_data.dpy, bg_pmap, 0, 0, tray_data.xsh.width,
        tray_data.xsh.height, XAllPlanes(), ZPixmap);
    if (bg_img == NULL) return FAILURE;
    /* Tint the image if necessary. If bg_pmap was not updated, tinting
     * is not needed, since it has been already done */
    if (settings.tint_level
        && ((settings.pixmap_bg && bg_pmap_updated) || settings.transparent)) {
        image_tint(bg_img, &settings.tint_color, settings.tint_level);
        XPutImage(tray_data.dpy, bg_pmap, bg_gc, bg_img, 0, 0, 0, 0,
            tray_data.xsh.width, tray_data.xsh.height);
        bg_pmap_updated = False;
    }
    /* Apply fuzzy edges */
    /* XXX: THIS IS UGLY */
    if (settings.fuzzy_edges) {
        static CARD8 *alpha_mask = NULL;
        /* Portion of root pixmap under the tray */
        XImage *root_img = XGetImage(tray_data.dpy, root_pmap, clr.x, clr.y,
            clr.w, clr.h, XAllPlanes(), ZPixmap);
        /* Proxy structure to work on */
        XImage *tmp_img = NULL;
        static Pixmap tmp_pmap = None;
        static GC tmp_gc = None;
        if (root_img == NULL) {
            LOG_ERROR(
                ("Failed to get image of root pixmap under tray window\n"));
            LOG_TRACE(("Clipping rectangle: %dx%d+%d+%d\n", clr.w, clr.h,
                clr.x, clr.y));
            return x11_ok();
        }
        /* Alpha mask needs to be updated only on size changes */
        if (old_width != tray_data.xsh.width
            || old_height != tray_data.xsh.height) {
            recreate_pixmap(tmp_pmap, tmp_gc);
            if (alpha_mask != NULL) free(alpha_mask);
            alpha_mask = image_create_alpha_mask(settings.fuzzy_edges,
                tray_data.xsh.width, tray_data.xsh.height);
        }
        XPutImage(tray_data.dpy, tmp_pmap, tmp_gc, root_img, clr_loc.x,
            clr_loc.y, 0, 0, clr_loc.w, clr_loc.h);
        tmp_img = XGetImage(tray_data.dpy, tmp_pmap, 0, 0, tray_data.xsh.width,
            tray_data.xsh.height, XAllPlanes(), ZPixmap);
        if (alpha_mask != NULL && tmp_img != NULL)
            image_compose(bg_img, tmp_img, alpha_mask);
        XDestroyImage(root_img);
        if (tmp_img != NULL) XDestroyImage(tmp_img);
    }
    XPutImage(tray_data.dpy, final_pmap, final_gc, bg_img, 0, 0, 0, 0,
        tray_data.xsh.width, tray_data.xsh.height);
    XSetWindowBackgroundPixmap(tray_data.dpy, tray_data.tray, final_pmap);
    XDestroyImage(bg_img);
    old_width = tray_data.xsh.width;
    old_height = tray_data.xsh.height;
    RETURN_STATUS(x11_ok());
}

void tray_refresh_window(int exposures)
{
    LOG_TRACE(("refreshing tray window\n"));
    icon_list_forall(&embedder_refresh);
    x11_refresh_window(tray_data.dpy, tray_data.tray, tray_data.xsh.width,
        tray_data.xsh.height, exposures);
    scrollbars_refresh(exposures);
}

int tray_calc_window_size(
    int base_width, int base_height, int *wnd_width, int *wnd_height)
{
    *wnd_width = base_width;
    *wnd_height = base_height;
    if (settings.scrollbars_mode & SB_MODE_HORZ)
        *wnd_width += settings.scrollbars_size * 2;
    if (settings.scrollbars_mode & SB_MODE_VERT)
        *wnd_height += settings.scrollbars_size * 2;
    return SUCCESS;
}

int tray_calc_tray_area_size(
    int wnd_width, int wnd_height, int *base_width, int *base_height)
{
    *base_width = wnd_width;
    *base_height = wnd_height;
    if (settings.scrollbars_mode & SB_MODE_HORZ)
        *base_width -= settings.scrollbars_size * 2;
    if (settings.scrollbars_mode & SB_MODE_VERT)
        *base_height -= settings.scrollbars_size * 2;
    return SUCCESS;
}

int tray_update_window_strut()
{
    /* Set window strut */
    if (settings.wm_strut_mode != WM_STRUT_NONE) {
        int strut_mode;
        if (settings.wm_strut_mode == WM_STRUT_AUTO) {
            /* Do autodetection: if some edge of tray is adjacent to one
             * of screen edges, we could set window strut to that */
            int h_strut_mode, v_strut_mode;
            int p_strut_mode, s_strut_mode;
            h_strut_mode =
                (tray_data.xsh.x == 0 ? WM_STRUT_LFT
                                      : (tray_data.xsh.x + tray_data.xsh.width
                                                  == tray_data.root_wnd.width
                                              ? WM_STRUT_RHT
                                              : WM_STRUT_NONE));
            v_strut_mode =
                (tray_data.xsh.y == 0 ? WM_STRUT_TOP
                                      : (tray_data.xsh.x + tray_data.xsh.height
                                                  == tray_data.root_wnd.height
                                              ? WM_STRUT_BOT
                                              : WM_STRUT_NONE));
            /* If tray is vertical, horizontal strut mode has higher priority,
             * else vertical strut mode has higher priority */
            if (settings.vertical) {
                p_strut_mode = h_strut_mode;
                s_strut_mode = v_strut_mode;
            } else {
                p_strut_mode = v_strut_mode;
                s_strut_mode = h_strut_mode;
            }
            if (p_strut_mode != WM_STRUT_NONE)
                strut_mode = p_strut_mode;
            else
                strut_mode = s_strut_mode;
        } else
            strut_mode = settings.wm_strut_mode;
        LOG_TRACE(("computed final strut mode: %d\n", strut_mode));
        /* Update respective window hint */
        if (strut_mode != WM_STRUT_NONE) {
            wm_strut_t wm_strut;
            int base_idx;
            memset(wm_strut, 0, sizeof(wm_strut));
            LOG_TRACE(
                ("current tray geometry: %dx%d+%d+%d\n", tray_data.xsh.width,
                    tray_data.xsh.height, tray_data.xsh.x, tray_data.xsh.y));
            if (strut_mode == WM_STRUT_TOP || strut_mode == WM_STRUT_BOT) {
                if (strut_mode == WM_STRUT_TOP) {
                    base_idx = WM_STRUT_IDX_TOP;
                    wm_strut[WM_STRUT_IDX_TOP] =
                        tray_data.xsh.y + tray_data.xsh.height;
                } else {
                    base_idx = WM_STRUT_IDX_BOT;
                    wm_strut[WM_STRUT_IDX_BOT] =
                        tray_data.root_wnd.height - tray_data.xsh.y;
                }
                wm_strut[WM_STRUT_IDX_START(base_idx)] = tray_data.xsh.x;
                wm_strut[WM_STRUT_IDX_END(base_idx)] =
                    tray_data.xsh.x + tray_data.xsh.width - 1;
            } else {
                if (strut_mode == WM_STRUT_LFT) {
                    base_idx = WM_STRUT_IDX_LFT;
                    wm_strut[WM_STRUT_IDX_LFT] =
                        tray_data.xsh.x + tray_data.xsh.width;
                } else {
                    base_idx = WM_STRUT_IDX_RHT;
                    wm_strut[WM_STRUT_IDX_RHT] =
                        tray_data.root_wnd.width - tray_data.xsh.x;
                }
                wm_strut[WM_STRUT_IDX_START(base_idx)] = tray_data.xsh.y;
                wm_strut[WM_STRUT_IDX_END(base_idx)] =
                    tray_data.xsh.y + tray_data.xsh.height - 1;
            }
            {
                int i;
                for (i = 0; i < _NET_WM_STRUT_PARTIAL_SZ; i++)
                    LOG_TRACE(("computed hints [%d] = %lu\n", i, wm_strut[i]));
            }
            ewmh_set_window_strut(tray_data.dpy, tray_data.tray, wm_strut);
        }
    }
    return SUCCESS;
}

int tray_update_window_props()
{
    XSizeHints xsh;
    XWindowAttributes xwa;
    Window child;
    int cur_base_width, cur_base_height;
    int new_width, new_height;
    int layout_width, layout_height;
    /* Phase 1: calculate new tray window dimensions.
     * Algorithm summary:
     * new_dims =
     *      if (layout_dims > max_dims) max_dims;
     * else if ((shrink_back && layout_dims > orig_dims) ||
     *          (layout_dims > current_dims)) layout_dims;
     * else if (shrink_back) orig_dims;
     * else                  current_dims;
     */
    /* Read the window's size and absolute position from a single
     * XGetWindowAttributes snapshot. Reading them separately (the size here,
     * the position later in phase 2) let a resize we had just requested apply
     * between the two reads, producing an inconsistent size/position pair: the
     * gravity shift below would then run a second time and the tray would jump
     * by one icon width on an otherwise no-op update -- e.g. an icon's
     * dock-confirmation message right after it was added. */
    XGetWindowAttributes(tray_data.dpy, tray_data.tray, &xwa);
    XTranslateCoordinates(tray_data.dpy, tray_data.tray, xwa.root, 0, 0,
        &tray_data.xsh.x, &tray_data.xsh.y, &child);
    tray_data.xsh.width = xwa.width;
    tray_data.xsh.height = xwa.height;
    layout_get_size(&layout_width, &layout_height);
    LOG_TRACE(("layout geometry: %dx%d\n", layout_width, layout_height));
    tray_calc_tray_area_size(tray_data.xsh.width, tray_data.xsh.height,
        &cur_base_width, &cur_base_height);
    LOG_TRACE(
        ("base tray geometry: %dx%d\n", cur_base_width, cur_base_height));
    LOG_TRACE(("orig tray geometry: %dx%d\n", settings.orig_tray_dims.x,
        settings.orig_tray_dims.y));
    LOG_TRACE(("max tray geometry: %dx%d\n", settings.max_tray_dims.x,
        settings.max_tray_dims.y));
#define CALC_DIM(tgt, cur, layout, max, orig) \
    if (layout > max) \
        tgt = max; \
    else if ((settings.shrink_back_mode && layout > orig) || layout > cur) \
        tgt = layout; \
    else if (settings.shrink_back_mode) \
        tgt = orig; \
    else \
        tgt = cur;
    CALC_DIM(new_width, cur_base_width, layout_width, settings.max_tray_dims.x,
        settings.orig_tray_dims.x);
    CALC_DIM(new_height, cur_base_height, layout_height,
        settings.max_tray_dims.y, settings.orig_tray_dims.y);
    LOG_TRACE(("new base tray geometry: %dx%d\n", new_width, new_height));
    tray_calc_window_size(new_width, new_height, &new_width, &new_height);
    LOG_TRACE(("new tray geometry: %dx%d\n", new_width, new_height));
#if 1
    /* Not sure if this is really necessary */
    xsh.x = tray_data.xsh.x;
    xsh.y = tray_data.xsh.y;
    xsh.width = new_width;
    xsh.height = new_height;
#endif
    xsh.min_width = new_width;
    xsh.min_height = new_height;
    xsh.max_width = new_width;
    xsh.max_height = new_height;
    xsh.width_inc = settings.slot_size.x;
    xsh.height_inc = settings.slot_size.y;
    tray_calc_window_size(0, 0, &xsh.base_width, &xsh.base_height);
    xsh.win_gravity = NorthWestGravity;
    xsh.flags = tray_data.xsh.flags;
    XSetWMNormalHints(tray_data.dpy, tray_data.tray, &xsh);
    /* Phase 2: set new window size
     * This check helps to avod extra (erroneous) moves of the window when
     * geometry changes are not updated yet, but tray_update_window_props() was
     * called once again */
    if (new_width != tray_data.xsh.width
        || new_height != tray_data.xsh.height) {
        /* Apparently, not every WM (hello, WindowMaker!) handles gravity the
         * way I have expected (i.e. using it to calculate reference point as
         * described in ICCM/WM specs). Perhaps, I was dreaming.  So, prior to
         * resizing trays window, it is necessary to recalculate window
         * absolute position and shift it according to grow gravity settings.
         * The absolute position was captured together with the size in phase 1
         * (above), so the shift stays consistent with the size we compare
         * against. */
        LOG_TRACE(("old tray window geometry: %dx%d+%d+%d\n", new_width,
            new_height, tray_data.xsh.x, tray_data.xsh.y));
        if (settings.grow_gravity & GRAV_E)
            tray_data.xsh.x -= new_width - tray_data.xsh.width;
        else if (!(settings.grow_gravity & GRAV_H))
            tray_data.xsh.x -= (new_width - tray_data.xsh.width) / 2;
        if (settings.grow_gravity & GRAV_S)
            tray_data.xsh.y -= new_height - tray_data.xsh.height;
        else if (!(settings.grow_gravity & GRAV_V))
            tray_data.xsh.y -= (new_height - tray_data.xsh.height) / 2;
        tray_data.xsh.width = new_width;
        tray_data.xsh.height = new_height;
        LOG_TRACE(("new tray window geometry: %dx%d+%d+%d\n", new_width,
            new_height, tray_data.xsh.x, tray_data.xsh.y));
        XResizeWindow(tray_data.dpy, tray_data.tray, new_width, new_height);
        XMoveWindow(
            tray_data.dpy, tray_data.tray, tray_data.xsh.x, tray_data.xsh.y);
        if (!x11_ok()) {
            LOG_TRACE(("could not update tray window geometry\n"));
            return FAILURE;
        }
    } else {
        /* XXX: Why do we need this again? */
        XResizeWindow(tray_data.dpy, tray_data.tray, tray_data.xsh.width,
            tray_data.xsh.height);
    }
    tray_update_window_strut();
    scrollbars_update();
    return SUCCESS;
}

void tray_create_window(int argc, char **argv)
{
    XTextProperty wm_name;
    XSetWindowAttributes xswa;
    XClassHint xch;
    XWMHints xwmh;
    Atom net_system_tray_orientation;
    Atom orient;
    Atom protocols_atoms[3];
    /* Create some atoms */
    tray_data.xa_wm_delete_window =
        XInternAtom(tray_data.dpy, "WM_DELETE_WINDOW", False);
    tray_data.xa_net_wm_ping =
        XInternAtom(tray_data.dpy, "_NET_WM_PING", False);
    tray_data.xa_wm_take_focus =
        XInternAtom(tray_data.dpy, "WM_TAKE_FOCUS", False);
    tray_data.xa_wm_protocols =
        XInternAtom(tray_data.dpy, "WM_PROTOCOLS", False);
    tray_data.xa_kde_net_system_tray_windows =
        XInternAtom(tray_data.dpy, "_KDE_NET_SYSTEM_TRAY_WINDOWS", False);
    tray_data.xa_net_client_list =
        XInternAtom(tray_data.dpy, "_NET_CLIENT_LIST", False);
    /* Create tray window */
    tray_data.tray = XCreateSimpleWindow(tray_data.dpy,
        DefaultRootWindow(tray_data.dpy), tray_data.xsh.x, tray_data.xsh.y,
        tray_data.xsh.width, tray_data.xsh.height, 0, settings.bg_color.pixel,
        settings.bg_color.pixel);
    LOG_TRACE(("created tray window: 0x%lx\n", tray_data.tray));
    if (settings.dockapp_mode == DOCKAPP_WMAKER) {
        tray_data.hint_win = XCreateSimpleWindow(tray_data.dpy,
            DefaultRootWindow(tray_data.dpy), 0, 0, 1, 1, 0,
            settings.bg_color.pixel, settings.bg_color.pixel);
        LOG_TRACE(("created hint_win window: 0x%lx\n", tray_data.hint_win));
    }
    /* Set tray window properties */
    xswa.bit_gravity = settings.bit_gravity;
    xswa.win_gravity = settings.win_gravity;
    xswa.backing_store = settings.parent_bg ? NotUseful : WhenMapped;
    XChangeWindowAttributes(tray_data.dpy, tray_data.tray,
        CWBitGravity | CWWinGravity | CWBackingStore, &xswa);
    {
        /* XXX: use XStoreName ?*/
        int numtries = 0;
        /* First, try user-supplied value */
        while (1) {
            if (XmbTextListToTextProperty(
                    tray_data.dpy, &settings.wnd_name, 1, XTextStyle, &wm_name)
                != Success)
                /* Retry with default value */
                settings.wnd_name = PROGNAME;
            else
                break;
            if (numtries++ > 1)
                DIE(("Invalid window name \"%s\"\n", settings.wnd_name));
        }
    }
    XSetWMName(tray_data.dpy, tray_data.tray, &wm_name);
    XFree(wm_name.value);
    /* Setup class hints */
    xch.res_class = PROGNAME;
    xch.res_name = PROGNAME;
    /* Setup window manager hints */
    xwmh.flags = StateHint | InputHint;
    xwmh.input = False;
    xwmh.initial_state =
        settings.dockapp_mode != DOCKAPP_NONE ? WithdrawnState : NormalState;
    /* Apply hints */
    XSetWMHints(tray_data.dpy, tray_data.tray, &xwmh);
    XSetClassHint(tray_data.dpy, tray_data.tray, &xch);
    XSetWMNormalHints(tray_data.dpy, tray_data.tray, &tray_data.xsh);
    XSetCommand(tray_data.dpy, tray_data.tray, argv, argc);
    /* Set properties of hint window if WindowMaker dockapp mode enabled */
    if (settings.dockapp_mode == DOCKAPP_WMAKER) {
        xwmh.flags |= IconWindowHint | IconPositionHint | WindowGroupHint;
        xwmh.initial_state = WithdrawnState;
        xwmh.icon_x = tray_data.xsh.x;
        xwmh.icon_y = tray_data.xsh.y;
        xwmh.icon_window = tray_data.tray;
        xwmh.window_group = tray_data.hint_win;
        XSetClassHint(tray_data.dpy, tray_data.hint_win, &xch);
        XSetWMHints(tray_data.dpy, tray_data.hint_win, &xwmh);
    }
    /* v0.2 tray protocol support */
    orient = settings.vertical ? _NET_SYSTEM_TRAY_ORIENTATION_HORZ
                               : _NET_SYSTEM_TRAY_ORIENTATION_VERT;
    net_system_tray_orientation =
        XInternAtom(tray_data.dpy, TRAY_ORIENTATION_ATOM, False);
    XChangeProperty(tray_data.dpy, tray_data.tray, net_system_tray_orientation,
        net_system_tray_orientation, 32, PropModeReplace,
        (unsigned char *)&orient, 1);
    /* Ask X server / WM to report certain events */
    protocols_atoms[0] = tray_data.xa_wm_delete_window;
    protocols_atoms[1] = tray_data.xa_wm_take_focus;
    protocols_atoms[2] = tray_data.xa_net_wm_ping;
    XSetWMProtocols(tray_data.dpy, tray_data.tray, protocols_atoms, 3);
    XSelectInput(tray_data.dpy, tray_data.tray,
        StructureNotifyMask | FocusChangeMask | PropertyChangeMask
            | ExposureMask);
    x11_extend_root_event_mask(tray_data.dpy, PropertyChangeMask);
    scrollbars_create();
    /* Set tray window background if necessary */
#ifdef _ST_WITH_XPM
    if (settings.pixmap_bg) tray_init_pixmap_bg();
#endif
    if (settings.parent_bg)
        XSetWindowBackgroundPixmap(
            tray_data.dpy, tray_data.tray, ParentRelative);
    else if (settings.transparent || settings.fuzzy_edges) {
        tray_data.xa_xrootpmap_id =
            XInternAtom(tray_data.dpy, "_XROOTPMAP_ID", False);
        tray_data.xa_xsetroot_id =
            XInternAtom(tray_data.dpy, "_XSETROOT_ID", False);
    }
    tray_update_bg(True);
}

void tray_create_phony_window()
{
    /* Create fake tray window */
    tray_data.tray = XCreateSimpleWindow(
        tray_data.dpy, DefaultRootWindow(tray_data.dpy), 0, 0, 1, 1, 0, 0, 0);
    /* Select for PropertyNotify so that x11_get_server_timestamp() works */
    XSelectInput(tray_data.dpy, tray_data.tray, PropertyChangeMask);
}

int tray_set_wm_hints()
{
    int mwm_decor = 0;
    if (settings.deco_flags & DECO_TITLE)
        mwm_decor |= MWM_DECOR_TITLE | MWM_DECOR_MENU;
    if (settings.deco_flags & DECO_BORDER)
        mwm_decor |= MWM_DECOR_RESIZEH | MWM_DECOR_BORDER;
    mwm_set_hints(tray_data.dpy, tray_data.tray, mwm_decor, MWM_FUNC_ALL);
    if (settings.sticky) {
        ewmh_add_window_state(
            tray_data.dpy, tray_data.tray, _NET_WM_STATE_STICKY);
        ewmh_set_window_atom32(
            tray_data.dpy, tray_data.tray, _NET_WM_DESKTOP, 0xFFFFFFFF);
    }
    if (settings.skip_taskbar)
        ewmh_add_window_state(
            tray_data.dpy, tray_data.tray, _NET_WM_STATE_SKIP_TASKBAR);
    if (settings.wnd_layer != NULL)
        ewmh_add_window_state(
            tray_data.dpy, tray_data.tray, settings.wnd_layer);
    if (strcmp(settings.wnd_type, _NET_WM_WINDOW_TYPE_NORMAL) != 0)
        ewmh_add_window_type(tray_data.dpy, tray_data.tray, settings.wnd_type);
    /* Alwas add NORMAL window type for WM that do not support (some) special
     * types */
    ewmh_add_window_type(
        tray_data.dpy, tray_data.tray, _NET_WM_WINDOW_TYPE_NORMAL);
    return SUCCESS;
}

void tray_init_selection_atoms()
{
    static char *tray_sel_atom_name = NULL;
    /* Obtain selection atom name basing on current screen number */
    if (tray_sel_atom_name == NULL) {
        tray_sel_atom_name = (char *)malloc(strlen(TRAY_SEL_ATOM) + 10);
        if (tray_sel_atom_name == NULL)
            DIE_OOM(("could not allocate memory for selection atom name\n"));
        snprintf(tray_sel_atom_name, strlen(TRAY_SEL_ATOM) + 10, "%s%u",
            TRAY_SEL_ATOM, DefaultScreen(tray_data.dpy));
    }
    LOG_TRACE(("tray_sel_atom_name=%s\n", tray_sel_atom_name));
    /* Initialize atom values */
    tray_data.xa_tray_selection =
        XInternAtom(tray_data.dpy, tray_sel_atom_name, False);
    tray_data.xa_tray_opcode =
        XInternAtom(tray_data.dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
    tray_data.xa_tray_data =
        XInternAtom(tray_data.dpy, "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
}

void tray_acquire_selection()
{
    Time timestamp = x11_get_server_timestamp(tray_data.dpy, tray_data.tray);
    tray_init_selection_atoms();
    /* Save old selection owner */
    tray_data.old_selection_owner =
        XGetSelectionOwner(tray_data.dpy, tray_data.xa_tray_selection);
    LOG_TRACE(("old selection owner: 0x%lx\n", tray_data.old_selection_owner));
    /* Acquire selection */
    XSetSelectionOwner(
        tray_data.dpy, tray_data.xa_tray_selection, tray_data.tray, timestamp);
    /* Check if we have really got the selection */
    if (XGetSelectionOwner(tray_data.dpy, tray_data.xa_tray_selection)
        != tray_data.tray) {
        DIE(("could not set selection owner.\nMay be another (greedy) tray "
             "running?\n"));
    } else {
        tray_data.is_active = True;
        LOG_TRACE(("ok, got _NET_SYSTEM_TRAY selection\n"));
    }
    /* Send the message notifying about new MANAGER */
    x11_send_client_msg32(tray_data.dpy, DefaultRootWindow(tray_data.dpy),
        DefaultRootWindow(tray_data.dpy),
        XInternAtom(tray_data.dpy, "MANAGER", False), timestamp,
        tray_data.xa_tray_selection, tray_data.tray, 0, 0);
}

void tray_show_window()
{
    tray_set_wm_hints();
    tray_update_window_props();
    XMapRaised(tray_data.dpy, tray_data.tray);
    if (settings.dockapp_mode == DOCKAPP_NONE)
        XMoveWindow(
            tray_data.dpy, tray_data.tray, tray_data.xsh.x, tray_data.xsh.y);
    if (settings.dockapp_mode == DOCKAPP_WMAKER)
        XMapWindow(tray_data.dpy, tray_data.hint_win);
    /* XXX: I do not why, but for some WM it is
     * required to set hints / window properties
     * after and before window creation */
    /* TODO: check if this is really necessary */
    tray_set_wm_hints();
    tray_update_window_props();
}
