/* screen object */

/*
 * Copyright (C) 2001 Havoc Pennington
 * Copyright (C) 2003 Kim Woelders
 * Copyright (C) 2003 Red Hat, Inc.
 *
 * 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_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef GDK_MULTIHEAD_SAFE
#undef GDK_MULTIHEAD_SAFE
#endif

#include <gdk/gdk.h>
#include <gdk/gdkx.h>

#include "netk-screen.h"
#include "netk-window.h"
#include "netk-workspace.h"
#include "netk-application.h"
#include "netk-class-group.h"
#include "netk-xutils.h"
#include "netk-private.h"

#define NETK_SCREEN_GET_PRIVATE(o)    (G_TYPE_INSTANCE_GET_PRIVATE ((o), NETK_TYPE_SCREEN, NetkScreenPrivate))

static NetkScreen **screens = NULL;

struct _NetkScreenPrivate
{
    int number;
    Window xroot;
    Screen *xscreen;

    /* in map order */
    GList *mapped_windows;
    /* in stacking order */
    GList *stacked_windows;
    /* in 0-to-N order */
    GList *workspaces;

    NetkWindow *active_window;
    NetkWorkspace *active_workspace;

    Pixmap bg_pixmap;

    guint update_handler;

    guint showing_desktop:1;

    /* if you add flags, be sure to set them
       * when we create the screen so we get an initial update
     */
    guint need_update_stack_list:1;
    guint need_update_workspace_list:1;
    guint need_update_viewport_settings:1;
    guint need_update_active_workspace:1;
    guint need_update_active_window:1;
    guint need_update_workspace_names:1;
    guint need_update_bg_pixmap:1;
    guint need_update_showing_desktop:1;
};

enum
{
    ACTIVE_WINDOW_CHANGED,
    ACTIVE_WORKSPACE_CHANGED,
    WINDOW_STACKING_CHANGED,
    WINDOW_OPENED,
    WINDOW_CLOSED,
    WORKSPACE_CREATED,
    WORKSPACE_DESTROYED,
    APPLICATION_OPENED,
    APPLICATION_CLOSED,
    CLASS_GROUP_OPENED,
    CLASS_GROUP_CLOSED,
    BACKGROUND_CHANGED,
    SHOWING_DESKTOP_CHANGED,
    VIEWPORTS_CHANGED,
    LAST_SIGNAL
};

static void netk_screen_init (NetkScreen * screen);
static void netk_screen_class_init (NetkScreenClass * klass);
static void netk_screen_finalize (GObject * object);

static void update_client_list (NetkScreen * screen);
static void update_workspace_list (NetkScreen * screen);
static void update_viewport_settings (NetkScreen * screen);
static void update_active_workspace (NetkScreen * screen);
static void update_active_window (NetkScreen * screen);
static void update_workspace_names (NetkScreen * screen);
static void update_showing_desktop (NetkScreen * screen);

static void queue_update (NetkScreen * screen);
static void unqueue_update (NetkScreen * screen);
static void do_update_now (NetkScreen * screen);

static void emit_active_window_changed (NetkScreen * screen);
static void emit_active_workspace_changed (NetkScreen * screen);
static void emit_window_stacking_changed (NetkScreen * screen);
static void emit_window_opened (NetkScreen * screen, NetkWindow * window);
static void emit_window_closed (NetkScreen * screen, NetkWindow * window);
static void emit_workspace_created (NetkScreen * screen,
                                    NetkWorkspace * space);
static void emit_workspace_destroyed (NetkScreen * screen,
                                      NetkWorkspace * space);
static void emit_application_opened (NetkScreen * screen,
                                     NetkApplication * app);
static void emit_application_closed (NetkScreen * screen,
                                     NetkApplication * app);
static void emit_class_group_opened (NetkScreen * screen,
                                     NetkClassGroup * class_group);
static void emit_class_group_closed (NetkScreen * screen,
                                     NetkClassGroup * class_group);
static void emit_background_changed (NetkScreen * screen);
static void emit_showing_desktop_changed (NetkScreen * screen);
static void emit_viewports_changed (NetkScreen * screen);

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

GType
netk_screen_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,
                                             "NetkScreen",
                                             sizeof (NetkScreenClass),
                                             netk_screen_class_init,
                                             sizeof (NetkScreen),
                                             netk_screen_init,
                                             0);                                    
    }

    return type;
}

static void
netk_screen_init (NetkScreen * screen)
{
    screen->priv = NETK_SCREEN_GET_PRIVATE (screen);
}

