/* window object */

/*
 * Copyright (C) 2001 Havoc Pennington
 * Copyright (C) 2003 Kim Woelders
 * Copyright (C) 2003 Red Hat, Inc.
 * Includes urgency notification based on Matthew Eaton's patch for libwnck
 *
 * 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
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "netk-window.h"
#include "netk-class-group.h"
#include "netk-xutils.h"
#include "netk-private.h"
#include "netk-enum-types.h"
#include "netk-marshal.h"

#define FALLBACK_NAME "??"
#define ALL_WORKSPACES (0xFFFFFFFF)

#define NETK_WINDOW_GET_PRIVATE(o)    (G_TYPE_INSTANCE_GET_PRIVATE ((o), NETK_TYPE_WINDOW, NetkWindowPrivate))

static GHashTable *window_hash = NULL;

/* Keep 0-10 in sync with the numbers in the WindowState enum. Yeah I'm
 * a loser.
 */
#define COMPRESS_STATE(window)                          \
  ( ((window)->priv->is_minimized        << 0) |        \
    ((window)->priv->is_maximized_horz   << 1) |        \
    ((window)->priv->is_maximized_vert   << 2) |        \
    ((window)->priv->is_shaded           << 3) |        \
    ((window)->priv->skip_pager          << 4) |        \
    ((window)->priv->skip_taskbar        << 5) |        \
    ((window)->priv->is_sticky           << 6) |        \
    ((window)->priv->is_hidden           << 7) |        \
    ((window)->priv->is_fullscreen       << 8) |        \
    ((window)->priv->is_urgent           << 9) |        \
    ((window)->priv->demands_attention   << 10) )

struct _NetkWindowPrivate
{
    Window xwindow;
    NetkScreen *screen;
    NetkApplication *app;
    NetkClassGroup *class_group;
    Window group_leader;
    Window transient_for;
    GdkRectangle icon_geometry;
    char *name;
    char *icon_name;
    char *session_id;
    char *session_id_utf8;
    char *res_class;
    char *res_name;

    int pid;
    int workspace;

    NetkWindowType wintype;

    GdkPixbuf *icon;
    GdkPixbuf *mini_icon;

    NetkIconCache *icon_cache;

    NetkWindowActions actions;

    int x;
    int y;
    int width;
    int height;

    guint transient_for_root:1;

    /* window state */
    guint is_minimized:1;
    guint is_maximized_horz:1;
    guint is_maximized_vert:1;
    guint is_shaded:1;
    guint skip_pager:1;
    guint skip_taskbar:1;
    guint is_sticky:1;
    guint is_hidden:1;
    guint is_fullscreen:1;
    guint is_urgent : 1;
    guint demands_attention : 1;

    /* _NET_WM_STATE_HIDDEN doesn't map directly into an
     * externally-visible state (it determines the WM_STATE
     * interpretation)
     */
    guint net_wm_state_hidden:1;
    guint wm_state_iconic:1;

    /* idle handler for updates */
    guint update_handler;

    /* if you add flags, be sure to set them
     * when we create the window so we get an initial update
     */
    guint need_update_name:1;
    guint need_update_state:1;
    guint need_update_wm_state:1;
    guint need_update_icon_name:1;
    guint need_update_workspace:1;
    guint need_emit_icon_changed:1;
    guint need_update_actions:1;
    guint need_update_wintype:1;
    guint need_update_transient_for:1;
    guint need_update_wmclass : 1;
    guint need_update_wmhints : 1;
    guint need_update_icon_geometry : 1;

    /* if available */
    char *client_machine;
};

enum
{
    NAME_CHANGED,
    STATE_CHANGED,
    WORKSPACE_CHANGED,
    ICON_CHANGED,
    ACTIONS_CHANGED,
    GEOMETRY_CHANGED,
    LAST_SIGNAL
};

static void netk_window_init (NetkWindow * window);
static void netk_window_class_init (NetkWindowClass * klass);
static void netk_window_finalize (GObject * object);

static void emit_name_changed (NetkWindow * window);
static void emit_state_changed (NetkWindow * window,
                                NetkWindowState changed_mask,
                                NetkWindowState new_state);
static void emit_workspace_changed (NetkWindow * window);
static void emit_icon_changed (NetkWindow * window);
static void emit_actions_changed (NetkWindow * window,
                                  NetkWindowActions changed_mask,
                                  NetkWindowActions new_actions);
static void emit_geometry_changed (NetkWindow * window);

static void update_name (NetkWindow * window);
static void update_state (NetkWindow * window);
static void update_wm_state (NetkWindow * window);
static void update_icon_name (NetkWindow * window);
static void update_workspace (NetkWindow * window);
static void update_actions (NetkWindow * window);
static void update_wintype (NetkWindow * window);
static void update_transient_for (NetkWindow * window);
static void update_wmclass (NetkWindow *window);
static void unqueue_update (NetkWindow * window);
static void queue_update (NetkWindow * window);
static void force_update_now (NetkWindow * window);

static NetkWindow *find_last_transient_for (GList * windows, Window xwindow);

static GObjectClass *parent_class;
static guint signals[LAST_SIGNAL] = { 0 };

GType
netk_window_get_type (void)
{
    static GType type = G_TYPE_INVALID;

    if (G_UNLIKELY (type == G_TYPE_INVALID))
    {
        type = _netk_g_type_register_simple (G_TYPE_OBJECT,
                                             "NetkWindow",
                                             sizeof (NetkWindowClass),
                                             netk_window_class_init,
                                             sizeof (NetkWindow),
                                             netk_window_init,
                                             0);
    }

    return type;
}

static void
netk_window_init (NetkWindow * window)
{
    window->priv = NETK_WINDOW_GET_PRIVATE (window);
    
    window->priv->name = g_strdup (FALLBACK_NAME);;
    window->priv->icon_name = NULL;
    window->priv->client_machine = NULL;
    window->priv->workspace = ALL_WORKSPACES;
    
    window->priv->icon_geometry.width = -1;

    window->priv->icon_cache = p_netk_icon_cache_new ();
}

