/* Xlib utils */

/*
 * Copyright (C) 2001 Havoc Pennington
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <stdio.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include <libxfce4util/libxfce4util.h>

#include "netk-xutils.h"
#include "netk-screen.h"
#include "netk-private.h"
#include "netk-inlinepixbufs.h"
#include "icons.h"

#ifndef XUrgencyHint
#define XUrgencyHint (1L << 8)
#endif

gboolean
p_netk_get_cardinal (Window xwindow, Atom atom, int *val)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    gulong *num;
    int err, result;

    *val = 0;

    p_netk_error_trap_push ();
    type = None;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            XA_CARDINAL, &type, &format, &nitems,
                            &bytes_after, (guchar **)&num);
    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return FALSE;

    if (type != XA_CARDINAL)
    {
        XFree (num);
        return FALSE;
    }

    *val = *num;

    XFree (num);

    return TRUE;
}

int
p_netk_get_wm_state (Window xwindow)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    gulong *num;
    int err, result;
    Atom wm_state;
    int retval;

    wm_state = p_netk_atom_get ("WM_STATE");
    retval = NormalState;

    p_netk_error_trap_push ();
    type = None;
    result =
        XGetWindowProperty (gdk_display, xwindow, wm_state, 0, G_MAXLONG,
                            False, wm_state, &type, &format, &nitems,
                            &bytes_after, (guchar **)&num);
    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return retval;

    if (type != wm_state)
    {
        XFree (num);
        return retval;
    }

    retval = *num;

    XFree (num);

    return retval;
}

gboolean
p_netk_get_window (Window xwindow, Atom atom, Window * val)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    Window *w;
    int err, result;

    *val = 0;

    p_netk_error_trap_push ();
    type = None;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            XA_WINDOW, &type, &format, &nitems, &bytes_after,
                            (guchar **)&w);
    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return FALSE;

    if (type != XA_WINDOW)
    {
        XFree (w);
        return FALSE;
    }

    *val = *w;

    XFree (w);

    return TRUE;
}

gboolean
p_netk_get_pixmap (Window xwindow, Atom atom, Pixmap * val)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    Window *w;
    int err, result;

    *val = 0;

    p_netk_error_trap_push ();
    type = None;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            XA_PIXMAP, &type, &format, &nitems, &bytes_after,
                            (guchar **)&w);
    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return FALSE;

    if (type != XA_PIXMAP)
    {
        XFree (w);
        return FALSE;
    }

    *val = *w;

    XFree (w);

    return TRUE;
}

gboolean
p_netk_get_atom (Window xwindow, Atom atom, Atom * val)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    Atom *a;
    int err, result;

    *val = 0;

    p_netk_error_trap_push ();
    type = None;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            XA_ATOM, &type, &format, &nitems, &bytes_after,
                            (guchar **)&a);
    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return FALSE;

    if (type != XA_ATOM)
    {
        XFree (a);
        return FALSE;
    }

    *val = *a;

    XFree (a);

    return TRUE;
}

static char *
text_property_to_utf8 (const XTextProperty * prop)
{
    char **list;
    int count;
    char *retval;

    list = NULL;

    count =
        gdk_text_property_to_utf8_list (gdk_x11_xatom_to_atom
                                        (prop->encoding), prop->format,
                                        prop->value, prop->nitems, &list);

    if (count == 0)
        return NULL;

    retval = list[0];
    list[0] = g_strdup ("");    /* something to free */

    g_strfreev (list);

    return retval;
}

char *
p_netk_get_text_property (Window xwindow, Atom atom)
{
    XTextProperty text;
    char *retval;

    p_netk_error_trap_push ();

    text.nitems = 0;
    if (XGetTextProperty (gdk_display, xwindow, &text, atom))
    {
        retval = text_property_to_utf8 (&text);
        if (retval)
        {
            xfce_utf8_remove_controls((gchar *) retval, -1, NULL);
        }
        if (text.value)
            XFree (text.value);
    }
    else
    {
        retval = NULL;
    }

    p_netk_error_trap_pop ();

    return retval;
}

char *
p_netk_get_string_property_latin1 (Window xwindow, Atom atom)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    guchar *str;
    int err, result;
    char *retval;

    p_netk_error_trap_push ();
    str = NULL;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            XA_STRING, &type, &format, &nitems, &bytes_after,
                            (guchar **)&str);

    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return NULL;

    if (type != XA_STRING)
    {
        XFree (str);
        return NULL;
    }

    retval = g_strdup ((gchar*) str);

    XFree (str);

    return retval;
}

char *
p_netk_get_utf8_property (Window xwindow, Atom atom)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    guchar *val;
    int err, result;
    char *retval;
    Atom utf8_string;

    utf8_string = p_netk_atom_get ("UTF8_STRING");

    p_netk_error_trap_push ();
    type = None;
    val = NULL;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            utf8_string, &type, &format, &nitems,
                            &bytes_after, (guchar **)&val);
    err = p_netk_error_trap_pop ();

    if (err != Success || result != Success)
        return NULL;

    if (type != utf8_string || format != 8 || nitems == 0 || !val)
    {
        if (val)
            XFree (val);
        return NULL;
    }

    if (!g_utf8_validate ((gchar*) val, nitems, NULL))
    {
        /* Property contained invalid UTF-8 */
        XFree (val);
        return NULL;
    }

    xfce_utf8_remove_controls((gchar *) val, nitems, NULL);
    retval = g_strndup ((gchar*) val, nitems);

    XFree (val);

    return retval;
}

gboolean
p_netk_get_window_list (Window xwindow, Atom atom, Window ** windows,
                        int *len)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    Window *data;
    int err, result;

    *windows = NULL;
    *len = 0;

    p_netk_error_trap_push ();
    type = None;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            XA_WINDOW, &type, &format, &nitems, &bytes_after,
                            (guchar **)&data);
    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return FALSE;

    if (type != XA_WINDOW)
    {
        XFree (data);
        return FALSE;
    }

    *windows = g_new (Window, nitems);
    memcpy (*windows, data, sizeof (Window) * nitems);
    *len = nitems;

    XFree (data);

    return TRUE;
}

gboolean
p_netk_get_atom_list (Window xwindow, Atom atom, Atom ** atoms, int *len)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    Atom *data;
    int err, result;

    *atoms = NULL;
    *len = 0;

    p_netk_error_trap_push ();
    type = None;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            XA_ATOM, &type, &format, &nitems, &bytes_after,
                            (guchar **)&data);
    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return FALSE;

    if (type != XA_ATOM)
    {
        XFree (data);
        return FALSE;
    }

    *atoms = g_new (Atom, nitems);
    memcpy (*atoms, data, sizeof (Atom) * nitems);
    *len = nitems;

    XFree (data);

    return TRUE;
}

gboolean
p_netk_get_cardinal_list (Window xwindow, Atom atom, gulong ** cardinals,
                          int *len)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    gulong *nums;
    int err, result;

    *cardinals = NULL;
    *len = 0;

    p_netk_error_trap_push ();
    type = None;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            XA_CARDINAL, &type, &format, &nitems,
                            &bytes_after, (guchar **)&nums);
    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return FALSE;

    if (type != XA_CARDINAL)
    {
        XFree (nums);
        return FALSE;
    }

    *cardinals = g_new (gulong, nitems);
    memcpy (*cardinals, nums, sizeof (gulong) * nitems);
    *len = nitems;

    XFree (nums);

    return TRUE;
}