static void
netk_screen_class_init (NetkScreenClass * klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    
    g_type_class_add_private (klass, sizeof (NetkScreenPrivate));

    p_netk_init ();

    parent_class = g_type_class_peek_parent (klass);

    object_class->finalize = netk_screen_finalize;

    signals[ACTIVE_WINDOW_CHANGED] =
        g_signal_new (I_("active_window_changed"),
                      G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass,
                                       active_window_changed), NULL, NULL,
                      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

    signals[ACTIVE_WORKSPACE_CHANGED] =
        g_signal_new (I_("active_workspace_changed"),
                      G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass,
                                       active_workspace_changed), NULL, NULL,
                      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

    signals[WINDOW_STACKING_CHANGED] =
        g_signal_new (I_("window_stacking_changed"),
                      G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass,
                                       window_stacking_changed), NULL, NULL,
                      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

    signals[WINDOW_OPENED] =
        g_signal_new (I_("window_opened"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NetkScreenClass,
                                                          window_opened),
                      NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                      G_TYPE_NONE, 1, NETK_TYPE_WINDOW);

    signals[WINDOW_CLOSED] =
        g_signal_new (I_("window_closed"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NetkScreenClass,
                                                          window_closed),
                      NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                      G_TYPE_NONE, 1, NETK_TYPE_WINDOW);

    signals[WORKSPACE_CREATED] =
        g_signal_new (I_("workspace_created"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NetkScreenClass,
                                                          workspace_created),
                      NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                      G_TYPE_NONE, 1, NETK_TYPE_WORKSPACE);

    signals[WORKSPACE_DESTROYED] =
        g_signal_new (I_("workspace_destroyed"),
                      G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass, workspace_destroyed),
                      NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                      G_TYPE_NONE, 1, NETK_TYPE_WORKSPACE);

    signals[APPLICATION_OPENED] =
        g_signal_new (I_("application_opened"),
                      G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass, application_opened),
                      NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                      G_TYPE_NONE, 1, NETK_TYPE_APPLICATION);

    signals[APPLICATION_CLOSED] =
        g_signal_new (I_("application_closed"),
                      G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass, application_closed),
                      NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                      G_TYPE_NONE, 1, NETK_TYPE_APPLICATION);

    signals[CLASS_GROUP_OPENED] =
        g_signal_new (I_("class_group_opened"),
                      G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass, class_group_opened),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__OBJECT,
                      G_TYPE_NONE, 1, NETK_TYPE_CLASS_GROUP);

    signals[CLASS_GROUP_CLOSED] =
        g_signal_new (I_("class_group_closed"),
                      G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass, class_group_closed),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__OBJECT,
                      G_TYPE_NONE, 1, NETK_TYPE_CLASS_GROUP);

    signals[BACKGROUND_CHANGED] =
        g_signal_new (I_("background_changed"),
                      G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass, background_changed),
                      NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
                      0);

    signals[SHOWING_DESKTOP_CHANGED] =
        g_signal_new (I_("showing_desktop_changed"),
                      G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass, showing_desktop_changed),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

    signals[VIEWPORTS_CHANGED] =
        g_signal_new (I_("viewports_changed"),
                      G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkScreenClass, viewports_changed),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
}