static void
netk_window_class_init (NetkWindowClass * klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    
    g_type_class_add_private (klass, sizeof (NetkWindowPrivate));

    parent_class = g_type_class_peek_parent (klass);

    object_class->finalize = netk_window_finalize;

    signals[NAME_CHANGED] =
        g_signal_new (I_("name_changed"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NetkWindowClass,
                                                          name_changed), NULL,
                      NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

    signals[STATE_CHANGED] =
        g_signal_new (I_("state_changed"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NetkWindowClass,
                                                          state_changed),
                      NULL, NULL, p_netk_marshal_VOID__FLAGS_FLAGS,
                      G_TYPE_NONE, 2, NETK_TYPE_WINDOW_STATE,
                      NETK_TYPE_WINDOW_STATE);

    signals[WORKSPACE_CHANGED] =
        g_signal_new (I_("workspace_changed"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NetkWindowClass,
                                                          workspace_changed),
                      NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

    signals[ICON_CHANGED] =
        g_signal_new (I_("icon_changed"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NetkWindowClass,
                                                          icon_changed), NULL,
                      NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

    signals[ACTIONS_CHANGED] =
        g_signal_new (I_("actions_changed"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NetkWindowClass,
                                                          actions_changed),
                      NULL, NULL, p_netk_marshal_VOID__FLAGS_FLAGS,
                      G_TYPE_NONE, 2, NETK_TYPE_WINDOW_ACTIONS,
                      NETK_TYPE_WINDOW_ACTIONS);

    signals[GEOMETRY_CHANGED] =
        g_signal_new (I_("geometry_changed"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NetkWindowClass,
                                                          geometry_changed),
                      NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
                      0);
}

static void
netk_window_finalize (GObject * object)
{
    NetkWindow *window;

    window = NETK_WINDOW (object);

    unqueue_update (window);

    if (window->priv->icon)
        g_object_unref (G_OBJECT (window->priv->icon));

    if (window->priv->mini_icon)
        g_object_unref (G_OBJECT (window->priv->mini_icon));

    p_netk_icon_cache_free (window->priv->icon_cache);

    g_free (window->priv->client_machine);
    g_free (window->priv->name);
    g_free (window->priv->icon_name);
    g_free (window->priv->session_id);
    g_free (window->priv->res_class);
    g_free (window->priv->res_name);

    if (window->priv->app)
        g_object_unref (G_OBJECT (window->priv->app));

    G_OBJECT_CLASS (parent_class)->finalize (object);
}

/**
 * netk_window_get:
 * @xwindow: an Xlib window ID
 *
 * Gets a preexisting #NetkWindow for the X window @xwindow.
 * Will not create a #NetkWindow if none exists. Robust
 * against bogus window IDs.
 *
 * Return value: the #NetkWindow for this X window
 **/
NetkWindow *
netk_window_get (gulong xwindow)
{
    if (window_hash == NULL)
        return NULL;
    else
        return g_hash_table_lookup (window_hash, &xwindow);
}

/**
 * netk_window_get_screen:
 * @window: a #NetkWindow
 *
 * Gets the screen this window is on.
 *
 * Return value: a #NetkScreen
 **/
NetkScreen *
netk_window_get_screen (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

    return window->priv->screen;
}

NetkWindow *
p_netk_window_create (Window xwindow, NetkScreen * screen)
{
    NetkWindow *window;

    if (window_hash == NULL)
        window_hash = g_hash_table_new (p_netk_xid_hash, p_netk_xid_equal);

    g_return_val_if_fail (g_hash_table_lookup (window_hash, &xwindow) == NULL,
                          NULL);

    window = g_object_new (NETK_TYPE_WINDOW, NULL);
    window->priv->xwindow = xwindow;
    window->priv->screen = screen;

    g_hash_table_insert (window_hash, &window->priv->xwindow, window);

    /* Hash now owns one ref, caller gets none */

    /* Note that xwindow may correspond to a NetkApplication's xwindow,
     * that's why we select the union of the mask we want for Application
     * and the one we want for window
     */
    p_netk_select_input (window->priv->xwindow, NETK_APP_WINDOW_EVENT_MASK);

    window->priv->group_leader =
        p_netk_get_group_leader (window->priv->xwindow);

    window->priv->session_id = p_netk_get_session_id (window->priv->xwindow);

    window->priv->pid = p_netk_get_pid (window->priv->xwindow);
    
    window->priv->client_machine = p_netk_get_client_machine (window->priv->xwindow);

    p_netk_get_window_geometry (NETK_SCREEN_XSCREEN (window->priv->screen),
                                xwindow, &window->priv->x, &window->priv->y,
                                &window->priv->width, &window->priv->height);

    window->priv->need_update_name = TRUE;
    window->priv->need_update_state = TRUE;
    window->priv->need_update_icon_name = TRUE;
    window->priv->need_update_wm_state = TRUE;
    window->priv->need_update_workspace = TRUE;
    window->priv->need_update_actions = TRUE;
    window->priv->need_update_wintype = TRUE;
    window->priv->need_update_transient_for = TRUE;
    window->priv->need_update_wmclass = TRUE;
    window->priv->need_update_wmhints = TRUE;
    window->priv->need_update_icon_geometry = TRUE;
    force_update_now (window);

    return window;
}

void
p_netk_window_destroy (NetkWindow * window)
{
    g_return_if_fail (netk_window_get (window->priv->xwindow) == window);

    g_hash_table_remove (window_hash, &window->priv->xwindow);

    g_return_if_fail (netk_window_get (window->priv->xwindow) == NULL);

    window->priv->xwindow = None;

    /* remove hash's ref on the window */
    g_object_unref (G_OBJECT (window));
}

/**
 * netk_window_get_name:
 * @window: a #NetkWindow
 *
 * Gets the name of the window, as it should be displayed in a pager
 * or tasklist. Always returns some value, even if the window
 * hasn't set a name.
 *
 * For icons titles, use netk_window_get_icon_name() instead.
 *
 * Return value: name of the window
 **/
const char *
netk_window_get_name (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

    return window->priv->name;
}

/**
 * netk_window_get_icon_name:
 * @window: a #NetkWindow
 *
 * Gets the name of the window, as it should be displayed for an
 * icon. Always returns some value, even if the window hasn't set a
 * name. Contrast with netk_window_get_name(), which returns the
 * window title, not the icon title.
 *
 * Return value: name of the window
 **/
const char *
netk_window_get_icon_name (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

    if (window->priv->icon_name)
        return window->priv->icon_name;
    else
        return window->priv->name;
}

NetkApplication *
netk_window_get_application (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

    return window->priv->app;
}

gulong
netk_window_get_group_leader (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), None);

    return window->priv->group_leader;
}

gulong
netk_window_get_xid (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), None);

    return window->priv->xwindow;
}

NetkClassGroup *
netk_window_get_class_group (NetkWindow *window)
{
  g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

  return window->priv->class_group;
}

const char *
netk_window_get_session_id (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

    return window->priv->session_id;
}

const char *
netk_window_get_session_id_utf8 (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

    if (window->priv->session_id_utf8 == NULL
        && window->priv->session_id != NULL)
    {
        GString *str;
        char *p;

        str = g_string_new ("");

        p = window->priv->session_id;
        while (*p)
        {
            g_string_append_unichar (str, g_utf8_get_char (p));
            p = g_utf8_next_char (p);
        }

        window->priv->session_id_utf8 = g_string_free (str, FALSE);
    }

    return window->priv->session_id_utf8;
}

/**
 * netk_window_get_pid:
 * @window: a #NetkWindow
 *
 * Gets the client machine FQDN of @window, if available.
 *
 * Return value: FQDN of machine of @window, or NULL if none available
 **/
const char *
netk_window_get_client_machine (NetkWindow * window)
{
	return window->priv->client_machine;
}