char **
p_netk_get_utf8_list (Window xwindow, Atom atom)
{
    Atom type;
    int format;
    gulong i, nitems;
    gulong bytes_after;
    gchar *val;
    int err, result;
    Atom utf8_string;
    char **retval;
    gulong n_strings;
    gchar *p;

    utf8_string = p_netk_atom_get ("UTF8_STRING");

    p_netk_error_trap_push ();
    type = None;
    val = NULL;
    result =
        XGetWindowProperty (gdk_display, xwindow, atom, 0, G_MAXLONG, False,
                            utf8_string, &type, &format, &nitems,
                            &bytes_after, (guchar **)&val);
    err = p_netk_error_trap_pop ();

    if (err != Success || result != Success || !val)
        return NULL;

    if (type != utf8_string || format != 8 || nitems == 0)
    {
        if (val)
            XFree (val);
        return NULL;
    }

    /* I'm not sure this is right, but I'm guessing the
     * property is nul-separated
     */
    i = 0;
    n_strings = 0;
    while (i < nitems)
    {
        if (val[i] == '\0')
            ++n_strings;
        ++i;
    }

    if (val[nitems - 1] != '\0')
    {
        ++n_strings;
    }

    /* we're guaranteed that val has a nul on the end
     * by XGetWindowProperty
     */

    retval = g_new0 (char *, n_strings + 1);

    p = val;
    i = 0;
    while (i < n_strings)
    {
        if (!g_utf8_validate (p, -1, NULL))
        {
            /* Property contained invalid UTF-8 */
            XFree (val);
            g_strfreev (retval);
            return NULL;
        }

        xfce_utf8_remove_controls((gchar *) p, -1, NULL);
        retval[i] = g_strdup (p);

        p = p + strlen (p) + 1;
        ++i;
    }

    XFree (val);

    return retval;
}

void
p_netk_set_utf8_list (Window xwindow, Atom atom, char **list)
{
    Atom utf8_string;
    GString *flattened;
    int i;

    utf8_string = p_netk_atom_get ("UTF8_STRING");

    /* flatten to nul-separated list */
    flattened = g_string_new ("");
    i = 0;
    while (list[i] != NULL)
    {
        g_string_append_len (flattened, list[i], strlen (list[i]) + 1);
        ++i;
    }

    p_netk_error_trap_push ();

    XChangeProperty (gdk_display, xwindow, atom, utf8_string, 8,
                     PropModeReplace, (unsigned char*) flattened->str, flattened->len);

    p_netk_error_trap_pop ();

    g_string_free (flattened, TRUE);
}

void
p_netk_error_trap_push (void)
{
    gdk_error_trap_push ();
}

int
p_netk_error_trap_pop (void)
{
    XSync (gdk_display, False);
    return gdk_error_trap_pop ();
}

static GHashTable *atom_hash = NULL;
static GHashTable *reverse_atom_hash = NULL;

Atom
p_netk_atom_get (const char *atom_name)
{
    Atom retval;

    g_return_val_if_fail (atom_name != NULL, None);

    if (!atom_hash)
    {
        atom_hash = g_hash_table_new (g_str_hash, g_str_equal);
        reverse_atom_hash = g_hash_table_new (NULL, NULL);
    }

    retval = GPOINTER_TO_UINT (g_hash_table_lookup (atom_hash, atom_name));
    if (!retval)
    {
        retval = XInternAtom (gdk_display, atom_name, FALSE);

        if (retval != None)
        {
            char *name_copy;

            name_copy = g_strdup (atom_name);

            g_hash_table_insert (atom_hash, name_copy,
                                 GUINT_TO_POINTER (retval));
            g_hash_table_insert (reverse_atom_hash, GUINT_TO_POINTER (retval),
                                 name_copy);
        }
    }

    return retval;
}

const char *
p_netk_atom_name (Atom atom)
{
    if (reverse_atom_hash)
        return g_hash_table_lookup (reverse_atom_hash,
                                    GUINT_TO_POINTER (atom));
    else
        return NULL;
}

static GdkFilterReturn
filter_func (GdkXEvent * gdkxevent, GdkEvent * event, gpointer data)
{
    XEvent *xevent = gdkxevent;

    switch (xevent->type)
    {
        case PropertyNotify:
            {
                NetkScreen *screen;

                screen = netk_screen_get_for_root (xevent->xany.window);
                if (screen != NULL)
                    p_netk_screen_process_property_notify (screen, xevent);
                else
                {
                    NetkWindow *window;
                    NetkApplication *app;

                    window = netk_window_get (xevent->xany.window);
                    app = netk_application_get (xevent->xany.window);

                    if (app)
                        p_netk_application_process_property_notify (app,
                                                                    xevent);

                    if (window)
                        p_netk_window_process_property_notify (window,
                                                               xevent);
                }
            }
            break;

        case ConfigureNotify:
            {
                NetkWindow *window;

                window = netk_window_get (xevent->xconfigure.window);

                if (window)
                    p_netk_window_process_configure_notify (window, xevent);
            }
            break;

        case SelectionClear:
            {
                p_netk_desktop_layout_manager_process_event (xevent);
            }
            break;
    }

    return GDK_FILTER_CONTINUE;
}

void
p_netk_event_filter_init (void)
{
    static gboolean initialized = FALSE;

    if (!initialized)
    {
        gdk_window_add_filter (NULL, filter_func, NULL);
        initialized = TRUE;
    }
}

int
p_netk_xid_equal (gconstpointer v1, gconstpointer v2)
{
    return *((const gulong *) v1) == *((const gulong *) v2);
}

guint
p_netk_xid_hash (gconstpointer v)
{
    gulong val = *(const gulong *) v;

    /* I'm not sure this works so well. */
#if G_SIZEOF_LONG > 4
    return (guint) (val ^ (val >> 32));
#else
    return val;
#endif
}

void
p_netk_iconify (Window xwindow)
{
    p_netk_error_trap_push ();
    XIconifyWindow (gdk_display, xwindow, DefaultScreen (gdk_display));
    p_netk_error_trap_pop ();
}

void
p_netk_deiconify (Window xwindow)
{
    /* We need special precautions, because GDK doesn't like
     * XMapWindow() called on its windows, need to use the
     * GDK functions
     */
    GdkWindow *gdkwindow;

    gdkwindow = gdk_xid_table_lookup (xwindow);

    p_netk_error_trap_push ();
    if (gdkwindow)
        gdk_window_show (gdkwindow);
    else
        XMapRaised (gdk_display, xwindow);
    p_netk_error_trap_pop ();
}