static void
netk_screen_finalize (GObject * object)
{
    NetkScreen *screen;

    screen = NETK_SCREEN (object);

    unqueue_update (screen);

    /* FIXME this isn't right, we need to destroy the items
     * in the list. But it doesn't matter at the
     * moment since we never finalize screens anyway.
     */
    g_list_free (screen->priv->mapped_windows);
    g_list_free (screen->priv->stacked_windows);
    g_list_free (screen->priv->workspaces);

    screens[screen->priv->number] = NULL;

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

static void
netk_screen_construct (NetkScreen * screen, int number)
{
    /* Create the initial state of the screen. */
    screen->priv->xroot = RootWindow (gdk_display, number);
    screen->priv->xscreen = ScreenOfDisplay (gdk_display, number);
    screen->priv->number = number;

    screen->priv->bg_pixmap = None;

    p_netk_select_input (screen->priv->xroot, PropertyChangeMask);

    screen->priv->need_update_workspace_list = TRUE;
    screen->priv->need_update_stack_list = TRUE;
    screen->priv->need_update_viewport_settings = TRUE;
    screen->priv->need_update_active_workspace = TRUE;
    screen->priv->need_update_active_window = TRUE;
    screen->priv->need_update_workspace_names = TRUE;
    screen->priv->need_update_bg_pixmap = TRUE;
    screen->priv->need_update_showing_desktop = TRUE;

    do_update_now (screen);
}

/**
 * netk_screen_get:
 * @index: screen number, starting from 0, as for Xlib
 * 
 * Gets the #NetkScreen for a given screen on the default display.
 * Creates the #NetkScreen if necessary.
 * 
 * Return value: the #NetkScreen for screen @index
 **/
NetkScreen *
netk_screen_get (int index)
{
    g_return_val_if_fail (gdk_display != NULL, NULL);
    g_return_val_if_fail (index < ScreenCount (gdk_display), NULL);

    if (screens == NULL)
    {
        screens = g_new0 (NetkScreen *, ScreenCount (gdk_display));
        p_netk_event_filter_init ();
    }

    if (screens[index] == NULL)
    {
        screens[index] = g_object_new (NETK_TYPE_SCREEN, NULL);

        netk_screen_construct (screens[index], index);
    }

    return screens[index];
}

NetkScreen *
p_netk_screen_get_existing (int number)
{
    g_return_val_if_fail (gdk_display != NULL, NULL);
    g_return_val_if_fail (number < ScreenCount (gdk_display), NULL);

    if (screens != NULL)
        return screens[number];
    else
        return NULL;
}

NetkScreen *
netk_screen_get_default (void)
{
    int default_screen;

    default_screen = DefaultScreen (gdk_display);

    return netk_screen_get (default_screen);
}

/**
 * netk_screen_get_for_root:
 * @root_window_id: an Xlib window ID
 * 
 * Gets the #NetkScreen for the root window at @root_window_id, or
 * returns %NULL if no #NetkScreen exists for this root window. Never
 * creates a new #NetkScreen, unlike netk_screen_get().
 * 
 * Return value: #NetkScreen or %NULL
 **/
NetkScreen *
netk_screen_get_for_root (gulong root_window_id)
{
    int i;

    if (screens == NULL)
        return NULL;

    i = 0;
    while (i < ScreenCount (gdk_display))
    {
        if (screens[i] != NULL && screens[i]->priv->xroot == root_window_id)
            return screens[i];

        ++i;
    }

    return NULL;
}

/**
 * netk_screen_get_workspace:
 * @screen: a #NetkScreen
 * @number: a workspace index
 * 
 * Gets the workspace numbered @number for screen @screen, or returns %NULL if
 * no such workspace exists.
 * 
 * Return value: the workspace, or %NULL
 **/
NetkWorkspace *
netk_screen_get_workspace (NetkScreen * screen, int number)
{
    GList *list;

    /* We trust this function with property-provided numbers, it
     * must reliably return NULL on bad data
     */
    list = g_list_nth (screen->priv->workspaces, number);

    if (list == NULL)
        return NULL;

    return NETK_WORKSPACE (list->data);
}

/**
 * netk_screen_get_active_workspace:
 * @screen: a #NetkScreen 
 * 
 * Gets the active workspace. May return %NULL sometimes,
 * if we are in a weird state due to the asynchronous
 * nature of our interaction with the window manager.
 * 
 * Return value: active workspace or %NULL
 **/
NetkWorkspace *
netk_screen_get_active_workspace (NetkScreen * screen)
{
    g_return_val_if_fail (NETK_IS_SCREEN (screen), NULL);

    return screen->priv->active_workspace;
}

/**
 * netk_screen_get_active_window:
 * @screen: a #NetkScreen
 * 
 * Gets the active window. May return %NULL sometimes,
 * since not all window managers guarantee that a window
 * is always active.
 * 
 * Return value: active window or %NULL
 **/
NetkWindow *
netk_screen_get_active_window (NetkScreen * screen)
{
    g_return_val_if_fail (NETK_IS_SCREEN (screen), NULL);

    return screen->priv->active_window;
}

/**
 * netk_screen_get_workspaces:
 * @screen: a #NetkScreen
 * 
 * Gets the screen's list of workspaces. 
 * The list is not copied and should not be freed. 
 *
 * Return value: reference to list of workspaces
 **/
GList*
netk_screen_get_workspaces (NetkScreen * screen)
{
    return screen->priv->workspaces;
}

/**
 * netk_screen_get_windows:
 * @screen: a #NetkScreen
 * 
 * Gets the screen's list of windows. The list is not copied
 * and should not be freed. The list is not in a defined order,
 * but should be "stable" (windows shouldn't reorder themselves in
 * it). (However, the stability of the list is established by
 * the window manager, so don't blame libnetk if it breaks down.)
 * 
 * Return value: reference to list of windows
 **/
GList *
netk_screen_get_windows (NetkScreen * screen)
{
    g_return_val_if_fail (NETK_IS_SCREEN (screen), NULL);

    return screen->priv->mapped_windows;
}

/**
 * netk_screen_get_windows_stacked:
 * @screen: a #NetkScreen
 * 
 * Gets the screen's list of windows in bottom-to-top order.
 * The list is not copied and should not be freed.
 * 
 * Return value: reference to list of windows in stacking order
 **/
GList *
netk_screen_get_windows_stacked (NetkScreen * screen)
{
    g_return_val_if_fail (NETK_IS_SCREEN (screen), NULL);

    return screen->priv->stacked_windows;
}

/**
 * netk_screen_force_update:
 * @screen: a #NetkScreen
 * 
 * Synchronously and immediately update the window list.  This is
 * usually a bad idea for both performance and correctness reasons (to
 * get things right, you need to write model-view code that tracks
 * changes, not get a static list of open windows).
 * 
 **/
void
netk_screen_force_update (NetkScreen * screen)
{
    g_return_if_fail (NETK_IS_SCREEN (screen));

    do_update_now (screen);
}

/**
 * netk_screen_get_workspace_count:
 * @screen: a #NetkScreen
 * 
 * Gets the number of workspaces.
 * 
 * Return value: number of workspaces
 **/
int
netk_screen_get_workspace_count (NetkScreen * screen)
{
    g_return_val_if_fail (NETK_IS_SCREEN (screen), 0);

    return g_list_length (screen->priv->workspaces);
}

/**
 * netk_screen_change_workspace_count:
 * @screen: a #NetkScreen
 * @count: requested count
 * 
 * Asks the window manager to change the number of workspaces.
 **/
void
netk_screen_change_workspace_count (NetkScreen * screen, int count)
{
    XEvent xev;

    g_return_if_fail (NETK_IS_SCREEN (screen));
    g_return_if_fail (count >= 1);

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.window = screen->priv->xroot;
    xev.xclient.send_event = True;
    xev.xclient.display = DisplayOfScreen (screen->priv->xscreen);
    xev.xclient.message_type = p_netk_atom_get ("_NET_NUMBER_OF_DESKTOPS");
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = count;

    XSendEvent (DisplayOfScreen (screen->priv->xscreen),
                screen->priv->xroot,
                False,
                SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

void
p_netk_screen_process_property_notify (NetkScreen * screen, XEvent * xevent)
{
    /* most frequently-changed properties first */
    if (xevent->xproperty.atom == p_netk_atom_get ("_NET_ACTIVE_WINDOW"))
    {
        screen->priv->need_update_active_window = TRUE;
        queue_update (screen);
    }
    else if (xevent->xproperty.atom ==
             p_netk_atom_get ("_NET_CURRENT_DESKTOP"))
    {
        screen->priv->need_update_active_workspace = TRUE;
        queue_update (screen);
    }
    else if (xevent->xproperty.atom ==
             p_netk_atom_get ("_NET_CLIENT_LIST_STACKING")
             || xevent->xproperty.atom ==
             p_netk_atom_get ("_NET_CLIENT_LIST"))
    {
        screen->priv->need_update_stack_list = TRUE;
        queue_update (screen);
    }
    else if (xevent->xproperty.atom ==
             p_netk_atom_get ("_NET_DESKTOP_VIEWPORT"))
    {
        screen->priv->need_update_viewport_settings = TRUE;
        queue_update (screen);
    }
    else if (xevent->xproperty.atom ==
             p_netk_atom_get ("_NET_DESKTOP_GEOMETRY"))
    {
        screen->priv->need_update_viewport_settings = TRUE;
        queue_update (screen);
    }
    else if (xevent->xproperty.atom ==
             p_netk_atom_get ("_NET_NUMBER_OF_DESKTOPS"))
    {
        screen->priv->need_update_workspace_list = TRUE;
        queue_update (screen);
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("_NET_DESKTOP_NAMES"))
    {
        screen->priv->need_update_workspace_names = TRUE;
        queue_update (screen);
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("_XROOTPMAP_ID"))
    {
        screen->priv->need_update_bg_pixmap = TRUE;
        queue_update (screen);
    }
    else if (xevent->xproperty.atom ==
             p_netk_atom_get ("_NET_SHOWING_DESKTOP"))
    {
        screen->priv->need_update_showing_desktop = TRUE;
        queue_update (screen);
    }
}

static gboolean
lists_equal (GList * a, GList * b)
{
    GList *a_iter;
    GList *b_iter;

    a_iter = a;
    b_iter = b;

    while (a_iter && b_iter)
    {
        if (a_iter->data != b_iter->data)
            return FALSE;

        a_iter = a_iter->next;
        b_iter = b_iter->next;
    }

    if (a_iter || b_iter)
        return FALSE;

    return TRUE;
}

static int
wincmp (const void *a, const void *b)
{
    const Window *aw = a;
    const Window *bw = b;

    if (*aw < *bw)
        return -1;
    else if (*aw > *bw)
        return 1;
    else
        return 0;
}

static gboolean
arrays_contain_same_windows (Window * a, int a_len, Window * b, int b_len)
{
    Window *a_tmp;
    Window *b_tmp;
    gboolean result;

    if (a_len != b_len)
        return FALSE;

    if (a_len == 0 || b_len == 0)
        return FALSE;           /* one was nonzero */

    a_tmp = g_new (Window, a_len);
    b_tmp = g_new (Window, b_len);

    memcpy (a_tmp, a, a_len * sizeof (Window));
    memcpy (b_tmp, b, b_len * sizeof (Window));

    qsort (a_tmp, a_len, sizeof (Window), wincmp);
    qsort (b_tmp, b_len, sizeof (Window), wincmp);

    result = memcmp (a_tmp, b_tmp, sizeof (Window) * a_len) == 0;

    g_free (a_tmp);
    g_free (b_tmp);

    return result;
}

static void
update_client_list (NetkScreen * screen)
{
    /* stacking order */
    Window *stack = NULL;
    int stack_length = 0;
    /* mapping order */
    Window *mapping = NULL;
    int mapping_length = 0;
    GList *new_stack_list;
    GList *new_list;
    GList *created;
    GList *closed;
    GList *created_apps, *closed_apps;
    GList *created_class_groups, *closed_class_groups;
    GList *tmp;
    int i;
    GHashTable *new_hash;
    static int reentrancy_guard = 0;
    gboolean active_changed;
    gboolean stack_changed;
    gboolean list_changed;

    g_return_if_fail (reentrancy_guard == 0);

    if (!screen->priv->need_update_stack_list)
        return;

    ++reentrancy_guard;

    screen->priv->need_update_stack_list = FALSE;

    p_netk_get_window_list (screen->priv->xroot,
                            p_netk_atom_get ("_NET_CLIENT_LIST_STACKING"),
                            &stack, &stack_length);

    p_netk_get_window_list (screen->priv->xroot,
                            p_netk_atom_get ("_NET_CLIENT_LIST"), &mapping,
                            &mapping_length);

    if (!arrays_contain_same_windows
        (stack, stack_length, mapping, mapping_length))
    {
        /* Don't update until we're in a consistent state */
        g_free (stack);
        g_free (mapping);
        --reentrancy_guard;
        return;
    }

    created = NULL;
    closed = NULL;
    created_apps = NULL;
    closed_apps = NULL;
    created_class_groups = NULL;
    closed_class_groups = NULL;

    new_hash = g_hash_table_new (NULL, NULL);

    new_stack_list = NULL;
    i = 0;
    while (i < stack_length)
    {
        NetkWindow *window;

        window = netk_window_get (stack[i]);

        if (window == NULL)
        {
            const char *res_class;
            NetkClassGroup *class_group;
            gulong xid;
            Window leader;
            NetkApplication *app;

            window = p_netk_window_create (stack[i], screen);
            created = g_list_prepend (created, window);

            /* Application */
            leader = netk_window_get_group_leader (window);
            app = netk_application_get (leader);
            if (app == NULL)
            {
                app = p_netk_application_create (leader, screen);
                created_apps = g_list_prepend (created_apps, app);
            }

            p_netk_application_add_window (app, window);

            /* Class group */
            xid = netk_window_get_xid (window);
            res_class = netk_window_get_resource_class (window);
            class_group = netk_class_group_get (res_class);
            if (class_group == NULL)
            {
                class_group = p_netk_class_group_create (res_class);
                created_class_groups = g_list_prepend (created_class_groups, class_group);
            }

            p_netk_class_group_add_window (class_group, window);
        }

        new_stack_list = g_list_prepend (new_stack_list, window);

        g_hash_table_insert (new_hash, window, window);

        ++i;
    }

    /* put list back in order */
    new_stack_list = g_list_reverse (new_stack_list);

    /* Now we need to find windows in the old list that aren't
     * in this new list
     */
    tmp = screen->priv->stacked_windows;
    while (tmp != NULL)
    {
        NetkWindow *window = tmp->data;

        if (g_hash_table_lookup (new_hash, window) == NULL)
        {
            NetkApplication *app;
            NetkClassGroup *class_group;

            closed = g_list_prepend (closed, window);

            /* Remove from the app */
            app = netk_window_get_application (window);

            p_netk_application_remove_window (app, window);

            if (netk_application_get_windows (app) == NULL)
            {
                closed_apps = g_list_prepend (closed_apps, app);
            }
 
            /* Remove from the class group */

            class_group = netk_window_get_class_group (window);
            p_netk_class_group_remove_window (class_group, window);

            if (netk_class_group_get_windows (class_group) == NULL)
            {
              closed_class_groups = g_list_prepend (closed_class_groups, class_group);
            }
        }

        tmp = tmp->next;
    }

    g_hash_table_destroy (new_hash);

    /* Now get the mapping in list form */
    new_list = NULL;
    i = 0;
    while (i < mapping_length)
    {
        NetkWindow *window;

        window = netk_window_get (mapping[i]);

        g_assert (window != NULL);

        new_list = g_list_prepend (new_list, window);

        ++i;
    }

    g_free (stack);
    g_free (mapping);

    /* put list back in order */
    new_list = g_list_reverse (new_list);

    /* Now new_stack_list becomes screen->priv->stack_windows, new_list
     * becomes screen->priv->mapped_windows, and we emit the opened/closed
     * signals as appropriate
     */

    stack_changed =
        !lists_equal (screen->priv->stacked_windows, new_stack_list);
    list_changed = !lists_equal (screen->priv->mapped_windows, new_list);

    if (!(stack_changed || list_changed))
    {
        g_assert (created == NULL);
        g_assert (closed == NULL);
        g_assert (created_apps == NULL);
        g_assert (closed_apps == NULL);
        g_assert (created_class_groups == NULL);
        g_assert (closed_class_groups == NULL);
        g_list_free (new_stack_list);
        g_list_free (new_list);
        --reentrancy_guard;
        return;
    }

    g_list_free (screen->priv->mapped_windows);
    g_list_free (screen->priv->stacked_windows);
    screen->priv->mapped_windows = new_list;
    screen->priv->stacked_windows = new_stack_list;

    /* Here we could get reentrancy if someone ran the main loop in
     * signal callbacks; though that would be a bit pathological, so we
     * don't handle it, but we do warn about it using reentrancy_guard
     */

    /* Sequence is: class_group_opened, application_opened, window_opened,
     * window_closed, application_closed, class_group_closed. We have to do all
     * window list changes BEFORE doing any other signals, so that any observers
     * have valid state for the window structure before they take further action
     */
    for (tmp = created_class_groups; tmp; tmp = tmp->next)
    {
      emit_class_group_opened (screen, NETK_CLASS_GROUP (tmp->data));
    }

    for (tmp = created_apps; tmp; tmp = tmp->next)
    {
      emit_application_opened (screen, NETK_APPLICATION (tmp->data));
    }

    for (tmp = created; tmp; tmp = tmp->next)
    {
      emit_window_opened (screen, NETK_WINDOW (tmp->data));
    }

    active_changed = FALSE;
    for (tmp = closed; tmp; tmp = tmp->next)
    {
        NetkWindow *window;

        window = NETK_WINDOW (tmp->data);

        if (window == screen->priv->active_window)
        {
            screen->priv->active_window = NULL;
            active_changed = TRUE;
        }

        emit_window_closed (screen, window);
    }

    for (tmp = closed_apps; tmp; tmp = tmp->next)
    {
        emit_application_closed (screen, NETK_APPLICATION (tmp->data));
    }

    for (tmp = closed_class_groups; tmp; tmp = tmp->next)
    {
        emit_class_group_closed (screen, NETK_CLASS_GROUP (tmp->data));
    }

    if (stack_changed)
    {
        emit_window_stacking_changed (screen);
    }

    if (active_changed)
    {
        emit_active_window_changed (screen);
    }

    /* Now free the closed windows */
    for (tmp = closed; tmp; tmp = tmp->next)
    {
        p_netk_window_destroy (NETK_WINDOW (tmp->data));
    }

    /* Free the closed apps */
    for (tmp = closed_apps; tmp; tmp = tmp->next)
    {
        p_netk_application_destroy (NETK_APPLICATION (tmp->data));
    }

    /* Free the closed class groups */
    for (tmp = closed_class_groups; tmp; tmp = tmp->next)
    {
        p_netk_class_group_destroy (NETK_CLASS_GROUP (tmp->data));
    }

    g_list_free (closed);
    g_list_free (created);
    g_list_free (closed_apps);
    g_list_free (created_apps);
    g_list_free (closed_class_groups);
    g_list_free (created_class_groups);

    --reentrancy_guard;

    /* Maybe the active window is now valid if it wasn't */
    if (screen->priv->active_window == NULL)
    {
        screen->priv->need_update_active_window = TRUE;
        queue_update (screen);
    }
}

static void
update_workspace_list (NetkScreen * screen)
{
    int n_spaces;
    int old_n_spaces;
    GList *tmp;
    GList *deleted;
    GList *created;
    static int reentrancy_guard = 0;

    g_return_if_fail (reentrancy_guard == 0);

    if (!screen->priv->need_update_workspace_list)
        return;

    screen->priv->need_update_workspace_list = FALSE;

    ++reentrancy_guard;

    n_spaces = 0;
    p_netk_get_cardinal (screen->priv->xroot,
                         p_netk_atom_get ("_NET_NUMBER_OF_DESKTOPS"),
                         &n_spaces);

    old_n_spaces = g_list_length (screen->priv->workspaces);

    deleted = NULL;
    created = NULL;

    if (old_n_spaces == n_spaces)
    {
        --reentrancy_guard;
        return;                 /* nothing changed */
    }
    else if (old_n_spaces > n_spaces)
    {
        /* Need to delete some workspaces */
        deleted = g_list_nth (screen->priv->workspaces, n_spaces);
        if (deleted->prev)
            deleted->prev->next = NULL;
        deleted->prev = NULL;

        if (deleted == screen->priv->workspaces)
            screen->priv->workspaces = NULL;
    }
    else
    {
        int i;

        g_assert (old_n_spaces < n_spaces);

        /* Need to create some workspaces */
        i = 0;
        while (i < (n_spaces - old_n_spaces))
        {
            NetkWorkspace *space;

            space = p_netk_workspace_create (old_n_spaces + i, screen);

            screen->priv->workspaces =
                g_list_append (screen->priv->workspaces, space);

            created = g_list_prepend (created, space);

            ++i;
        }

        created = g_list_reverse (created);
    }

    /* Here we allow reentrancy, going into the main
     * loop could confuse us
     */
    tmp = deleted;
    while (tmp != NULL)
    {
        NetkWorkspace *space = NETK_WORKSPACE (tmp->data);

        if (space == screen->priv->active_workspace)
        {
            screen->priv->active_workspace = NULL;
            emit_active_workspace_changed (screen);
        }

        emit_workspace_destroyed (screen, space);

        tmp = tmp->next;
    }

    tmp = created;
    while (tmp != NULL)
    {
        emit_workspace_created (screen, NETK_WORKSPACE (tmp->data));

        tmp = tmp->next;
    }
    g_list_free (created);

    tmp = deleted;
    while (tmp != NULL)
    {
        g_object_unref (G_OBJECT (tmp->data));

        tmp = tmp->next;
    }
    g_list_free (deleted);

    /* Active workspace property may now be interpretable,
     * if it was a number larger than the active count previously
     */
    if (screen->priv->active_workspace == NULL)
    {
        screen->priv->need_update_active_workspace = TRUE;
        queue_update (screen);
    }

    --reentrancy_guard;
}

static void
update_viewport_settings (NetkScreen * screen)
{
    int i, n_spaces;
    NetkWorkspace *space;
    gulong *p_coord;
    int n_coord;
    gboolean do_update;
    int space_width, space_height;
    gboolean got_viewport_prop;

    if (!screen->priv->need_update_viewport_settings)
        return;

    screen->priv->need_update_viewport_settings = FALSE;

    do_update = FALSE;

    n_spaces = netk_screen_get_workspace_count (screen);

    /* If no property, use the screen's size */
    space_width = netk_screen_get_width (screen);
    space_height = netk_screen_get_height (screen);

    if (p_netk_get_cardinal_list (screen->priv->xroot,
                                 p_netk_atom_get ("_NET_DESKTOP_GEOMETRY"),
                                 &p_coord, &n_coord) && p_coord != NULL)
    {
        if (n_coord == 2)
        {
            space_width = p_coord[0];
            space_height = p_coord[1];

            if (space_width < netk_screen_get_width (screen))
                space_width = netk_screen_get_width (screen);

            if (space_height < netk_screen_get_height (screen))
                space_height = netk_screen_get_height (screen);
        }

        g_free (p_coord);
    }

    for (i = 0; i < n_spaces; i++)
    {
        space = netk_screen_get_workspace (screen, i);
        g_assert (space != NULL);

        if (p_netk_workspace_set_geometry (space, space_width, space_height))
            do_update = TRUE;
    }

    got_viewport_prop = FALSE;

    if (p_netk_get_cardinal_list (screen->priv->xroot,
                                 p_netk_atom_get ("_NET_DESKTOP_VIEWPORT"),
                                 &p_coord, &n_coord) && p_coord != NULL)
    {
        if (n_coord == 2 * n_spaces)
        {
            int screen_width, screen_height;

            got_viewport_prop = TRUE;

            screen_width = netk_screen_get_width (screen);
            screen_height = netk_screen_get_height (screen);

            for (i = 0; i < n_spaces; i++)
            {
                int x = 2 * i;
                int y = 2 * i + 1;

                space = netk_screen_get_workspace (screen, i);
                g_assert (space != NULL);

                if (p_coord[x] < 0)
                    p_coord[x] = 0;
                else if ((int) p_coord[x] > space_width - screen_width)
                    p_coord[x] = space_width - screen_width;

                if (p_coord[y] < 0)
                    p_coord[y] = 0;
                else if ((int) p_coord[y] > space_height - screen_height)
                    p_coord[y] = space_height - screen_height;

                if (p_netk_workspace_set_viewport (space,
                                                  p_coord[x], p_coord[y]))
                    do_update = TRUE;
            }
        }

        g_free (p_coord);
    }

    if (!got_viewport_prop)
    {
        for (i = 0; i < n_spaces; i++)
        {
            space = netk_screen_get_workspace (screen, i);
            g_assert (space != NULL);

            if (p_netk_workspace_set_viewport (space, 0, 0))
                do_update = TRUE;
        }
    }

    if (do_update)
        emit_viewports_changed (screen);
}

static void
update_active_workspace (NetkScreen * screen)
{
    int number;
    NetkWorkspace *space;

    if (!screen->priv->need_update_active_workspace)
        return;

    screen->priv->need_update_active_workspace = FALSE;

    number = 0;
    if (!p_netk_get_cardinal
        (screen->priv->xroot, p_netk_atom_get ("_NET_CURRENT_DESKTOP"),
         &number))
        number = -1;

    space = netk_screen_get_workspace (screen, number);

    if (space == screen->priv->active_workspace)
        return;

    screen->priv->active_workspace = space;

    emit_active_workspace_changed (screen);
}

static void
update_active_window (NetkScreen * screen)
{
    NetkWindow *window;
    Window xwindow;

    if (!screen->priv->need_update_active_window)
        return;

    screen->priv->need_update_active_window = FALSE;

    xwindow = None;
    p_netk_get_window (screen->priv->xroot,
                       p_netk_atom_get ("_NET_ACTIVE_WINDOW"), &xwindow);

    window = netk_window_get (xwindow);

    if (window == screen->priv->active_window)
        return;

    screen->priv->active_window = window;

    emit_active_window_changed (screen);
}

static void
update_workspace_names (NetkScreen * screen)
{
    char **names;
    int i;
    GList *tmp;
    GList *copy;

    if (!screen->priv->need_update_workspace_names)
        return;

    screen->priv->need_update_workspace_names = FALSE;

    names =
        p_netk_get_utf8_list (screen->priv->xroot,
                              p_netk_atom_get ("_NET_DESKTOP_NAMES"));

    copy = g_list_copy (screen->priv->workspaces);

    i = 0;
    tmp = copy;
    while (tmp != NULL)
    {
        if (names && names[i])
        {
            p_netk_workspace_update_name (tmp->data, names[i]);
            ++i;
        }
        else
            p_netk_workspace_update_name (tmp->data, NULL);

        tmp = tmp->next;
    }

    g_strfreev (names);
    g_list_free (copy);
}

static void
update_bg_pixmap (NetkScreen * screen)
{
    Pixmap p;

    if (!screen->priv->need_update_bg_pixmap)
        return;

    screen->priv->need_update_bg_pixmap = FALSE;

    p = None;
    p_netk_get_pixmap (screen->priv->xroot, p_netk_atom_get ("_XROOTPMAP_ID"),
                       &p);
    /* may have failed, so p may still be None */

    screen->priv->bg_pixmap = p;

    emit_background_changed (screen);
}

static void
update_showing_desktop (NetkScreen * screen)
{
    int showing_desktop;

    if (!screen->priv->need_update_showing_desktop)
        return;

    screen->priv->need_update_showing_desktop = FALSE;

    showing_desktop = FALSE;
    p_netk_get_cardinal (screen->priv->xroot,
                         p_netk_atom_get ("_NET_SHOWING_DESKTOP"),
                         &showing_desktop);

    screen->priv->showing_desktop = showing_desktop != 0;

    emit_showing_desktop_changed (screen);
}

static void
do_update_now (NetkScreen * screen)
{
    if (screen->priv->update_handler)
    {
        g_source_remove (screen->priv->update_handler);
        screen->priv->update_handler = 0;
    }

    /* if number of workspaces changes, we have to
     * update the per-workspace information as well
     * in case the WM changed the per-workspace info
     * first and number of spaces second.
     */
    if (screen->priv->need_update_workspace_list)
    {
        screen->priv->need_update_viewport_settings = TRUE;
        screen->priv->need_update_workspace_names = TRUE;
    }

    /* First get our big-picture state in order */
    update_workspace_list (screen);
    update_client_list (screen);

    /* Then note any smaller-scale changes */
    update_active_workspace (screen);
    update_viewport_settings (screen);
    update_active_window (screen);
    update_workspace_names (screen);
    update_showing_desktop (screen);

    update_bg_pixmap (screen);
}

static gboolean
update_idle (gpointer data)
{
    NetkScreen *screen;

    screen = data;

    screen->priv->update_handler = 0;

    do_update_now (screen);

    return FALSE;
}

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

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

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

static void
emit_active_window_changed (NetkScreen * screen)
{
    g_signal_emit (G_OBJECT (screen), signals[ACTIVE_WINDOW_CHANGED], 0);
}

static void
emit_active_workspace_changed (NetkScreen * screen)
{
    g_signal_emit (G_OBJECT (screen), signals[ACTIVE_WORKSPACE_CHANGED], 0);
}

static void
emit_window_stacking_changed (NetkScreen * screen)
{
    g_signal_emit (G_OBJECT (screen), signals[WINDOW_STACKING_CHANGED], 0);
}

static void
emit_window_opened (NetkScreen * screen, NetkWindow * window)
{
    g_signal_emit (G_OBJECT (screen), signals[WINDOW_OPENED], 0, window);
}

static void
emit_window_closed (NetkScreen * screen, NetkWindow * window)
{
    g_signal_emit (G_OBJECT (screen), signals[WINDOW_CLOSED], 0, window);
}

static void
emit_workspace_created (NetkScreen * screen, NetkWorkspace * space)
{
    g_signal_emit (G_OBJECT (screen), signals[WORKSPACE_CREATED], 0, space);
}

static void
emit_workspace_destroyed (NetkScreen * screen, NetkWorkspace * space)
{
    g_signal_emit (G_OBJECT (screen), signals[WORKSPACE_DESTROYED], 0, space);
}

static void
emit_application_opened (NetkScreen * screen, NetkApplication * app)
{
    g_signal_emit (G_OBJECT (screen), signals[APPLICATION_OPENED], 0, app);
}

static void
emit_application_closed (NetkScreen * screen, NetkApplication * app)
{
    g_signal_emit (G_OBJECT (screen), signals[APPLICATION_CLOSED], 0, app);
}

static void
emit_class_group_opened (NetkScreen     *screen,
                         NetkClassGroup *class_group)
{
  g_signal_emit (G_OBJECT (screen), signals[CLASS_GROUP_OPENED], 0, class_group);
}

static void
emit_class_group_closed (NetkScreen     *screen,
                         NetkClassGroup *class_group)
{
  g_signal_emit (G_OBJECT (screen), signals[CLASS_GROUP_CLOSED], 0, class_group);
}

static void
emit_background_changed (NetkScreen * screen)
{
    g_signal_emit (G_OBJECT (screen), signals[BACKGROUND_CHANGED], 0);
}

static void
emit_showing_desktop_changed (NetkScreen * screen)
{
    g_signal_emit (G_OBJECT (screen), signals[SHOWING_DESKTOP_CHANGED], 0);
}

static void
emit_viewports_changed (NetkScreen * screen)
{
    g_signal_emit (G_OBJECT (screen), signals[VIEWPORTS_CHANGED], 0);
}

gboolean
netk_screen_net_wm_supports (NetkScreen * screen, const char *atom)
{
    return gdk_net_wm_supports (gdk_atom_intern (atom, FALSE));
}

gulong
netk_screen_get_background_pixmap (NetkScreen * screen)
{
    g_return_val_if_fail (NETK_IS_SCREEN (screen), None);

    return screen->priv->bg_pixmap;
}

int
netk_screen_get_width (NetkScreen * screen)
{
    g_return_val_if_fail (NETK_IS_SCREEN (screen), 0);

    return WidthOfScreen (screen->priv->xscreen);
}

int
netk_screen_get_height (NetkScreen * screen)
{
    g_return_val_if_fail (NETK_IS_SCREEN (screen), 0);

    return HeightOfScreen (screen->priv->xscreen);
}

int
netk_screen_get_number (NetkScreen * screen)
{
    return screen->priv->number;
}

Screen *
p_netk_screen_get_xscreen (NetkScreen * screen)
{
    return screen->priv->xscreen;
}

int
netk_screen_try_set_workspace_layout (NetkScreen * screen, int current_token,
                                      int rows, int columns)
{
    int retval;

    g_return_val_if_fail (NETK_IS_SCREEN (screen), NETK_NO_MANAGER_TOKEN);

    retval =
        p_netk_try_desktop_layout_manager (screen->priv->xscreen,
                                           current_token);

    if (retval != NETK_NO_MANAGER_TOKEN)
    {
        p_netk_set_desktop_layout (screen->priv->xscreen, rows, columns);
    }

    return retval;
}

void
netk_screen_release_workspace_layout (NetkScreen * screen, int current_token)
{
    p_netk_release_desktop_layout_manager (screen->priv->xscreen,
                                           current_token);

}

gboolean
netk_screen_get_showing_desktop (NetkScreen * screen)
{
    g_return_val_if_fail (NETK_IS_SCREEN (screen), FALSE);

    return screen->priv->showing_desktop;
}

void
netk_screen_toggle_showing_desktop (NetkScreen * screen, gboolean show)
{
    g_return_if_fail (NETK_IS_SCREEN (screen));

    p_netk_toggle_showing_desktop (screen->priv->xscreen, show);
}

/**
 * netk_screen_move_viewport:
 * @screen: a #NetkScreen
 * @x: X offset of viewport
 * @y: Y offset of viewport
 *
 * Ask window manager to move the viewport of the current workspace.
 */
void
netk_screen_move_viewport (NetkScreen * screen, int x, int y)
{
    g_return_if_fail (NETK_IS_SCREEN (screen));
    g_return_if_fail (x >= 0);
    g_return_if_fail (y >= 0);

    p_netk_change_viewport (NETK_SCREEN_XSCREEN (screen), x, y);
}

void
p_netk_screen_change_workspace_name (NetkScreen * screen, int number,
                                     const char *name)
{
    int n_spaces;
    char **names;
    int i;

    n_spaces = netk_screen_get_workspace_count (screen);

    names = g_new0 (char *, n_spaces + 1);

    i = 0;
    while (i < n_spaces)
    {
        if (i == number)
            names[i] = (char *) name;
        else
        {
            NetkWorkspace *workspace;
            workspace = netk_screen_get_workspace (screen, i);
            if (workspace)
                names[i] = (char *) netk_workspace_get_name (workspace);
            else
                names[i] = (char *) "??";       /* maybe this should be a g_warning() */
        }

        ++i;
    }

    p_netk_set_utf8_list (screen->priv->xroot,
                          p_netk_atom_get ("_NET_DESKTOP_NAMES"), names);

    g_free (names);
}