/**
 * netk_window_get_pid:
 * @window: a #NetkWindow
 *
 * Gets the process ID of @window, if available.
 *
 * Return value: PID of @window, or 0 if none available
 **/
int
netk_window_get_pid (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), 0);

    return window->priv->pid;
}

/**
 * netk_window_get_window_type:
 * @window: a #NetkWindow
 * 
 * Retrieves the semantic type of the window.
 * 
 * Return value: semantic type
 **/
NetkWindowType
netk_window_get_window_type (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), 0);

    return window->priv->wintype;
}

const char*
netk_window_get_resource_class (NetkWindow *window)
{
  g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

  return window->priv->res_class;
}

const char*
netk_window_get_resource_name (NetkWindow *window)
{
  g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

  return window->priv->res_name;
}

/**
 * netk_window_is_minimized:
 * @window: a #NetkWindow
 *
 * If the window is minimized returns %TRUE. Minimization state
 * may change anytime a state_changed signal gets emitted.
 *
 * Return value: %TRUE if window is minimized
 **/
gboolean
netk_window_is_minimized (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->is_minimized;
}

gboolean
netk_window_is_maximized_horizontally (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->is_maximized_horz;
}

gboolean
netk_window_is_maximized_vertically (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->is_maximized_vert;
}

/**
 * netk_window_is_maximized:
 * @window: a #NetkWindow
 *
 * As for GDK, "maximized" means both vertically and horizontally.
 * If only one, then the window isn't considered maximized.
 *
 * Return value: %TRUE if the window is maximized in both directions
 **/
gboolean
netk_window_is_maximized (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->is_maximized_horz && window->priv->is_maximized_vert;
}

gboolean
netk_window_is_shaded (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->is_shaded;
}

gboolean
netk_window_is_skip_pager (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->skip_pager;
}

void
netk_window_set_skip_pager (NetkWindow * window, gboolean skip)
{
    g_return_if_fail (NETK_IS_WINDOW (window));
    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, skip,
                         p_netk_atom_get ("_NET_WM_STATE_SKIP_PAGER"), 0);
}

gboolean
netk_window_is_skip_tasklist (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->skip_taskbar;
}

void
netk_window_set_skip_tasklist (NetkWindow * window, gboolean skip)
{
    g_return_if_fail (NETK_IS_WINDOW (window));
    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, skip,
                         p_netk_atom_get ("_NET_WM_STATE_SKIP_TASKBAR"), 0);
}

gboolean
netk_window_is_fullscreen (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->is_fullscreen;
}

void
netk_window_set_fullscreen (NetkWindow * window, gboolean fullscreen)
{
    g_return_if_fail (NETK_IS_WINDOW (window));
    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                        window->priv->xwindow,
                        fullscreen,
                        p_netk_atom_get ("_NET_WM_STATE_FULLSCREEN"), 0);
}

/**
 * netk_window_demands_attention:
 * @window: a #NetkWindow
 *
 * If the window demands attention returns %TRUE. This state may change
 * anytime a state_changed signal gets emitted, as it can depend on flags
 * such as the demands_attention and is_urgent hints.
 *
 * Return value: %TRUE if window demands attention
 **/
gboolean
netk_window_demands_attention (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->demands_attention || window->priv->is_urgent;
}

/* Return whether one of the transients of @window demands attention */
static gboolean
transient_demands_attention (NetkWindow * window)
{
    GList *windows;
    NetkWindow *transient;

    if (!NETK_IS_WINDOW (window))
        return FALSE;

    windows = netk_screen_get_windows_stacked (window->priv->screen);

    transient = window;
    while ((transient =
            find_last_transient_for (windows, transient->priv->xwindow)))
    {
        /* catch transient cycles */
        if (transient == window)
            return FALSE;

        if (netk_window_demands_attention (transient))
            return TRUE;
    }

    return FALSE;
}

/**
 * netk_window_or_transient_demands_attention:
 * @window: a #NetkWindow
 *
 * If the window or one of its transients demands attention returns %TRUE.
 * This state may change anytime a state_changed signal gets emitted.
 *
 * Return value: %TRUE if window or one of its transients demands attention
 **/
gboolean
netk_window_or_transient_demands_attention (NetkWindow * window)
{
    return netk_window_demands_attention (window) ||
        transient_demands_attention (window);
}

/**
 * netk_window_is_sticky:
 * @window: a #NetkWindow
 *
 * Sticky here means "stuck to the glass," i.e. does not scroll with
 * the viewport. In GDK/GTK,
 * e.g. gdk_window_stick()/gtk_window_stick(), sticky means stuck to
 * the glass and _also_ that the window is on all workspaces.
 * But here it only means the viewport aspect of it.
 *
 * Return value: %TRUE if the window is "stuck to the glass"
 **/
gboolean
netk_window_is_sticky (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window->priv->is_sticky;
}

void
netk_window_close (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_close (NETK_SCREEN_XSCREEN (window->priv->screen),
                  window->priv->xwindow);
}

void
netk_window_minimize (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_iconify (window->priv->xwindow);
}

void
netk_window_unminimize (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_deiconify (window->priv->xwindow);
}

void
netk_window_maximize (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, TRUE,
                         p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_VERT"),
                         p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_HORZ"));
}

void
netk_window_unmaximize (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, FALSE,
                         p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_VERT"),
                         p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_HORZ"));
}

void
netk_window_maximize_horizontally (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, TRUE,
                         p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_HORZ"), 0);
}

void
netk_window_unmaximize_horizontally (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, FALSE,
                         p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_HORZ"), 0);
}

void
netk_window_maximize_vertically (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, TRUE,
                         p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_VERT"), 0);
}

void
netk_window_unmaximize_vertically (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, FALSE,
                         p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_VERT"), 0);
}

void
netk_window_shade (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, TRUE,
                         p_netk_atom_get ("_NET_WM_STATE_SHADED"), 0);
}

void
netk_window_unshade (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, FALSE,
                         p_netk_atom_get ("_NET_WM_STATE_SHADED"), 0);
}

void
netk_window_stick (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, TRUE,
                         p_netk_atom_get ("_NET_WM_STATE_STICKY"), 0);
}

void
netk_window_unstick (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_state (NETK_SCREEN_XSCREEN (window->priv->screen),
                         window->priv->xwindow, FALSE,
                         p_netk_atom_get ("_NET_WM_STATE_STICKY"), 0);
}

void
netk_window_keyboard_move (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_keyboard_move (NETK_SCREEN_XSCREEN (window->priv->screen),
                          window->priv->xwindow);
}

void
netk_window_keyboard_size (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_keyboard_size (NETK_SCREEN_XSCREEN (window->priv->screen),
                          window->priv->xwindow);
}

/**
 * netk_window_get_workspace:
 * @window: a #NetkWindow
 *
 * Gets the window's current workspace. If the window is
 * pinned (on all workspaces), or not on any workspaces,
 * %NULL may be returned.
 *
 * Return value: single current workspace or %NULL
 **/