void
p_netk_close (Screen * screen, Window xwindow)
{
    XEvent xev;

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.display = gdk_display;
    xev.xclient.window = xwindow;
    xev.xclient.message_type = p_netk_atom_get ("_NET_CLOSE_WINDOW");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = 0;
    xev.xclient.data.l[1] = 0;
    xev.xclient.data.l[2] = 0;
    xev.xclient.data.l[3] = 0;

    XSendEvent (gdk_display, RootWindowOfScreen (screen), False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT      0
#define _NET_WM_MOVERESIZE_SIZE_TOP          1
#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT     2
#define _NET_WM_MOVERESIZE_SIZE_RIGHT        3
#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT  4
#define _NET_WM_MOVERESIZE_SIZE_BOTTOM       5
#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT   6
#define _NET_WM_MOVERESIZE_SIZE_LEFT         7
#define _NET_WM_MOVERESIZE_MOVE              8
#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD     9
#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD    10

void
p_netk_keyboard_move (Screen * screen, Window xwindow)
{
    XEvent xev;

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.display = gdk_display;
    xev.xclient.window = xwindow;
    xev.xclient.message_type = p_netk_atom_get ("_NET_WM_MOVERESIZE");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = 0;  /* unused */
    xev.xclient.data.l[1] = 0;  /* unused */
    xev.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE_KEYBOARD;
    xev.xclient.data.l[3] = 0;  /* unused */

    XSendEvent (gdk_display, RootWindowOfScreen (screen), False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

void
p_netk_keyboard_size (Screen * screen, Window xwindow)
{
    XEvent xev;

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.display = gdk_display;
    xev.xclient.window = xwindow;
    xev.xclient.message_type = p_netk_atom_get ("_NET_WM_MOVERESIZE");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = 0;  /* unused */
    xev.xclient.data.l[1] = 0;  /* unused */
    xev.xclient.data.l[2] = _NET_WM_MOVERESIZE_SIZE_KEYBOARD;
    xev.xclient.data.l[3] = 0;  /* unused */
    

    XSendEvent (gdk_display, RootWindowOfScreen (screen), False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

void
p_netk_change_state (Screen * screen, Window xwindow, gboolean add,
                     Atom state1, Atom state2)
{
    XEvent xev;

#define _NET_WM_STATE_REMOVE        0   /* remove/unset property */
#define _NET_WM_STATE_ADD           1   /* add/set property */
#define _NET_WM_STATE_TOGGLE        2   /* toggle property  */

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.display = gdk_display;
    xev.xclient.window = xwindow;
    xev.xclient.message_type = p_netk_atom_get ("_NET_WM_STATE");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
    xev.xclient.data.l[1] = state1;
    xev.xclient.data.l[2] = state2;
    xev.xclient.data.l[3] = 0;

    XSendEvent (gdk_display, RootWindowOfScreen (screen), False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

void
p_netk_change_workspace (Screen * screen, Window xwindow, int new_space)
{
    XEvent xev;

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.display = gdk_display;
    xev.xclient.window = xwindow;
    xev.xclient.message_type = p_netk_atom_get ("_NET_WM_DESKTOP");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = new_space;
    xev.xclient.data.l[1] = 0;
    xev.xclient.data.l[2] = 0;
    xev.xclient.data.l[3] = 0;

    XSendEvent (gdk_display, RootWindowOfScreen (screen), False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

void
p_netk_activate (Screen * screen, Window xwindow)
{
    XEvent xev;

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.display = gdk_display;
    xev.xclient.window = xwindow;
    xev.xclient.message_type = p_netk_atom_get ("_NET_ACTIVE_WINDOW");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = 0;
    xev.xclient.data.l[1] = 0;
    xev.xclient.data.l[2] = 0;
    xev.xclient.data.l[3] = 0;

    XSendEvent (gdk_display, RootWindowOfScreen (screen), False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

void
p_netk_activate_workspace (Screen * screen, int new_active_space)
{
    XEvent xev;

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.display = gdk_display;
    xev.xclient.window = RootWindowOfScreen (screen);
    xev.xclient.message_type = p_netk_atom_get ("_NET_CURRENT_DESKTOP");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = new_active_space;
    xev.xclient.data.l[1] = 0;
    xev.xclient.data.l[2] = 0;
    xev.xclient.data.l[3] = 0;

    XSendEvent (gdk_display, RootWindowOfScreen (screen), False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

void
p_netk_change_viewport (Screen * screen, int x, int y)
{
    XEvent xev;

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.display = gdk_display;
    xev.xclient.window = RootWindowOfScreen (screen);
    xev.xclient.message_type = p_netk_atom_get ("_NET_DESKTOP_VIEWPORT");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = x;
    xev.xclient.data.l[1] = y;
    xev.xclient.data.l[2] = 0;
    xev.xclient.data.l[3] = 0;

    XSendEvent (gdk_display,
                RootWindowOfScreen (screen),
                False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

void
p_netk_toggle_showing_desktop (Screen * screen, gboolean show)
{
    XEvent xev;

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.display = DisplayOfScreen (screen);
    xev.xclient.window = RootWindowOfScreen (screen);
    xev.xclient.message_type = p_netk_atom_get ("_NET_SHOWING_DESKTOP");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = show != FALSE;
    xev.xclient.data.l[1] = 0;
    xev.xclient.data.l[2] = 0;
    xev.xclient.data.l[3] = 0;

    XSendEvent (DisplayOfScreen (screen),
                RootWindowOfScreen (screen),
                False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

Window
p_netk_get_group_leader (Window xwindow)
{
    XWMHints *hints;
    Window ret;

    p_netk_error_trap_push ();
    hints = XGetWMHints (gdk_display, xwindow);
    p_netk_error_trap_pop ();

    /* default to window being its own leader */
    ret = xwindow;

    if (hints && (hints->flags & WindowGroupHint))
        ret = hints->window_group;

    XFree (hints);

    return ret;
}

char *
p_netk_get_client_machine(Window xwindow)
{
	/* p_netk_get_string_property_latin1? */
	return p_netk_get_string_property_latin1 (xwindow, p_netk_atom_get ("WM_CLIENT_MACHINE"));
}


char *
p_netk_get_session_id (Window xwindow)
{
    Window client_leader;

    client_leader = None;
    p_netk_get_window (xwindow, p_netk_atom_get ("WM_CLIENT_LEADER"),
                       &client_leader);

    if (client_leader == None)
        return NULL;

    return p_netk_get_string_property_latin1 (client_leader,
                                              p_netk_atom_get
                                              ("SM_CLIENT_ID"));
}

int
p_netk_get_pid (Window xwindow)
{
    int val;

    if (!p_netk_get_cardinal (xwindow, p_netk_atom_get ("_NET_WM_PID"), &val))
        return 0;
    else
        return val;
}

static gchar *
netk_utf8_strndup (const gchar *src, gssize max_len)
{
    const gchar *s = src;

    if (max_len <= 0)
	return g_strdup (src);

    while (max_len && *s)
    {
	s = g_utf8_next_char (s);
	max_len--;
    }

    return g_strndup (src, s - src);
}

char *
p_netk_get_name (Window xwindow)
{
    char *s, *name = NULL;

    s = p_netk_get_utf8_property (xwindow, p_netk_atom_get ("_NET_WM_VISIBLE_NAME"));

    if (s == NULL)
        s = p_netk_get_utf8_property (xwindow, p_netk_atom_get ("_NET_WM_NAME"));

    if (s == NULL)
        s = p_netk_get_text_property (xwindow, XA_WM_NAME);

    if (s != NULL)
    {
        name = netk_utf8_strndup (s, NETK_MAX_TITLE_LENGTH);
        g_free (s);
    }
    
    return name;
}

static char *
latin1_to_utf8 (const char *latin1)
{
    GString *str;
    const char *p;

    str = g_string_new (NULL);

    p = latin1;
    while (*p)
    {
        g_string_append_unichar (str, (gunichar) * p);
        ++p;
    }

    return g_string_free (str, FALSE);
}

char*
p_netk_get_res_class_utf8 (Window xwindow)
{

    char *res_class;

    p_netk_get_wmclass (xwindow, &res_class, NULL);

    return res_class;
}

void
p_netk_get_wmclass (Window xwindow, char **res_class, char **res_name)
{

    XClassHint ch;
    char *retval;
  
    p_netk_error_trap_push ();

    ch.res_name = NULL;
    ch.res_class = NULL;

    XGetClassHint (gdk_display, xwindow, &ch);

    p_netk_error_trap_pop ();
  
    retval = NULL;


    if (res_class)
    {
        *res_class = NULL;
    }

    if (res_name)
    {
        *res_name = NULL;
    }

  
    if (ch.res_name)
    {
        if (res_name)
        {
            *res_name = latin1_to_utf8 (ch.res_name);
        }
        XFree (ch.res_name);
    }

    if (ch.res_class)
    {
        if (res_class)
        {
            *res_class = latin1_to_utf8 (ch.res_class);
        }
        XFree (ch.res_class);
    }
}

void
p_netk_select_input (Window xwindow, int mask)
{
    GdkWindow *gdkwindow;

    gdkwindow = gdk_xid_table_lookup (xwindow);

    p_netk_error_trap_push ();
    if (gdkwindow)
    {
        /* Avoid breaking GDK's setup,
         * this somewhat relies on people setting
         * event masks right after realization
         * and not changing them again
         */
        XWindowAttributes attrs;
        XGetWindowAttributes (gdk_display, xwindow, &attrs);
        mask |= attrs.your_event_mask;
    }

    XSelectInput (gdk_display, xwindow, mask);
    p_netk_error_trap_pop ();
}

/* The icon-reading code is copied
 * from metacity, please sync bugfixes
 */
static gboolean
find_largest_sizes (gulong * data, gulong nitems, int *width, int *height)
{
    *width = 0;
    *height = 0;

    while (nitems > 0)
    {
        int w, h;
        gboolean replace;

        replace = FALSE;

        if (nitems < 3)
            return FALSE;       /* no space for w, h */

        w = data[0];
        h = data[1];

        if (nitems < (gulong) ((w * h) + 2))
            return FALSE;       /* not enough data */

        *width = MAX (w, *width);
        *height = MAX (h, *height);

        data += (w * h) + 2;
        nitems -= (w * h) + 2;
    }

    return TRUE;
}

static gboolean
find_best_size (gulong * data, gulong nitems, int ideal_width, int ideal_height,
                int *width, int *height, gulong ** start)
{
    int best_w;
    int best_h;
    gulong *best_start;
    int max_width, max_height;

    *width = 0;
    *height = 0;
    *start = NULL;

    if (!find_largest_sizes (data, nitems, &max_width, &max_height))
        return FALSE;

    if (ideal_width < 0)
        ideal_width = max_width;
    if (ideal_height < 0)
        ideal_height = max_height;

    best_w = 0;
    best_h = 0;
    best_start = NULL;

    while (nitems > 0)
    {
        int w, h;
        gboolean replace;

        replace = FALSE;

        if (nitems < 3)
            return FALSE;       /* no space for w, h */

        w = data[0];
        h = data[1];

        if (nitems < (gulong) ((w * h) + 2))
            break;              /* not enough data */

        if (best_start == NULL)
        {
            replace = TRUE;
        }
        else
        {
            /* work with averages */
            const int ideal_size = (ideal_width + ideal_height) / 2;
            int best_size = (best_w + best_h) / 2;
            int this_size = (w + h) / 2;

            /* larger than desired is always better than smaller */
            if (best_size < ideal_size && this_size >= ideal_size)
                replace = TRUE;
            /* if we have too small, pick anything bigger */
            else if (best_size < ideal_size && this_size > best_size)
                replace = TRUE;
            /* if we have too large, pick anything smaller
             * but still >= the ideal
             */
            else if (best_size > ideal_size && this_size >= ideal_size
                     && this_size < best_size)
                replace = TRUE;
        }

        if (replace)
        {
            best_start = data + 2;
            best_w = w;
            best_h = h;
        }

        data += (w * h) + 2;
        nitems -= (w * h) + 2;
    }

    if (best_start)
    {
        *start = best_start;
        *width = best_w;
        *height = best_h;
        return TRUE;
    }
    else
        return FALSE;
}

static void
argbdata_to_pixdata (gulong * argb_data, int len, guchar ** pixdata)
{
    guchar *p;
    int i;

    *pixdata = g_new (guchar, len * 4);
    p = *pixdata;

    /* One could speed this up a lot. */
    i = 0;
    while (i < len)
    {
        guint argb;
        guint rgba;

        argb = argb_data[i];
        rgba = (argb << 8) | (argb >> 24);

        *p = rgba >> 24;
        ++p;
        *p = (rgba >> 16) & 0xff;
        ++p;
        *p = (rgba >> 8) & 0xff;
        ++p;
        *p = rgba & 0xff;
        ++p;

        ++i;
    }
}

static gboolean
read_rgb_icon (Window xwindow, int ideal_width, int ideal_height,
               int ideal_mini_width, int ideal_mini_height, int *width,
               int *height, guchar ** pixdata, int *mini_width,
               int *mini_height, guchar ** mini_pixdata)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    int result, err;
    gulong *data;
    gulong *best;
    int w, h;
    gulong *best_mini;
    int mini_w, mini_h;

    p_netk_error_trap_push ();
    type = None;
    data = NULL;
    result =
        XGetWindowProperty (gdk_display, xwindow,
                            p_netk_atom_get ("_NET_WM_ICON"), 0, G_MAXLONG,
                            False, XA_CARDINAL, &type, &format, &nitems,
                            &bytes_after, (guchar **)&data);

    err = p_netk_error_trap_pop ();

    if (err != Success || result != Success)
        return FALSE;

    if (type != XA_CARDINAL)
    {
        XFree (data);
        return FALSE;
    }

    if (!find_best_size
        (data, nitems, ideal_width, ideal_height, &w, &h, &best))
    {
        XFree (data);
        return FALSE;
    }

    if (!find_best_size
        (data, nitems, ideal_mini_width, ideal_mini_height, &mini_w, &mini_h,
         &best_mini))
    {
        XFree (data);
        return FALSE;
    }

    *width = w;
    *height = h;

    *mini_width = mini_w;
    *mini_height = mini_h;

    argbdata_to_pixdata (best, w * h, pixdata);
    argbdata_to_pixdata (best_mini, mini_w * mini_h, mini_pixdata);

    XFree (data);

    return TRUE;
}

static void
free_pixels (guchar * pixels, gpointer data)
{
    g_free (pixels);
}

static void
get_pixmap_geometry (Pixmap pixmap, int *w, int *h, int *d)
{
    Window root_ignored;
    int x_ignored, y_ignored;
    guint width, height;
    guint border_width_ignored;
    guint depth;

    if (w)
        *w = 1;
    if (h)
        *h = 1;
    if (d)
        *d = 1;

    XGetGeometry (gdk_display, pixmap, &root_ignored, &x_ignored, &y_ignored,
                  &width, &height, &border_width_ignored, &depth);

    if (w)
        *w = width;
    if (h)
        *h = height;
    if (d)
        *d = depth;
}

static GdkPixbuf *
apply_mask (GdkPixbuf * pixbuf, GdkPixbuf * mask)
{
    int w, h;
    int i, j;
    GdkPixbuf *with_alpha;
    guchar *src;
    guchar *dest;
    int src_stride;
    int dest_stride;

    w = MIN (gdk_pixbuf_get_width (mask), gdk_pixbuf_get_width (pixbuf));
    h = MIN (gdk_pixbuf_get_height (mask), gdk_pixbuf_get_height (pixbuf));

    with_alpha = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);

    dest = gdk_pixbuf_get_pixels (with_alpha);
    src = gdk_pixbuf_get_pixels (mask);

    dest_stride = gdk_pixbuf_get_rowstride (with_alpha);
    src_stride = gdk_pixbuf_get_rowstride (mask);

    i = 0;
    while (i < h)
    {
        j = 0;
        while (j < w)
        {
            guchar *s = src + i * src_stride + j * 3;
            guchar *d = dest + i * dest_stride + j * 4;

            /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0
             * otherwise
             */
            if (s[0] == 0)
                d[3] = 0;       /* transparent */
            else
                d[3] = 255;     /* opaque */

            ++j;
        }

        ++i;
    }

    return with_alpha;
}

static GdkColormap *
get_cmap (GdkPixmap * pixmap)
{
    GdkColormap *cmap;

    g_return_val_if_fail (pixmap != NULL, NULL);
    
    cmap = gdk_drawable_get_colormap (pixmap);
    if (cmap)
        g_object_ref (G_OBJECT (cmap));

    if (cmap == NULL)
    {
        if (gdk_drawable_get_depth (pixmap) == 1)
        {
            /* try null cmap */
            cmap = NULL;
        }
        else
        {
            /* Try system cmap */
            cmap = gdk_colormap_get_system ();
            g_object_ref (G_OBJECT (cmap));
        }
    }

    /* Be sure we aren't going to blow up due to visual mismatch */
    if (cmap
        && (gdk_colormap_get_visual (cmap)->depth !=
            gdk_drawable_get_depth (pixmap)))
    {
        g_object_unref (G_OBJECT (cmap));
        cmap = NULL;
    }
    return cmap;
}

GdkPixbuf *
p_netk_gdk_pixbuf_get_from_pixmap (GdkPixbuf * dest, Pixmap xpixmap,
                                   int src_x, int src_y, int dest_x,
                                   int dest_y, int width, int height)
{
    GdkDrawable *drawable;
    GdkPixbuf *retval;
    GdkColormap *cmap;

    retval = NULL;

    drawable = gdk_xid_table_lookup (xpixmap);

    if (drawable)
        g_object_ref (G_OBJECT (drawable));
    else
        drawable = gdk_pixmap_foreign_new (xpixmap);
        
    if (!drawable)
    {
        /* Pixmap is gone ?? */
        return NULL;
    }

    cmap = get_cmap (drawable);

    /* GDK is supposed to do this but doesn't in GTK 2.0.2,
     * fixed in 2.0.3
     */
    if (width < 0)
        gdk_drawable_get_size (drawable, &width, NULL);
    if (height < 0)
        gdk_drawable_get_size (drawable, NULL, &height);

    retval =
        gdk_pixbuf_get_from_drawable (dest, drawable, cmap, src_x, src_y,
                                      dest_x, dest_y, width, height);

    if (cmap)
        g_object_unref (G_OBJECT (cmap));
    g_object_unref (G_OBJECT (drawable));

    return retval;
}

static gboolean
try_pixmap_and_mask (Pixmap src_pixmap, Pixmap src_mask, GdkPixbuf ** iconp,
                     int ideal_width, int ideal_height,
                     GdkPixbuf ** mini_iconp, int ideal_mini_width,
                     int ideal_mini_height)
{
    GdkPixbuf *unscaled = NULL;
    GdkPixbuf *mask = NULL;
    int w, h;

    if (src_pixmap == None)
        return FALSE;

    p_netk_error_trap_push ();

    get_pixmap_geometry (src_pixmap, &w, &h, NULL);

    unscaled = p_netk_gdk_pixbuf_get_from_pixmap (NULL, src_pixmap, 0, 0, 0, 0, w, h);

    if (unscaled && src_mask != None)
    {
        get_pixmap_geometry (src_mask, &w, &h, NULL);
        mask = p_netk_gdk_pixbuf_get_from_pixmap (NULL, src_mask, 0, 0, 0, 0, w, h);
    }

    p_netk_error_trap_pop ();

    if (mask)
    {
        GdkPixbuf *masked;

        masked = apply_mask (unscaled, mask);
        g_object_unref (G_OBJECT (unscaled));
        unscaled = masked;

        g_object_unref (G_OBJECT (mask));
        mask = NULL;
    }

    if (unscaled)
    {
        *iconp =
            gdk_pixbuf_scale_simple (unscaled,
                                     ideal_width > 0 ? ideal_width :
                                     gdk_pixbuf_get_width (unscaled),
                                     ideal_height > 0 ? ideal_height :
                                     gdk_pixbuf_get_height (unscaled),
                                     GDK_INTERP_BILINEAR);
        *mini_iconp =
            gdk_pixbuf_scale_simple (unscaled,
                                     ideal_mini_width > 0 ? ideal_mini_width :
                                     gdk_pixbuf_get_width (unscaled),
                                     ideal_mini_height > 0 ? ideal_mini_height :
                                     gdk_pixbuf_get_height (unscaled),
                                     GDK_INTERP_BILINEAR);

        g_object_unref (G_OBJECT (unscaled));
        return TRUE;
    }
    else
        return FALSE;
}

static void
get_kwm_win_icon (Window xwindow, Pixmap * pixmap, Pixmap * mask)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    Pixmap *icons;
    int err, result;

    *pixmap = None;
    *mask = None;

    p_netk_error_trap_push ();
    icons = NULL;
    result =
        XGetWindowProperty (gdk_display, xwindow,
                            p_netk_atom_get ("KWM_WIN_ICON"), 0, G_MAXLONG,
                            False, p_netk_atom_get ("KWM_WIN_ICON"), &type,
                            &format, &nitems, &bytes_after,
                            (guchar **)&icons);

    err = p_netk_error_trap_pop ();
    if (err != Success || result != Success)
        return;

    if (type != p_netk_atom_get ("KWM_WIN_ICON"))
    {
        XFree (icons);
        return;
    }

    *pixmap = icons[0];
    *mask = icons[1];

    XFree (icons);

    return;
}

typedef enum
{
    /* These MUST be in ascending order of preference;
     * i.e. if we get _NET_WM_ICON and already have
     * WM_HINTS, we prefer _NET_WM_ICON
     */
    USING_NO_ICON,
    USING_FALLBACK_ICON,
    USING_KWM_WIN_ICON,
    USING_WM_HINTS,
    USING_NET_WM_ICON
}
IconOrigin;

struct _NetkIconCache
{
    IconOrigin origin;
    Pixmap prev_pixmap;
    Pixmap prev_mask;
    GdkPixbuf *icon;
    GdkPixbuf *mini_icon;
    int ideal_width;
    int ideal_height;
    int ideal_mini_width;
    int ideal_mini_height;
    guint want_fallback:1;
    /* TRUE if these props have changed */
    guint wm_hints_dirty:1;
    guint kwm_win_icon_dirty:1;
    guint net_wm_icon_dirty:1;
};

NetkIconCache *
p_netk_icon_cache_new (void)
{
    NetkIconCache *icon_cache;

    icon_cache = _netk_slice_new0 (NetkIconCache);

    icon_cache->origin = USING_NO_ICON;
    icon_cache->prev_pixmap = None;
    icon_cache->icon = NULL;
    icon_cache->mini_icon = NULL;
    icon_cache->ideal_width = -1;       /* won't be a legit width */
    icon_cache->ideal_height = -1;
    icon_cache->ideal_mini_width = -1;
    icon_cache->ideal_mini_height = -1;
    icon_cache->want_fallback = TRUE;
    icon_cache->wm_hints_dirty = TRUE;
    icon_cache->kwm_win_icon_dirty = TRUE;
    icon_cache->net_wm_icon_dirty = TRUE;

    return icon_cache;
}

static void
clear_icon_cache (NetkIconCache * icon_cache, gboolean dirty_all)
{
    if (icon_cache->icon)
        g_object_unref (G_OBJECT (icon_cache->icon));
    icon_cache->icon = NULL;

    if (icon_cache->mini_icon)
        g_object_unref (G_OBJECT (icon_cache->mini_icon));
    icon_cache->mini_icon = NULL;

    icon_cache->origin = USING_NO_ICON;

    if (dirty_all)
    {
        icon_cache->wm_hints_dirty = TRUE;
        icon_cache->kwm_win_icon_dirty = TRUE;
        icon_cache->net_wm_icon_dirty = TRUE;
    }
}

void
p_netk_icon_cache_free (NetkIconCache * icon_cache)
{
    clear_icon_cache (icon_cache, FALSE);

    _netk_slice_free (NetkIconCache, icon_cache);
}

void
p_netk_icon_cache_property_changed (NetkIconCache * icon_cache, Atom atom)
{
    if (atom == p_netk_atom_get ("_NET_WM_ICON"))
        icon_cache->net_wm_icon_dirty = TRUE;
    else if (atom == p_netk_atom_get ("KWM_WIN_ICON"))
        icon_cache->kwm_win_icon_dirty = TRUE;
    else if (atom == p_netk_atom_get ("WM_HINTS"))
        icon_cache->wm_hints_dirty = TRUE;
}

gboolean
p_netk_icon_cache_get_icon_invalidated (NetkIconCache * icon_cache)
{
    if (icon_cache->origin <= USING_KWM_WIN_ICON
        && icon_cache->kwm_win_icon_dirty)
        return TRUE;
    else if (icon_cache->origin <= USING_WM_HINTS
             && icon_cache->wm_hints_dirty)
        return TRUE;
    else if (icon_cache->origin <= USING_NET_WM_ICON
             && icon_cache->net_wm_icon_dirty)
        return TRUE;
    else if (icon_cache->origin < USING_FALLBACK_ICON
             && icon_cache->want_fallback)
        return TRUE;
    else if (icon_cache->origin == USING_NO_ICON)
        return TRUE;
    else if (icon_cache->origin == USING_FALLBACK_ICON
             && !icon_cache->want_fallback)
        return TRUE;
    else
        return FALSE;
}

void
p_netk_icon_cache_set_want_fallback (NetkIconCache * icon_cache,
                                     gboolean setting)
{
    icon_cache->want_fallback = setting;
}

gboolean
p_netk_icon_cache_get_is_fallback (NetkIconCache * icon_cache)
{
    return icon_cache->origin == USING_FALLBACK_ICON;
}

static void
replace_cache (NetkIconCache * icon_cache, IconOrigin origin,
               GdkPixbuf * new_icon, GdkPixbuf * new_mini_icon)
{
    clear_icon_cache (icon_cache, FALSE);

    icon_cache->origin = origin;

    if (new_icon)
        g_object_ref (G_OBJECT (new_icon));

    icon_cache->icon = new_icon;

    if (new_mini_icon)
        g_object_ref (G_OBJECT (new_mini_icon));

    icon_cache->mini_icon = new_mini_icon;
}

static GdkPixbuf *
scaled_from_pixdata (guchar * pixdata, int w, int h, int new_w, int new_h)
{
    GdkPixbuf *src;
    GdkPixbuf *dest;

    src =
        gdk_pixbuf_new_from_data (pixdata, GDK_COLORSPACE_RGB, TRUE, 8, w, h,
                                  w * 4, free_pixels, NULL);

    if (src == NULL)
        return NULL;

    if (w != h)
    {
        GdkPixbuf *tmp;
        int size;

        size = MAX (w, h);

        tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, size, size);

        if (tmp != NULL)
        {
            gdk_pixbuf_fill (tmp, 0);
            gdk_pixbuf_copy_area (src, 0, 0, w, h, tmp, (size - w) / 2,
                                  (size - h) / 2);

            g_object_unref (G_OBJECT (src));
            src = tmp;
        }
    }

    if (w != new_w || h != new_h)
    {
        dest =
            gdk_pixbuf_scale_simple (src, new_w, new_h, GDK_INTERP_BILINEAR);

        g_object_unref (G_OBJECT (src));
    }
    else
    {
        dest = src;
    }

    return dest;
}

gboolean
p_netk_is_urgent (Window xwindow)
{
    XWMHints *hints;
    gboolean retval;
    retval = FALSE;
    
    p_netk_error_trap_push ();
    hints = XGetWMHints (gdk_display, xwindow);
    p_netk_error_trap_pop ();
    
    if (hints)
    {
        retval = (hints->flags & XUrgencyHint);
        XFree (hints);
        hints = NULL;
    }
    return retval;
}
  
gboolean
p_netk_read_icons (Window xwindow, NetkIconCache * icon_cache,
                   GdkPixbuf ** iconp, int ideal_width, int ideal_height,
                   GdkPixbuf ** mini_iconp, int ideal_mini_width,
                   int ideal_mini_height)
{
    guchar *pixdata;
    int w, h;
    guchar *mini_pixdata;
    int mini_w, mini_h;
    Pixmap pixmap;
    Pixmap mask;
    XWMHints *hints;

    /* Return value is whether the icon changed */

    g_return_val_if_fail (icon_cache != NULL, FALSE);

    *iconp = NULL;
    *mini_iconp = NULL;

    if (ideal_width != icon_cache->ideal_width
        || ideal_height != icon_cache->ideal_height
        || ideal_mini_width != icon_cache->ideal_mini_width
        || ideal_mini_height != icon_cache->ideal_mini_height)
        clear_icon_cache (icon_cache, TRUE);

    icon_cache->ideal_width = ideal_width;
    icon_cache->ideal_height = ideal_height;
    icon_cache->ideal_mini_width = ideal_mini_width;
    icon_cache->ideal_mini_height = ideal_mini_height;

    if (!p_netk_icon_cache_get_icon_invalidated (icon_cache))
        return FALSE;           /* we have no new info to use */

    pixdata = NULL;

    /* Our algorithm here assumes that we can't have for example origin
     * < USING_NET_WM_ICON and icon_cache->net_wm_icon_dirty == FALSE
     * unless we have tried to read NET_WM_ICON.
     *
     * Put another way, if an icon origin is not dirty, then we have
     * tried to read it at the current size. If it is dirty, then
     * we haven't done that since the last change.
     */

    if (icon_cache->origin <= USING_NET_WM_ICON
        && icon_cache->net_wm_icon_dirty)

    {
        icon_cache->net_wm_icon_dirty = FALSE;

        if (read_rgb_icon
            (xwindow, ideal_width, ideal_height, ideal_mini_width,
             ideal_mini_height, &w, &h, &pixdata, &mini_w, &mini_h,
             &mini_pixdata))
        {
            *iconp =
                scaled_from_pixdata (pixdata, w, h, ideal_width,
                                     ideal_height);

            *mini_iconp =
                scaled_from_pixdata (mini_pixdata, mini_w, mini_h,
                                     ideal_mini_width, ideal_mini_height);

            replace_cache (icon_cache, USING_NET_WM_ICON, *iconp,
                           *mini_iconp);

            return TRUE;
        }
    }

    if (icon_cache->origin <= USING_WM_HINTS && icon_cache->wm_hints_dirty)
    {
        icon_cache->wm_hints_dirty = FALSE;

        p_netk_error_trap_push ();
        hints = XGetWMHints (gdk_display, xwindow);
        p_netk_error_trap_pop ();
        pixmap = None;
        mask = None;
        if (hints)
        {
            if (hints->flags & IconPixmapHint)
                pixmap = hints->icon_pixmap;
            if (hints->flags & IconMaskHint)
                mask = hints->icon_mask;

            XFree (hints);
            hints = NULL;
        }

        /* We won't update if pixmap is unchanged;
         * avoids a get_from_drawable() on every geometry
         * hints change
         */
        if (((pixmap != icon_cache->prev_pixmap) || 
              mask != icon_cache->prev_mask) && 
            (pixmap != None))
        {
            if (try_pixmap_and_mask
                (pixmap, mask, iconp, ideal_width, ideal_height, mini_iconp,
                 ideal_mini_width, ideal_mini_height))
            {
                icon_cache->prev_pixmap = pixmap;
                icon_cache->prev_mask = mask;

                replace_cache (icon_cache, USING_WM_HINTS, *iconp,
                               *mini_iconp);

                return TRUE;
            }
        }
    }

    if (icon_cache->origin <= USING_KWM_WIN_ICON
        && icon_cache->kwm_win_icon_dirty)
    {
        icon_cache->kwm_win_icon_dirty = FALSE;

        get_kwm_win_icon (xwindow, &pixmap, &mask);

        if (((pixmap != icon_cache->prev_pixmap || 
              mask != icon_cache->prev_mask)) && 
            (pixmap != None))
        {
            if (try_pixmap_and_mask
                (pixmap, mask, iconp, ideal_width, ideal_height, mini_iconp,
                 ideal_mini_width, ideal_mini_height))
            {
                icon_cache->prev_pixmap = pixmap;
                icon_cache->prev_mask = mask;

                replace_cache (icon_cache, USING_KWM_WIN_ICON, *iconp,
                               *mini_iconp);

                return TRUE;
            }
        }
    }

    if (icon_cache->want_fallback && icon_cache->origin < USING_FALLBACK_ICON)
    {
        p_netk_get_fallback_icons (iconp, ideal_width, ideal_height,
                                   mini_iconp, ideal_mini_width,
                                   ideal_mini_height);

        replace_cache (icon_cache, USING_FALLBACK_ICON, *iconp, *mini_iconp);

        return TRUE;
    }

    if (!icon_cache->want_fallback
        && icon_cache->origin == USING_FALLBACK_ICON)
    {
        /* Get rid of current icon */
        clear_icon_cache (icon_cache, FALSE);

        return TRUE;
    }

    /* found nothing new */
    return FALSE;
}

#ifdef HAVE_GDK_PIXBUF_NEW_FROM_STREAM
#define gdk_pixbuf_new_from_inline gdk_pixbuf_new_from_stream
#endif

static GdkPixbuf *
default_icon_at_size (int width, int height)
{

    GdkPixbuf *base;
    GdkPixbuf *nbase;
    base = NULL;
    
    if (width > 0) {
        if (!base) base = xfce_themed_icon_load_category (XFCE_ICON_CATEGORY_UNKNOWN, width);
        if (!base) base = xfce_themed_icon_load ("xfce-unknown", width);
        if (!base) base = xfce_themed_icon_load ("unknown", width);
        if (base) {
        	nbase = gdk_pixbuf_copy (base);
        	g_object_unref (G_OBJECT (base));
        	base = nbase;
        	nbase = NULL;
        }
    }

    if(!base)
        base = gdk_pixbuf_new_from_inline (-1, default_icon_data, FALSE, NULL);

    g_assert (base);

    if ((width < 0 && height < 0)
        || (gdk_pixbuf_get_width (base) == width
            && gdk_pixbuf_get_height (base) == height))
    {
        return base;
    }
    else
    {
        GdkPixbuf *scaled;

        scaled =
            gdk_pixbuf_scale_simple (base,
                                     width >
                                     0 ? width : gdk_pixbuf_get_width (base),
                                     height >
                                     0 ? height :
                                     gdk_pixbuf_get_height (base),
                                     GDK_INTERP_BILINEAR);

        g_object_unref (G_OBJECT (base));

        return scaled;
    }
}

void
p_netk_get_fallback_icons (GdkPixbuf ** iconp, int ideal_width,
                           int ideal_height, GdkPixbuf ** mini_iconp,
                           int ideal_mini_width, int ideal_mini_height)
{
    if (iconp)
        *iconp =
            default_icon_at_size (ideal_width > 0 ? ideal_width : 
                                  DEFAULT_ICON_WIDTH,
                                  ideal_height > 0 ? ideal_height : 
                                  DEFAULT_ICON_HEIGHT);

    if (mini_iconp)
        *mini_iconp =
            default_icon_at_size (ideal_mini_width > 0 ? ideal_mini_width : 
                                  DEFAULT_MINI_ICON_WIDTH,
                                  ideal_mini_height > 0 ? ideal_mini_height :
                                  DEFAULT_MINI_ICON_HEIGHT);
}


void
p_netk_get_window_geometry (Screen * screen, Window xwindow, int *xp, int *yp,
                            int *widthp, int *heightp)
{
    int x, y;
    unsigned int width, height, bw, depth;
    Window root_window;

    width = 1;
    height = 1;

    p_netk_error_trap_push ();

    XGetGeometry (gdk_display, xwindow, &root_window, &x, &y, &width, &height,
                  &bw, &depth);

    p_netk_error_trap_pop ();

    p_netk_get_window_position (screen, xwindow, xp, yp);

    if (widthp)
        *widthp = (int) width;
    if (heightp)
        *heightp = (int) height;
}

void
p_netk_get_window_position (Screen * screen, Window xwindow, int *xp, int *yp)
{
    int x, y;
    Window child;

    x = 0;
    y = 0;

    p_netk_error_trap_push ();
    XTranslateCoordinates (gdk_display, xwindow, RootWindowOfScreen (screen),
                           0, 0, &x, &y, &child);
    p_netk_error_trap_pop ();

    if (xp)
        *xp = x;
    if (yp)
        *yp = y;
}

void
p_netk_set_icon_geometry (Window xwindow, int x, int y, int width, int height)
{
    gulong data[4];

    data[0] = x;
    data[1] = y;
    data[2] = width;
    data[3] = height;

    p_netk_error_trap_push ();

    XChangeProperty (gdk_display, xwindow,
                     p_netk_atom_get ("_NET_WM_ICON_GEOMETRY"), XA_CARDINAL,
                     32, PropModeReplace, (guchar *) & data, 4);

    p_netk_error_trap_pop ();
}

gboolean
p_netk_get_icon_geometry (Window xwindow, int* x, int* y, int* width, int* height)
{
    Atom type;
    int format;
    gulong nitems;
    gulong bytes_after;
    gulong *data;
    int err, result;

    p_netk_error_trap_push ();
    type = None;
    result = XGetWindowProperty (gdk_display, xwindow, p_netk_atom_get ("_NET_WM_ICON_GEOMETRY"), 0, 4, False, XA_CARDINAL, &type, &format, &nitems, &bytes_after, (guchar **)&data);
    err = p_netk_error_trap_pop ();
    
    if (err != Success || result != Success)
        return FALSE;

    if (type != XA_CARDINAL || nitems != 4)
    {
        XFree (data);
        return FALSE;
    }

    *x = (int) data[0];
    *y = (int) data[1];
    *width = (int) data[2];
    *height = (int) data[3];

    XFree (data);

    return TRUE;
}

void
p_netk_set_type_hint (Window xwindow, NetkWindowType type)
{
    Atom atom;
    char *atom_type;
    
    switch (type)
    {
        case NETK_WINDOW_DESKTOP:
            atom_type = "_NET_WM_WINDOW_TYPE_DESKTOP";
            break;
        case NETK_WINDOW_DOCK:
            atom_type = "_NET_WM_WINDOW_TYPE_DOCK";
            break;
        case NETK_WINDOW_MODAL_DIALOG:
            p_netk_change_state (DefaultScreenOfDisplay(gdk_display),
                         xwindow, TRUE,
                         p_netk_atom_get ("_NET_WM_STATE_MODAL"), 0);
        /* Walk through */
        case NETK_WINDOW_DIALOG:
            atom_type = "_NET_WM_WINDOW_TYPE_DIALOG";
            break;
        case NETK_WINDOW_TOOLBAR:
            atom_type = "_NET_WM_WINDOW_TYPE_TOOLBAR";
            break;
        case NETK_WINDOW_MENU:
            atom_type = "_NET_WM_WINDOW_TYPE_MENU";
            break;
        case NETK_WINDOW_UTILITY:
            atom_type = "_NET_WM_WINDOW_TYPE_UTILITY";
            break;
        case NETK_WINDOW_SPLASHSCREEN:
            atom_type = "_NET_WM_WINDOW_TYPE_SPLASHSCREEN";
            break;
        default:
            atom_type = "_NET_WM_WINDOW_TYPE_NORMAL";
            break;
    }

    atom = p_netk_atom_get (atom_type);

    p_netk_error_trap_push ();

    XChangeProperty (gdk_display, xwindow,
                     p_netk_atom_get ("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32,
                     PropModeReplace, (guchar *) & atom, 1);

    p_netk_error_trap_pop ();
}

#define _NET_WM_ORIENTATION_HORZ 0
#define _NET_WM_ORIENTATION_VERT 1

/* starting corner for counting spaces */
#define _NET_WM_TOPLEFT     0
#define _NET_WM_TOPRIGHT    1
#define _NET_WM_BOTTOMRIGHT 2
#define _NET_WM_BOTTOMLEFT  3

void
p_netk_set_desktop_layout (Screen * xscreen, int rows, int columns)
{
    gulong data[4];

    /* FIXME: hack, hack, hack so as not
     * to have to add a orientation param
     * to netk_screen_try_set_workspace_layout.
     *
     * Remove this crack asap.
     */
    g_assert ((rows == 0) || (columns == 0));

    data[0] =
        (columns == 0) ? _NET_WM_ORIENTATION_HORZ : _NET_WM_ORIENTATION_VERT;
    data[1] = columns;
    data[2] = rows;
    data[3] = _NET_WM_TOPLEFT;

    p_netk_error_trap_push ();

    XChangeProperty (gdk_display, RootWindowOfScreen (xscreen),
                     p_netk_atom_get ("_NET_DESKTOP_LAYOUT"), XA_CARDINAL, 32,
                     PropModeReplace, (guchar *) &data, 4);

    p_netk_error_trap_pop ();
}

typedef struct
{
    Window window;
    Atom timestamp_prop_atom;
}
TimeStampInfo;

static Bool
timestamp_predicate (Display * display, XEvent * xevent, XPointer arg)
{
    TimeStampInfo *info = (TimeStampInfo *) arg;

    if (xevent->type == PropertyNotify
        && xevent->xproperty.window == info->window
        && xevent->xproperty.atom == info->timestamp_prop_atom)
        return True;

    return False;
}

/**
 * get_server_time:
 * @display: display from which to get the time
 * @window: a #Window, used for communication with the server.
 *          The window must have PropertyChangeMask in its
 *          events mask or a hang will result.
 * 
 * Routine to get the current X server time stamp. 
 * 
 * Return value: the time stamp.
 **/
static Time
get_server_time (Display * display, Window window)
{
    unsigned char c = 'a';
    XEvent xevent;
    TimeStampInfo info;

    info.timestamp_prop_atom =
        XInternAtom (display, "_TIMESTAMP_PROP", False);
    info.window = window;

    XChangeProperty (display, window, info.timestamp_prop_atom,
                     info.timestamp_prop_atom, 8, PropModeReplace, &c, 1);

    XIfEvent (display, &xevent, timestamp_predicate, (XPointer) & info);

    return xevent.xproperty.time;
}

typedef struct
{
    int screen_number;
    int token;
    Window window;
    Atom selection_atom;
    Atom manager_atom;
}
LayoutManager;

static GSList *layout_managers = NULL;
static int next_token = 1;

int
p_netk_try_desktop_layout_manager (Screen * xscreen, int current_token)
{
    GSList *tmp;
    int number;
    Time timestamp;
    XClientMessageEvent xev;
    char buffer[256];
    LayoutManager *lm;

    number = XScreenNumberOfScreen (xscreen);

    tmp = layout_managers;
    while (tmp != NULL)
    {
        lm = tmp->data;

        if (number == lm->screen_number)
        {
            if (current_token == lm->token)
                return current_token;   /* we still have the selection */
            else
                return NETK_NO_MANAGER_TOKEN;   /* someone else has it */
        }

        tmp = tmp->next;
    }

    /* No one in-process has the selection at the moment */

    lm = _netk_slice_new0 (LayoutManager);

    lm->screen_number = number;
    lm->token = next_token;
    ++next_token;

    sprintf (buffer, "_NET_DESKTOP_LAYOUT_S%d", number);
    lm->selection_atom = XInternAtom (gdk_display, buffer, False);
    lm->manager_atom = XInternAtom (gdk_display, "MANAGER", False);

    lm->window =
        XCreateSimpleWindow (gdk_display, RootWindowOfScreen (xscreen), 0, 0,
                             10, 10, 0, WhitePixel (gdk_display, number),
                             WhitePixel (gdk_display, number));

    XSelectInput (gdk_display, lm->window, PropertyChangeMask);
    timestamp = get_server_time (gdk_display, lm->window);

    XSetSelectionOwner (gdk_display, lm->selection_atom, lm->window,
                        timestamp);

    /* Check to see if we managed to claim the selection. */

    if (XGetSelectionOwner (gdk_display, lm->selection_atom) != lm->window)
    {
        _netk_slice_free (LayoutManager, lm);
        return NETK_NO_MANAGER_TOKEN;
    }

    xev.type = ClientMessage;
    xev.window = RootWindow (gdk_display, number);
    xev.message_type = lm->manager_atom;
    xev.format = 32;
    xev.data.l[0] = timestamp;
    xev.data.l[1] = lm->selection_atom;
    xev.data.l[2] = lm->window;
    xev.data.l[3] = 0;          /* manager specific data */
    xev.data.l[4] = 0;          /* manager specific data */

    XSendEvent (gdk_display, RootWindow (gdk_display, number), False,
                StructureNotifyMask, (XEvent *) & xev);

    layout_managers = g_slist_prepend (layout_managers, lm);

    return lm->token;
}

void
p_netk_release_desktop_layout_manager (Screen * xscreen, int current_token)
{
    GSList *tmp;
    int number;
    LayoutManager *lm;

    number = XScreenNumberOfScreen (xscreen);

    tmp = layout_managers;
    while (tmp != NULL)
    {
        lm = tmp->data;

        if (number == lm->screen_number)
        {
            if (current_token == lm->token)
            {
                XDestroyWindow (gdk_display, lm->window);

                _netk_slice_free (LayoutManager, lm);
                layout_managers = g_slist_remove (layout_managers, lm);

                return;
            }
        }

        tmp = tmp->next;
    }
}

gboolean
p_netk_desktop_layout_manager_process_event (XEvent * xev)
{
    GSList *tmp;
    LayoutManager *lm;

    if (xev->type != SelectionClear)
        return FALSE;

    tmp = layout_managers;
    while (tmp != NULL)
    {
        lm = tmp->data;

        if (xev->xany.window == lm->window
            && xev->xselectionclear.selection == lm->selection_atom)
        {
            XDestroyWindow (gdk_display, lm->window);

            _netk_slice_free (LayoutManager, lm);
            layout_managers = g_slist_remove (layout_managers, lm);

            return TRUE;
        }

        tmp = tmp->next;
    }

    return FALSE;
}

gboolean
p_netk_get_desktop_margins (Screen * xscreen, p_DesktopMargins * margins)
{
    Atom real_type;
    int real_format;
    int number;
    unsigned long items_read, items_left;
    unsigned long *data = NULL;
    int number_of_desktops;

    number = XScreenNumberOfScreen (xscreen);
    if ((p_netk_get_cardinal
         (RootWindowOfScreen (xscreen),
          p_netk_atom_get ("_NET_NUMBER_OF_DESKTOPS"), &number_of_desktops))
        &&
        (XGetWindowProperty
         (gdk_display, RootWindowOfScreen (xscreen),
          p_netk_atom_get ("_NET_WORKAREA"), 0L, 4 * number_of_desktops,
          False, XA_CARDINAL, &real_type, &real_format, &items_read,
          &items_left, (unsigned char **) &data) == Success)
        && (items_read >= (gulong) 4 * number_of_desktops))
    {
        int current_desktop;

        if ((p_netk_get_cardinal
             (RootWindowOfScreen (xscreen),
              p_netk_atom_get ("_NET_CURRENT_DESKTOP"), &current_desktop))
            && (current_desktop < number_of_desktops))
        {
            margins->left = (int) 
                (data[4 * current_desktop]);
            margins->top = (int) 
                (data[4 * current_desktop + 1]);
            margins->right = (int) 
                (XDisplayWidth (gdk_display, number) - 
                    (data[4 * current_desktop + 2] + margins->left));
            margins->bottom = (int)
                (XDisplayHeight (gdk_display, number) - 
                    (data[4 * current_desktop + 3] + margins->top));
        }
        else
        {
            margins->left = (int) 
                (data[4 * (number_of_desktops - 1)]);
            margins->top = (int) 
                (data[4 * (number_of_desktops - 1) + 1]);
            margins->right = (int)
                (XDisplayWidth (gdk_display, number) - 
                    (data[4 * (number_of_desktops - 1) + 2] + margins->left));
            margins->bottom = (int)
                (XDisplayHeight (gdk_display, number) - 
                    (data[4 * (number_of_desktops - 1) + 3] + margins->top));
        }
        XFree (data);
        return TRUE;
    }
    else if ((XGetWindowProperty
              (gdk_display, RootWindowOfScreen (xscreen),
               p_netk_atom_get ("GNOME_PANEL_DESKTOP_AREA"), 0L, 4L, False,
               XA_CARDINAL, &real_type, &real_format, &items_read,
               &items_left, (unsigned char **) &data) == Success)
             && (items_read >= 4))
    {
        margins->left = (int) data[0];
        margins->top = (int) data[1];
        margins->right = (int) data[2];
        margins->bottom = (int) data[3];
        XFree (data);
        return TRUE;
    }
    else
    {
        margins->left = 0;
        margins->top = 0;
        margins->right = 0;
        margins->bottom = 0;
    }
    return FALSE;
}

void
p_netk_set_desktop_margins (Window xwindow, p_DesktopMargins * margins)
{
    unsigned long *data = NULL;
    unsigned long *ptr = NULL;

    data = g_new0 (unsigned long, 4);
    ptr = data;
    *ptr++ = (unsigned long) margins->left;
    *ptr++ = (unsigned long) margins->right;
    *ptr++ = (unsigned long) margins->top;
    *ptr++ = (unsigned long) margins->bottom;
    XChangeProperty (gdk_display, xwindow, p_netk_atom_get ("_NET_WM_STRUT"),
                     XA_CARDINAL, 32, PropModeReplace, (unsigned char *) data,
                     4);
    g_free (data);
}