NetkWorkspace *
netk_window_get_workspace (NetkWindow * window)
{
    if (window->priv->workspace == (gint) ALL_WORKSPACES)
        return NULL;
    else
        return netk_screen_get_workspace (window->priv->screen,
                                          window->priv->workspace);
}

void
netk_window_move_to_workspace (NetkWindow * window, NetkWorkspace * space)
{
    g_return_if_fail (NETK_IS_WINDOW (window));
    g_return_if_fail (NETK_IS_WORKSPACE (space));

    p_netk_change_workspace (NETK_SCREEN_XSCREEN (window->priv->screen),
                             window->priv->xwindow,
                             netk_workspace_get_number (space));
}

/**
 * netk_window_is_pinned:
 * @window: a #NetkWindow
 *
 * %TRUE if the window is on all workspaces. Note that if this
 * changes, it's signalled by a workspace_changed signal,
 * not state_changed.
 *
 * Return value: %TRUE if on all workspaces
 **/
gboolean
netk_window_is_pinned (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return (window->priv->workspace == (gint)ALL_WORKSPACES);
}

void
netk_window_pin (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_change_workspace (NETK_SCREEN_XSCREEN (window->priv->screen),
                             window->priv->xwindow, ALL_WORKSPACES);
}

/**
 * netk_window_unpin:
 * @window: a #NetkWindow
 *
 * Sets @window's workspace to only the currently active workspace,
 * if the window was previously pinned. If the window wasn't pinned,
 * doesn't change the window's workspace. If the active workspace
 * isn't known for some reason (shouldn't happen much), sets the
 * window's workspace to 0.
 *
 **/
void
netk_window_unpin (NetkWindow * window)
{
    NetkWorkspace *active;

    g_return_if_fail (NETK_IS_WINDOW (window));

    if (window->priv->workspace != (gint)ALL_WORKSPACES)
        return;

    active = netk_screen_get_active_workspace (window->priv->screen);

    p_netk_change_workspace (NETK_SCREEN_XSCREEN (window->priv->screen),
                             window->priv->xwindow,
                             active ? netk_workspace_get_number (active) : 0);
}

/**
 * netk_window_activate:
 * @window: a #NetkWindow
 *
 * Ask the window manager to make @window the active window.  The
 * window manager may choose to raise @window along with focusing it.
 **/
void
netk_window_activate (NetkWindow * window)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    p_netk_activate (NETK_SCREEN_XSCREEN (window->priv->screen),
                     window->priv->xwindow);
}

/**
 * netk_window_is_active:
 * @window: a #NetkWindow
 *
 *
 *
 * Return value: %TRUE if the window is the active window
 **/
gboolean
netk_window_is_active (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return window == netk_screen_get_active_window (window->priv->screen);
}

static NetkWindow *
find_last_transient_for (GList * windows, Window xwindow)
{
    GList *tmp;
    NetkWindow *retval;

    /* find _last_ transient for xwindow in the list */

    retval = NULL;

    tmp = windows;
    while (tmp != NULL)
    {
        NetkWindow *w = tmp->data;

        if (w->priv->transient_for == xwindow)
            retval = w;

        tmp = tmp->next;
    }

    return retval;
}

/**
 * netk_window_activate_transient:
 * @window: a #NetkWindow
 *
 * If @window has transients, activates the most likely transient
 * instead of the window itself. Otherwise activates @window.
 * 
 * FIXME the ideal behavior of this function is probably to activate
 * the most recently active window among @window and its transients.
 * This is probably best implemented on the window manager side.
 * 
 **/
void
netk_window_activate_transient (NetkWindow * window)
{
    GList *windows;
    NetkWindow *transient;
    NetkWindow *next;

    g_return_if_fail (NETK_IS_WINDOW (window));

    windows = netk_screen_get_windows_stacked (window->priv->screen);

    transient = NULL;
    next = find_last_transient_for (windows, window->priv->xwindow);

    while (next != NULL)
    {
        if (next == window)
        {
            /* catch transient cycles */
            transient = NULL;
            break;
        }

        transient = next;

        next = find_last_transient_for (windows, transient->priv->xwindow);
    }

    if (transient != NULL)
    {
        /* Raise but don't focus the main window */
        XRaiseWindow (gdk_display, window->priv->xwindow);
        /* then activate the transient */
        netk_window_activate (transient);
    }
    else
    {
        netk_window_activate (window);
    }
}

static void
get_icons (NetkWindow * window)
{
    GdkPixbuf *icon;
    GdkPixbuf *mini_icon;

    icon = NULL;
    mini_icon = NULL;

    if (p_netk_read_icons
        (window->priv->xwindow, window->priv->icon_cache, &icon,
         DEFAULT_ICON_WIDTH, DEFAULT_ICON_HEIGHT, &mini_icon,
         DEFAULT_MINI_ICON_WIDTH, DEFAULT_MINI_ICON_HEIGHT))
    {
        window->priv->need_emit_icon_changed = TRUE;

        if (window->priv->icon)
        {
            g_object_unref (G_OBJECT (window->priv->icon));
        }

        if (window->priv->mini_icon)
        {
            g_object_unref (G_OBJECT (window->priv->mini_icon));
        }

        window->priv->icon = icon;
        window->priv->mini_icon = mini_icon;
    }

    g_assert ((window->priv->icon && window->priv->mini_icon)
              || !(window->priv->icon || window->priv->mini_icon));
}

GdkPixbuf *
netk_window_get_icon (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

    get_icons (window);
    if (window->priv->need_emit_icon_changed)
    {
        queue_update (window);
    }
    return window->priv->icon;
}

GdkPixbuf *
netk_window_get_mini_icon (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), NULL);

    get_icons (window);
    if (window->priv->need_emit_icon_changed)
    {
        queue_update (window);
    }
    return window->priv->mini_icon;
}

/**
 * netk_window_get_icon_is_fallback:
 * @window: a #NetkWindow
 *
 * Checks if we are using a default fallback icon because
 * none was set on the window.
 * 
 * Return value: %TRUE if icon is a fallback
 **/
gboolean
netk_window_get_icon_is_fallback (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);

    return p_netk_icon_cache_get_is_fallback (window->priv->icon_cache);
}

/**
 * netk_window_get_actions:
 * @window: a #NetkWindow
 * 
 * Gets the window operations that should be sensitive for @window.
 * 
 * Return value: bitmask of actions the window supports
 **/
NetkWindowActions
netk_window_get_actions (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), 0);

    return window->priv->actions;
}


/**
 * netk_window_get_state:
 * @window: a #NetkWindow
 * 
 * Gets the state of a window as a bitmask.
 * 
 * Return value: bitmask of active state flags.
 **/
NetkWindowState
netk_window_get_state (NetkWindow * window)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), 0);

    return COMPRESS_STATE (window);
}

/**
 * netk_window_get_geometry:
 * @window: a #NetkWindow
 * @xp: return location for X coordinate of window 
 * @yp: return location for Y coordinate of window
 * @widthp: return location for width of window
 * @heightp: return location for height of window
 *
 * Gets the size and position of the window, as last received
 * in a configure notify (i.e. this call does not round-trip
 * to the server, just gets the last size we were notified of).
 * The X and Y coordinates are relative to the root window.
 * 
 **/
void
netk_window_get_geometry (NetkWindow * window, int *xp, int *yp, int *widthp,
                          int *heightp)
{
    g_return_if_fail (NETK_IS_WINDOW (window));

    if (xp)
        *xp = window->priv->x;
    if (yp)
        *yp = window->priv->y;
    if (widthp)
        *widthp = window->priv->width;
    if (heightp)
        *heightp = window->priv->height;
}

/**
 * netk_window_is_visible_on_workspace:
 * @window: a #NetkWindow
 * @workspace: a #NetkWorkspace
 * 
 * Like netk_window_is_on_workspace(), but also checks that
 * the window is in a visible state (i.e. not minimized or shaded).
 * 
 * Return value: %TRUE if @window appears on @workspace in normal state
 **/
gboolean
netk_window_is_visible_on_workspace (NetkWindow * window,
                                     NetkWorkspace * workspace)
{
    NetkWindowState state;

    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);
    g_return_val_if_fail (NETK_IS_WORKSPACE (workspace), FALSE);

    state = netk_window_get_state (window);

    if (state & (NETK_WINDOW_STATE_HIDDEN | NETK_WINDOW_STATE_SHADED))
        return FALSE;           /* not visible */

    return netk_window_is_on_workspace (window, workspace);
}

/**
 * netk_window_get_icon_geometry:
 * @window: a #NetkWindow
 * @xp: return location for X coordinate of icon
 * @yp: return location for Y coordinate of icon
 * @widthp: return location for width of icon
 * @heightp: return location for height of icon
 *
 * Gets the size and position of the icon.
 * returns %TRUE if there was one.
 * 
 **/
gboolean
netk_window_get_icon_geometry (NetkWindow * window, int *xp, int *yp, int *widthp, int *heightp)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);
    
    if (window->priv->icon_geometry.width == -1) {
        return FALSE;
    }

    if (xp != NULL)
        *xp = window->priv->icon_geometry.x;

    if (yp != NULL)
        *yp = window->priv->icon_geometry.y;

    if (widthp != NULL)
        *widthp = window->priv->icon_geometry.width;

    if (heightp != NULL)
        *heightp = window->priv->icon_geometry.height;
        
    return TRUE;
}

/**
 * netk_window_set_icon_geometry:
 * @window: a #NetkWindow
 * @x: x coordinate in pixels
 * @y: y coordinate in pixels
 * @width: width in pixels
 * @height: height in pixels
 *
 * Set the icon geometry for a NetkWindow.
 */
void
netk_window_set_icon_geometry (NetkWindow * window, int x, int y, int width,
                               int height)
{
    if ((window->priv->icon_geometry.x == x)  &&
        (window->priv->icon_geometry.y == y) &&
        (window->priv->icon_geometry.width == width) &&
        (window->priv->icon_geometry.height == height))
            return;

    window->priv->icon_geometry.x = x;
    window->priv->icon_geometry.y = y;
    window->priv->icon_geometry.width = width;
    window->priv->icon_geometry.height = height;

    p_netk_set_icon_geometry (window->priv->xwindow, x, y, width, height);
}

/**
 * netk_window_is_on_workspace:
 * @window: a #NetkWindow
 * @workspace: a #NetkWorkspace
 * 
 * 
 * 
 * Return value: %TRUE if @window appears on @workspace
 **/
gboolean
netk_window_is_on_workspace (NetkWindow * window, NetkWorkspace * workspace)
{
    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);
    g_return_val_if_fail (NETK_IS_WORKSPACE (workspace), FALSE);

    return (netk_window_is_pinned (window)
        || (netk_window_get_workspace (window) == workspace));
}

/**
 * netk_window_is_in_viewport:
 * @window: a #NetkWindow
 * @workspace: a #NetkWorkspace
 * 
 * Returns #TRUE if the window is inside the current viewport
 * of the given workspace.
 * 
 * Return value: %TRUE if @window appears in current viewport
 **/
gboolean
netk_window_is_in_viewport (NetkWindow * window, NetkWorkspace * workspace)
{
    GdkRectangle window_rect;
    GdkRectangle viewport_rect;

    g_return_val_if_fail (NETK_IS_WINDOW (window), FALSE);
    g_return_val_if_fail (NETK_IS_WORKSPACE (workspace), FALSE);

    if (netk_window_get_workspace (window) != workspace &&
        !netk_window_is_pinned (window))
        return FALSE;

    viewport_rect.x = netk_workspace_get_viewport_x (workspace);
    viewport_rect.y = netk_workspace_get_viewport_y (workspace);
    viewport_rect.width = netk_screen_get_width (window->priv->screen);
    viewport_rect.height = netk_screen_get_height (window->priv->screen);

    window_rect.x = window->priv->x + viewport_rect.x;
    window_rect.y = window->priv->y + viewport_rect.y;
    window_rect.width = window->priv->width;
    window_rect.height = window->priv->height;

    return gdk_rectangle_intersect (&viewport_rect, &window_rect,
                                    &window_rect);
}

void
p_netk_window_set_application (NetkWindow * window, NetkApplication * app)
{
    g_return_if_fail (NETK_IS_WINDOW (window));
    g_return_if_fail (app == NULL || NETK_IS_APPLICATION (app));

    if (app)
        g_object_ref (G_OBJECT (app));
    if (window->priv->app)
        g_object_unref (G_OBJECT (window->priv->app));
    window->priv->app = app;
}

void
p_netk_window_set_class_group (NetkWindow     *window,
                             NetkClassGroup *class_group)
{
    g_return_if_fail (NETK_IS_WINDOW (window));
    g_return_if_fail (class_group == NULL || NETK_IS_CLASS_GROUP (class_group));

    if (class_group)
        g_object_ref (G_OBJECT (class_group));

    if (window->priv->class_group)
        g_object_unref (G_OBJECT (window->priv->class_group));

    window->priv->class_group = class_group;
}

void
p_netk_window_process_property_notify (NetkWindow * window, XEvent * xevent)
{
    if (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_STATE"))
    {
        window->priv->need_update_state = TRUE;
        queue_update (window);
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("WM_STATE"))
    {
        window->priv->need_update_wm_state = TRUE;
        queue_update (window);
    }
    else if ((xevent->xproperty.atom == XA_WM_NAME) ||
             (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_NAME")) ||
             (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_VISIBLE_NAME")))
    {
        window->priv->need_update_name = TRUE;
        queue_update (window);
    }
    else if ((xevent->xproperty.atom == XA_WM_ICON_NAME) ||
             (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_ICON_NAME")) ||
             (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_VISIBLE_ICON_NAME")))
    {
        window->priv->need_update_icon_name = TRUE;
        queue_update (window);
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_ALLOWED_ACTIONS"))
    {
        window->priv->need_update_actions = TRUE;
        queue_update (window);
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_DESKTOP"))
    {
        window->priv->need_update_workspace = TRUE;
        queue_update (window);
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_WINDOW_TYPE"))
    {
        window->priv->need_update_wintype = TRUE;
        queue_update (window);
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("WM_TRANSIENT_FOR"))
    {
        window->priv->need_update_transient_for = TRUE;
        window->priv->need_update_wintype = TRUE;
        queue_update (window);
    }
    else if (xevent->xproperty.atom == XA_WM_CLASS)
    {
        window->priv->need_update_wmclass = TRUE;
        queue_update (window);
    }
    else if ((xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_ICON")) ||
             (xevent->xproperty.atom == p_netk_atom_get ("KWM_WIN_ICON")))
    {
        p_netk_icon_cache_property_changed (window->priv->icon_cache,
                                            xevent->xproperty.atom);
        queue_update (window);
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("WM_HINTS"))
    {
        p_netk_icon_cache_property_changed (window->priv->icon_cache,
                                            xevent->xproperty.atom);
        window->priv->need_update_wmhints = TRUE;
        queue_update (window);
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_ICON_GEOMETRY"))
    {
        window->priv->need_update_icon_geometry = TRUE;
        queue_update (window);
    }
}

void
p_netk_window_process_configure_notify (NetkWindow * window, XEvent * xevent)
{
    if (xevent->xconfigure.send_event)
    {
        window->priv->x = xevent->xconfigure.x;
        window->priv->y = xevent->xconfigure.y;
    }
    else
    {
        p_netk_get_window_position (NETK_SCREEN_XSCREEN
                                    (window->priv->screen),
                                    window->priv->xwindow, &window->priv->x,
                                    &window->priv->y);
    }

    window->priv->width = xevent->xconfigure.width;
    window->priv->height = xevent->xconfigure.height;

    emit_geometry_changed (window);
}

static void
update_wm_state (NetkWindow * window)
{
    int state;

    if (!window->priv->need_update_wm_state)
        return;

    window->priv->need_update_wm_state = FALSE;

    window->priv->wm_state_iconic = FALSE;

    state = p_netk_get_wm_state (window->priv->xwindow);

    if (state == IconicState)
        window->priv->wm_state_iconic = TRUE;
}

static void
update_state (NetkWindow * window)
{
    Atom *atoms;
    int n_atoms;
    int i;
    gboolean reread_net_wm_state;

    reread_net_wm_state = window->priv->need_update_state;

    window->priv->need_update_state = FALSE;

    /* This is a bad hack, we always add the
     * state based on window type in to the state,
     * even if no state update is pending (since the
     * state update just means the _NET_WM_STATE prop
     * changed
     */

    if (reread_net_wm_state)
    {
        window->priv->is_maximized_horz = FALSE;
        window->priv->is_maximized_vert = FALSE;
        window->priv->is_sticky = FALSE;
        window->priv->is_shaded = FALSE;
        window->priv->skip_taskbar = FALSE;
        window->priv->skip_pager = FALSE;
        window->priv->net_wm_state_hidden = FALSE;
        window->priv->demands_attention = FALSE;

        atoms = NULL;
        n_atoms = 0;
        p_netk_get_atom_list (window->priv->xwindow,
                              p_netk_atom_get ("_NET_WM_STATE"), &atoms,
                              &n_atoms);

        i = 0;
        while (i < n_atoms)
        {
            if (atoms[i] == p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_VERT"))
                window->priv->is_maximized_vert = TRUE;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_STATE_MAXIMIZED_HORZ"))
                window->priv->is_maximized_horz = TRUE;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_STATE_HIDDEN"))
                window->priv->net_wm_state_hidden = TRUE;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_STATE_STICKY"))
                window->priv->is_sticky = TRUE;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_STATE_SHADED"))
                window->priv->is_shaded = TRUE;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_STATE_FULLSCREEN"))
                window->priv->is_fullscreen = TRUE;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_STATE_SKIP_TASKBAR"))
                window->priv->skip_taskbar = TRUE;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_STATE_SKIP_PAGER"))
                window->priv->skip_pager = TRUE;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_STATE_DEMANDS_ATTENTION"))
                window->priv->demands_attention = TRUE;

            ++i;
        }

        g_free (atoms);
    }

    switch (window->priv->wintype)
    {
        case NETK_WINDOW_DESKTOP:
        case NETK_WINDOW_DOCK:
        case NETK_WINDOW_SPLASHSCREEN:
            window->priv->skip_taskbar = TRUE;
            break;
        case NETK_WINDOW_TOOLBAR:
        case NETK_WINDOW_MENU:
        case NETK_WINDOW_UTILITY:
        case NETK_WINDOW_DIALOG:
        case NETK_WINDOW_MODAL_DIALOG:
            /* Skip taskbar if the window is transient
             * for some main application window
             */
            if (window->priv->transient_for != None)
            {
                window->priv->skip_taskbar = TRUE;
            }
            break;
        case NETK_WINDOW_NORMAL:
            break;
    }

    switch (window->priv->wintype)
    {
        case NETK_WINDOW_DESKTOP:
        case NETK_WINDOW_DOCK:
        case NETK_WINDOW_TOOLBAR:
        case NETK_WINDOW_MENU:
        case NETK_WINDOW_SPLASHSCREEN:
            window->priv->skip_pager = TRUE;
            break;

        case NETK_WINDOW_NORMAL:
        case NETK_WINDOW_DIALOG:
        case NETK_WINDOW_MODAL_DIALOG:
        case NETK_WINDOW_UTILITY:
            break;
    }

    /* FIXME we need to recompute this if the window manager changes */
    if (netk_screen_net_wm_supports
        (window->priv->screen, "_NET_WM_STATE_HIDDEN"))
    {
        window->priv->is_hidden = window->priv->net_wm_state_hidden;
        window->priv->is_minimized = window->priv->is_hidden;
    }
    else
    {
        window->priv->is_minimized = window->priv->wm_state_iconic;
        window->priv->is_hidden = window->priv->is_minimized
            || window->priv->is_shaded;
    }
}

static void
update_name (NetkWindow * window)
{
    g_return_if_fail (window->priv->name == NULL);

    if (!window->priv->need_update_name)
        return;

    window->priv->need_update_name = FALSE;

    window->priv->name = p_netk_get_name (window->priv->xwindow);

    if (window->priv->name == NULL)
        window->priv->name = g_strdup (FALLBACK_NAME);
}

static void
update_icon_name (NetkWindow * window)
{
    g_return_if_fail (window->priv->icon_name == NULL);

    if (!window->priv->need_update_icon_name)
        return;

    window->priv->need_update_icon_name = FALSE;

    if (window->priv->icon_name == NULL)
        window->priv->icon_name =
            p_netk_get_utf8_property (window->priv->xwindow,
                                      p_netk_atom_get
                                      ("_NET_WM_VISIBLE_ICON_NAME"));

    if (window->priv->icon_name == NULL)
        window->priv->icon_name =
            p_netk_get_utf8_property (window->priv->xwindow,
                                      p_netk_atom_get ("_NET_WM_ICON_NAME"));

    if (window->priv->icon_name == NULL)
        window->priv->icon_name =
            p_netk_get_text_property (window->priv->xwindow, XA_WM_ICON_NAME);
}

static void
update_workspace (NetkWindow * window)
{
    int val;
    int old;

    if (!window->priv->need_update_workspace)
        return;

    window->priv->need_update_workspace = FALSE;

    old = window->priv->workspace;

    val = ALL_WORKSPACES;
    p_netk_get_cardinal (window->priv->xwindow,
                         p_netk_atom_get ("_NET_WM_DESKTOP"), &val);

    window->priv->workspace = val;

    if (old != window->priv->workspace)
        emit_workspace_changed (window);
}

static void
update_actions (NetkWindow * window)
{
    Atom *atoms;
    int n_atoms;
    int i;

    if (!window->priv->need_update_actions)
        return;

    window->priv->need_update_actions = FALSE;

    window->priv->actions = 0;

    if (!p_netk_get_atom_list
        (window->priv->xwindow, p_netk_atom_get ("_NET_WM_ALLOWED_ACTIONS"),
         &atoms, &n_atoms))
    {
        window->priv->actions =
            NETK_WINDOW_ACTION_MOVE |
            NETK_WINDOW_ACTION_RESIZE | NETK_WINDOW_ACTION_SHADE |
            NETK_WINDOW_ACTION_STICK |
            NETK_WINDOW_ACTION_MAXIMIZE_HORIZONTALLY |
            NETK_WINDOW_ACTION_MAXIMIZE_VERTICALLY |
            NETK_WINDOW_ACTION_CHANGE_WORKSPACE | NETK_WINDOW_ACTION_CLOSE |
            NETK_WINDOW_ACTION_UNMAXIMIZE_HORIZONTALLY |
            NETK_WINDOW_ACTION_UNMAXIMIZE_VERTICALLY |
            NETK_WINDOW_ACTION_UNSHADE | NETK_WINDOW_ACTION_UNSTICK |
            NETK_WINDOW_ACTION_MAXIMIZE | NETK_WINDOW_ACTION_UNMAXIMIZE |
            NETK_WINDOW_ACTION_MINIMIZE | NETK_WINDOW_ACTION_UNMINIMIZE;
        return;
    }

    i = 0;
    while (i < n_atoms)
    {
        if (atoms[i] == p_netk_atom_get ("_NET_WM_ACTION_MOVE"))
            window->priv->actions |= NETK_WINDOW_ACTION_MOVE;

        else if (atoms[i] == p_netk_atom_get ("_NET_WM_ACTION_RESIZE"))
            window->priv->actions |= NETK_WINDOW_ACTION_RESIZE;

        else if (atoms[i] == p_netk_atom_get ("_NET_WM_ACTION_SHADE"))
            window->priv->actions |=
                NETK_WINDOW_ACTION_SHADE | NETK_WINDOW_ACTION_UNSHADE;

        else if (atoms[i] == p_netk_atom_get ("_NET_WM_ACTION_STICK"))
            window->priv->actions |=
                NETK_WINDOW_ACTION_STICK | NETK_WINDOW_ACTION_UNSTICK;

        else if (atoms[i] == p_netk_atom_get ("_NET_WM_ACTION_MAXIMIZE_HORZ"))
            window->priv->actions |=
                NETK_WINDOW_ACTION_MAXIMIZE_HORIZONTALLY |
                NETK_WINDOW_ACTION_UNMAXIMIZE_HORIZONTALLY;

        else if (atoms[i] == p_netk_atom_get ("_NET_WM_ACTION_MAXIMIZE_VERT"))
            window->priv->actions |=
                NETK_WINDOW_ACTION_MAXIMIZE_VERTICALLY |
                NETK_WINDOW_ACTION_UNMAXIMIZE_VERTICALLY;

        else if (atoms[i] ==
                 p_netk_atom_get ("_NET_WM_ACTION_CHANGE_DESKTOP"))
            window->priv->actions |= NETK_WINDOW_ACTION_CHANGE_WORKSPACE;

        else if (atoms[i] == p_netk_atom_get ("_NET_WM_ACTION_CLOSE"))
            window->priv->actions |= NETK_WINDOW_ACTION_CLOSE;

        i++;
    }

    g_free (atoms);

    if ((window->priv->actions & NETK_WINDOW_ACTION_MAXIMIZE_HORIZONTALLY)
        && (window->priv->actions & NETK_WINDOW_ACTION_MAXIMIZE_VERTICALLY))
        window->priv->actions |=
            NETK_WINDOW_ACTION_MAXIMIZE | NETK_WINDOW_ACTION_UNMAXIMIZE;

    /* These are always enabled */
    window->priv->actions |=
        NETK_WINDOW_ACTION_MINIMIZE | NETK_WINDOW_ACTION_UNMINIMIZE;
}

static void
update_wintype (NetkWindow * window)
{
    Atom *atoms;
    int n_atoms;
    NetkWindowType type;
    gboolean found_type;

    if (!window->priv->need_update_wintype)
        return;

    window->priv->need_update_wintype = FALSE;

    found_type = FALSE;
    type = NETK_WINDOW_NORMAL;

    if (p_netk_get_atom_list
        (window->priv->xwindow, p_netk_atom_get ("_NET_WM_WINDOW_TYPE"),
         &atoms, &n_atoms))
    {
        int i;

        i = 0;
        while (i < n_atoms && !found_type)
        {
            /* We break as soon as we find one we recognize,
             * supposed to prefer those near the front of the list
             */
            found_type = TRUE;
            if (atoms[i] == p_netk_atom_get ("_NET_WM_WINDOW_TYPE_DESKTOP"))
                type = NETK_WINDOW_DESKTOP;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_WINDOW_TYPE_DOCK"))
                type = NETK_WINDOW_DOCK;
            else if (atoms[i] ==
                     p_netk_atom_get ("_NET_WM_WINDOW_TYPE_TOOLBAR"))
                type = NETK_WINDOW_TOOLBAR;
            else if (atoms[i] == p_netk_atom_get ("_NET_WM_WINDOW_TYPE_MENU"))
                type = NETK_WINDOW_MENU;
            else if (atoms[i] ==
                     p_netk_atom_get ("_NET_WM_WINDOW_TYPE_DIALOG"))
                type = NETK_WINDOW_DIALOG;
            else if (atoms[i] ==
                     p_netk_atom_get ("_NET_WM_WINDOW_TYPE_NORMAL"))
                type = NETK_WINDOW_NORMAL;
            else if (atoms[i] ==
                     p_netk_atom_get ("_NET_WM_WINDOW_TYPE_MODAL_DIALOG"))
                type = NETK_WINDOW_MODAL_DIALOG;
            else if (atoms[i] ==
                     p_netk_atom_get ("_NET_WM_WINDOW_TYPE_UTILITY"))
                type = NETK_WINDOW_UTILITY;
            else if (atoms[i] ==
                     p_netk_atom_get ("_NET_WM_WINDOW_TYPE_SPLASH"))
                type = NETK_WINDOW_SPLASHSCREEN;
            else
                found_type = FALSE;

            ++i;
        }

        g_free (atoms);
    }

    if (!found_type)
    {
        if (window->priv->transient_for != None)
        {
            type = NETK_WINDOW_DIALOG;
        }
        else
        {
            type = NETK_WINDOW_NORMAL;
        }
        found_type = TRUE;
    }

    window->priv->wintype = type;
}

static void
update_transient_for (NetkWindow * window)
{
    Window parent;

    if (!window->priv->need_update_transient_for)
        return;

    window->priv->need_update_transient_for = FALSE;

    if (p_netk_get_window
        (window->priv->xwindow, p_netk_atom_get ("WM_TRANSIENT_FOR"),
         &parent))
    {
        window->priv->transient_for = parent;
        if (netk_screen_get_for_root (window->priv->transient_for) != NULL)
            window->priv->transient_for_root = TRUE;
        else
            window->priv->transient_for_root = FALSE;
    }
    else
    {
        window->priv->transient_for = None;
        window->priv->transient_for_root = FALSE;
    }
}

static void
update_wmclass (NetkWindow *window)
{
    if (!window->priv->need_update_wmclass)
      return;

    window->priv->need_update_wmclass = FALSE;

    g_free (window->priv->res_class);
    g_free (window->priv->res_name);

    p_netk_get_wmclass (window->priv->xwindow,
                        &window->priv->res_class,
                        &window->priv->res_name);
}

static void 
update_icon_geometry (NetkWindow *window)
{
    if (!window->priv->need_update_icon_geometry)
        return;
        
    window->priv->need_update_icon_geometry = FALSE;
    if (!p_netk_get_icon_geometry(window->priv->xwindow, &window->priv->icon_geometry.x, &window->priv->icon_geometry.y, &window->priv->icon_geometry.width, &window->priv->icon_geometry.height)) {
        window->priv->icon_geometry.width = -1;
    }
}

static void
update_wmhints (NetkWindow *window)
{
    if (!window->priv->need_update_wmhints)
      return;

    window->priv->need_update_wmhints = FALSE;
    if (p_netk_is_urgent (window->priv->xwindow))
    {
        window->priv->is_urgent = TRUE;
    }
    else
    {
        window->priv->is_urgent = FALSE;
    }
}

static void
force_update_now (NetkWindow * window)
{
    NetkWindowState old_state;
    NetkWindowState new_state;
    NetkWindowActions old_actions;
    char *old_name;
    char *old_icon_name;

    unqueue_update (window);

    /* Name must be done before all other stuff,
     * because we have iconsistent state across the
     * update_name/update_icon_name functions (no window name),
     * and we have to fix that before we emit any other signals
     */

    old_name = window->priv->name;
    window->priv->name = NULL;
    update_name (window);

    if (window->priv->name == NULL)
        window->priv->name = old_name;
    else
    {
        if (strcmp (window->priv->name, old_name) != 0)
            emit_name_changed (window);
        g_free (old_name);
    }

    old_icon_name = window->priv->icon_name;
    window->priv->icon_name = NULL;
    update_icon_name (window);
    
    if (window->priv->icon_name == NULL)
    {
        window->priv->icon_name = old_icon_name;
    }
    else
    {
        if (old_icon_name == NULL ||
            strcmp (window->priv->icon_name, old_icon_name) != 0)
            emit_name_changed (window);
        g_free (old_icon_name);
    }

    old_state = COMPRESS_STATE (window);
    old_actions = window->priv->actions;

    update_wmclass (window);
    update_wmhints (window);
    update_transient_for (window);      /* wintype needs this to be first */
    update_wintype (window);
    update_icon_geometry (window);
    update_wm_state (window);
    update_state (window);      /* must come after the above, since they affect
                                 * our calculated state
                                 */
    update_workspace (window);  /* emits signals */
    update_actions (window);

    get_icons (window);

    new_state = COMPRESS_STATE (window);
    if (old_state != new_state)
    {
        NetkWindowState changed_state = old_state ^ new_state;

        emit_state_changed (window, changed_state, new_state);
        /* Hack, pwack, arck, crack...!!! */
        if ((changed_state & NETK_WINDOW_STATE_SKIP_TASKLIST) &&
            (old_state & NETK_WINDOW_STATE_SKIP_TASKLIST))
        {
            emit_geometry_changed (window);
        }
    }
    if (old_actions != window->priv->actions)
        emit_actions_changed (window, old_actions ^ window->priv->actions,
                              window->priv->actions);

    if (window->priv->need_emit_icon_changed)
        emit_icon_changed (window);
}


static gboolean
update_idle (gpointer data)
{
    NetkWindow *window = NETK_WINDOW (data);

    window->priv->update_handler = 0;
    force_update_now (window);
    return FALSE;
}

static void
queue_update (NetkWindow * window)
{
    if (window->priv->update_handler != 0)
        return;

    window->priv->update_handler = g_idle_add (update_idle, window);
}

static void
unqueue_update (NetkWindow * window)
{
    if (window->priv->update_handler != 0)
    {
        g_source_remove (window->priv->update_handler);
        window->priv->update_handler = 0;
    }
}

static void
emit_name_changed (NetkWindow * window)
{
    g_signal_emit (G_OBJECT (window), signals[NAME_CHANGED], 0);
}

static void
emit_state_changed (NetkWindow * window, NetkWindowState changed_mask,
                    NetkWindowState new_state)
{
    g_signal_emit (G_OBJECT (window), signals[STATE_CHANGED], 0, changed_mask,
                   new_state);
}

static void
emit_workspace_changed (NetkWindow * window)
{
    g_signal_emit (G_OBJECT (window), signals[WORKSPACE_CHANGED], 0);
}

static void
emit_icon_changed (NetkWindow * window)
{
    window->priv->need_emit_icon_changed = FALSE;
    g_signal_emit (G_OBJECT (window), signals[ICON_CHANGED], 0);
}

static void
emit_actions_changed (NetkWindow * window, NetkWindowActions changed_mask,
                      NetkWindowActions new_actions)
{
    g_signal_emit (G_OBJECT (window), signals[ACTIONS_CHANGED], 0,
                   changed_mask, new_actions);
}

static void
emit_geometry_changed (NetkWindow * window)
{
    g_signal_emit (G_OBJECT (window), signals[GEOMETRY_CHANGED], 0);
}
