/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */

/* 
 * Copyright (C) 2001 Havoc Pennington
 * Copyright (C) 2002, 2003, 2004 Red Hat, Inc.
 * Copyright (C) 2003, 2004 Rob Adams
 * Copyright (C) 2004-2006 Elijah Newren
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

/**
 * SECTION:display
 * @title: MetaDisplay
 * @short_description: Mutter X display handler
 *
 * The display is represented as a #MetaDisplay struct.
 */

#define _XOPEN_SOURCE 600 /* for gethostname() */

#include <config.h>
#include "display-private.h"
#include "util-private.h"
#include <meta/main.h>
#include "screen-private.h"
#include "window-private.h"
#include "window-x11.h"
#include "window-props.h"
#include "group-props.h"
#include "frame.h"
#include <meta/errors.h>
#include "keybindings-private.h"
#include <meta/prefs.h>
#include "resizepopup.h"
#include "xprops.h"
#include "workspace-private.h"
#include "bell.h"
#include <meta/compositor.h>
#include <meta/compositor-mutter.h>
#include <X11/Xatom.h>
#include "mutter-enum-types.h"
#include "meta-idle-monitor-private.h"
#include "meta-cursor-tracker-private.h"

#ifdef HAVE_RANDR
#include <X11/extensions/Xrandr.h>
#endif
#ifdef HAVE_SHAPE
#include <X11/extensions/shape.h>
#endif
#ifdef HAVE_XKB
#include <X11/XKBlib.h>
#endif
#include <X11/Xcursor/Xcursor.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xdamage.h>
#include <X11/extensions/Xfixes.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "meta-xwayland-private.h"

#define GRAB_OP_IS_WINDOW_SWITCH(g)                     \
        (g == META_GRAB_OP_KEYBOARD_TABBING_NORMAL  ||  \
         g == META_GRAB_OP_KEYBOARD_TABBING_DOCK    ||  \
         g == META_GRAB_OP_KEYBOARD_TABBING_GROUP   ||  \
         g == META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL ||  \
         g == META_GRAB_OP_KEYBOARD_ESCAPING_DOCK   ||  \
         g == META_GRAB_OP_KEYBOARD_ESCAPING_GROUP)

/*
 * SECTION:pings
 *
 * Sometimes we want to see whether a window is responding,
 * so we send it a "ping" message and see whether it sends us back a "pong"
 * message within a reasonable time. Here we have a system which lets us
 * nominate one function to be called if we get the pong in time and another
 * function if we don't. The system is rather more complicated than it needs
 * to be, since we only ever use it to destroy windows which are asked to
 * close themselves and don't do so within a reasonable amount of time, and
 * therefore we always use the same callbacks. It's possible that we might
 * use it for other things in future, or on the other hand we might decide
 * that we're never going to do so and simplify it a bit.
 */

/**
 * MetaPingData:
 *
 * Describes a ping on a window. When we send a ping to a window, we build
 * one of these structs, and it eventually gets passed to the timeout function
 * or to the function which handles the response from the window. If the window
 * does or doesn't respond to the ping, we use this information to deal with
 * these facts; we have a handler function for each.
 */
typedef struct
{
  MetaWindow  *window;
  guint32      timestamp;
  MetaWindowPingFunc ping_reply_func;
  MetaWindowPingFunc ping_timeout_func;
  void        *user_data;
  guint        ping_timeout_id;
} MetaPingData;

G_DEFINE_TYPE(MetaDisplay, meta_display, G_TYPE_OBJECT);

/* Signals */
enum
{
  OVERLAY_KEY,
  ACCELERATOR_ACTIVATED,
  MODIFIERS_ACCELERATOR_ACTIVATED,
  FOCUS_WINDOW,
  WINDOW_CREATED,
  WINDOW_DEMANDS_ATTENTION,
  WINDOW_MARKED_URGENT,
  GRAB_OP_BEGIN,
  GRAB_OP_END,
  LAST_SIGNAL
};

enum {
  PROP_0,

  PROP_FOCUS_WINDOW
};

static guint display_signals [LAST_SIGNAL] = { 0 };

/*
 * The display we're managing.  This is a singleton object.  (Historically,
 * this was a list of displays, but there was never any way to add more
 * than one element to it.)  The goofy name is because we don't want it
 * to shadow the parameter in its object methods.
 */
static MetaDisplay *the_display = NULL;


static const char *gnome_wm_keybindings = "Mutter";
static const char *net_wm_name = "Mutter";

#ifdef WITH_VERBOSE_MODE
static void   meta_spew_event           (MetaDisplay    *display,
                                         XEvent         *event);
#endif

static gboolean xevent_callback         (XEvent         *event,
                                         gpointer        data);
static gboolean event_callback          (const ClutterEvent *event,
                                         gpointer            data);
static Window event_get_modified_window (MetaDisplay    *display,
                                         XEvent         *event);
static Window xievent_get_modified_window (MetaDisplay    *display,
                                           XIEvent        *input_event);
static guint32 event_get_time           (MetaDisplay    *display,
                                         XEvent         *event);
static void    process_request_frame_extents (MetaDisplay    *display,
                                              XEvent         *event);
static void    process_selection_request (MetaDisplay   *display,
                                          XEvent        *event);
static void    process_selection_clear   (MetaDisplay   *display,
                                          XEvent        *event);

static void    update_window_grab_modifiers (MetaDisplay *display);

static void    prefs_changed_callback    (MetaPreference pref,
                                          void          *data);

static void    sanity_check_timestamps   (MetaDisplay *display,
                                          guint32      known_good_timestamp);
 
MetaGroup*     get_focussed_group (MetaDisplay *display);

static void
meta_display_get_property(GObject         *object,
                          guint            prop_id,
                          GValue          *value,
                          GParamSpec      *pspec)
{
  MetaDisplay *display = META_DISPLAY (object);

  switch (prop_id)
    {
    case PROP_FOCUS_WINDOW:
      g_value_set_object (value, display->focus_window);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
meta_display_set_property(GObject         *object,
                          guint            prop_id,
                          const GValue    *value,
                          GParamSpec      *pspec)
{
  switch (prop_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
meta_display_class_init (MetaDisplayClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->get_property = meta_display_get_property;
  object_class->set_property = meta_display_set_property;

  display_signals[OVERLAY_KEY] =
    g_signal_new ("overlay-key",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  display_signals[ACCELERATOR_ACTIVATED] =
    g_signal_new ("accelerator-activated",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);

  /**
   * MetaDisplay::modifiers-accelerator-activated:
   * @display: the #MetaDisplay instance
   *
   * The ::modifiers-accelerator-activated signal will be emitted when
   * a special modifiers-only keybinding is activated.
   *
   * Returns: %TRUE means that the keyboard device should remain
   *    frozen and %FALSE for the default behavior of unfreezing the
   *    keyboard.
   */
  display_signals[MODIFIERS_ACCELERATOR_ACTIVATED] =
    g_signal_new ("modifiers-accelerator-activated",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  g_signal_accumulator_first_wins, NULL, NULL,
                  G_TYPE_BOOLEAN, 0);

  display_signals[WINDOW_CREATED] =
    g_signal_new ("window-created",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1, META_TYPE_WINDOW);

  display_signals[WINDOW_DEMANDS_ATTENTION] =
    g_signal_new ("window-demands-attention",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1, META_TYPE_WINDOW);

  display_signals[WINDOW_MARKED_URGENT] =
    g_signal_new ("window-marked-urgent",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  META_TYPE_WINDOW);

  display_signals[GRAB_OP_BEGIN] =
    g_signal_new ("grab-op-begin",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 3,
                  META_TYPE_SCREEN,
                  META_TYPE_WINDOW,
                  META_TYPE_GRAB_OP);

  display_signals[GRAB_OP_END] =
    g_signal_new ("grab-op-end",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 3,
                  META_TYPE_SCREEN,
                  META_TYPE_WINDOW,
                  META_TYPE_GRAB_OP);

  g_object_class_install_property (object_class,
                                   PROP_FOCUS_WINDOW,
                                   g_param_spec_object ("focus-window",
                                                        "Focus window",
                                                        "Currently focused window",
                                                        META_TYPE_WINDOW,
                                                        G_PARAM_READABLE));
}


/**
 * ping_data_free:
 *
 * Destructor for #MetaPingData structs. Will destroy the
 * event source for the struct as well.
 */
static void
ping_data_free (MetaPingData *ping_data)
{
  /* Remove the timeout */
  if (ping_data->ping_timeout_id != 0)
    g_source_remove (ping_data->ping_timeout_id);

  g_free (ping_data);
}

/**
 * remove_pending_pings_for_window:
 * @display: The display the window appears on
 * @xwindow: The X ID of the window whose pings we should remove
 *
 * Frees every pending ping structure for the given X window on the
 * given display. This means that we also destroy the timeouts.
 */
static void
remove_pending_pings_for_window (MetaDisplay *display, Window xwindow)
{
  GSList *tmp;
  GSList *dead;

  /* could obviously be more efficient, don't care */

  /* build list to be removed */
  dead = NULL;
  for (tmp = display->pending_pings; tmp; tmp = tmp->next)
    {
      MetaPingData *ping_data = tmp->data;

      if (ping_data->window->xwindow == xwindow)
        dead = g_slist_prepend (dead, ping_data);
    }

  /* remove what we found */
  for (tmp = dead; tmp; tmp = tmp->next)
    {
      MetaPingData *ping_data = tmp->data;

      display->pending_pings = g_slist_remove (display->pending_pings, ping_data);
      ping_data_free (ping_data);
    }

  g_slist_free (dead);
}


#ifdef HAVE_STARTUP_NOTIFICATION
static void
sn_error_trap_push (SnDisplay *sn_display,
                    Display   *xdisplay)
{
  MetaDisplay *display;
  display = meta_display_for_x_display (xdisplay);
  if (display != NULL)
    meta_error_trap_push (display);
}

static void
sn_error_trap_pop (SnDisplay *sn_display,
                   Display   *xdisplay)
{
  MetaDisplay *display;
  display = meta_display_for_x_display (xdisplay);
  if (display != NULL)
    meta_error_trap_pop (display);
}
#endif

static void
enable_compositor (MetaDisplay *display)
{
  GSList *list;

  if (!META_DISPLAY_HAS_COMPOSITE (display) ||
      !META_DISPLAY_HAS_DAMAGE (display) ||
      !META_DISPLAY_HAS_RENDER (display))
    {
      meta_warning ("Missing %s extension required for compositing",
                    !META_DISPLAY_HAS_COMPOSITE (display) ? "composite" :
                    !META_DISPLAY_HAS_DAMAGE (display) ? "damage" : "render");
      return;
    }

  if (!display->compositor)
      display->compositor = meta_compositor_new (display);

  if (!display->compositor)
    return;
  
  for (list = display->screens; list != NULL; list = list->next)
    {
      MetaScreen *screen = list->data;
      
      meta_compositor_manage_screen (screen->display->compositor,
				     screen);
    }
}

static void
meta_display_init (MetaDisplay *disp)
{
  /* Some stuff could go in here that's currently in _open,
   * but it doesn't really matter. */
}

/**
 * meta_set_wm_name: (skip)
 * @wm_name: value for _NET_WM_NAME
 *
 * Set the value to use for the _NET_WM_NAME property. To take effect,
 * it is necessary to call this function before meta_init().
 */
void
meta_set_wm_name (const char *wm_name)
{
  g_return_if_fail (the_display == NULL);

  net_wm_name = wm_name;
}

/**
 * meta_set_gnome_wm_keybindings: (skip)
 * @wm_keybindings: value for _GNOME_WM_KEYBINDINGS
 *
 * Set the value to use for the _GNOME_WM_KEYBINDINGS property. To take
 * effect, it is necessary to call this function before meta_init().
 */
void
meta_set_gnome_wm_keybindings (const char *wm_keybindings)
{
  g_return_if_fail (the_display == NULL);

  gnome_wm_keybindings = wm_keybindings;
}

/**
 * meta_display_open:
 *
 * Opens a new display, sets it up, initialises all the X extensions
 * we will need, and adds it to the list of displays.
 *
 * Returns: %TRUE if the display was opened successfully, and %FALSE
 * otherwise-- that is, if the display doesn't exist or it already
 * has a window manager.
 */
gboolean
meta_display_open (void)
{
  Display *xdisplay;
  GSList *screens;
  MetaScreen *screen;
  int i;
  guint32 timestamp;

  /* A list of all atom names, so that we can intern them in one go. */
  char *atom_names[] = {
#define item(x) #x,
#include <meta/atomnames.h>
#undef item
  };
  Atom atoms[G_N_ELEMENTS(atom_names)];
  
  meta_verbose ("Opening display '%s'\n", XDisplayName (NULL));

  xdisplay = meta_ui_get_display ();
  
  if (xdisplay == NULL)
    {
      meta_warning (_("Failed to open X Window System display '%s'\n"),
		    XDisplayName (NULL));
      return FALSE;
    }

  if (meta_is_wayland_compositor ())
    meta_xwayland_complete_init ();

  if (meta_is_syncing ())
    XSynchronize (xdisplay, True);
  
  g_assert (the_display == NULL);
  the_display = g_object_new (META_TYPE_DISPLAY, NULL);

  the_display->closing = 0;
  
  /* here we use XDisplayName which is what the user
   * probably put in, vs. DisplayString(display) which is
   * canonicalized by XOpenDisplay()
   */
  the_display->name = g_strdup (XDisplayName (NULL));
  the_display->xdisplay = xdisplay;
  the_display->error_trap_synced_at_last_pop = TRUE;
  the_display->error_traps = 0;
  the_display->error_trap_handler = NULL;
  the_display->server_grab_count = 0;
  the_display->display_opening = TRUE;

  the_display->pending_pings = NULL;
  the_display->autoraise_timeout_id = 0;
  the_display->autoraise_window = NULL;
  the_display->focus_window = NULL;
  the_display->focus_serial = 0;
  the_display->server_focus_window = None;
  the_display->server_focus_serial = 0;
  the_display->grab_old_window_stacking = NULL;

  the_display->mouse_mode = TRUE; /* Only relevant for mouse or sloppy focus */
  the_display->allow_terminal_deactivation = TRUE; /* Only relevant for when a
                                                  terminal has the focus */

  /* FIXME copy the checks from GDK probably */
  the_display->static_gravity_works = g_getenv ("MUTTER_USE_STATIC_GRAVITY") != NULL;
  
  meta_bell_init (the_display);

  meta_display_init_keys (the_display);

  update_window_grab_modifiers (the_display);

  meta_prefs_add_listener (prefs_changed_callback, the_display);

  meta_verbose ("Creating %d atoms\n", (int) G_N_ELEMENTS (atom_names));
  XInternAtoms (the_display->xdisplay, atom_names, G_N_ELEMENTS (atom_names),
                False, atoms);
  {
    int i = 0;    
#define item(x) the_display->atom_##x = atoms[i++];
#include <meta/atomnames.h>
#undef item
  }

  the_display->prop_hooks = NULL;
  meta_display_init_window_prop_hooks (the_display);
  the_display->group_prop_hooks = NULL;
  meta_display_init_group_prop_hooks (the_display);
  
  /* Offscreen unmapped window used for _NET_SUPPORTING_WM_CHECK,
   * created in screen_new
   */
  the_display->leader_window = None;
  the_display->timestamp_pinging_window = None;

  the_display->monitor_cache_invalidated = TRUE;

  the_display->groups_by_leader = NULL;

  the_display->window_with_menu = NULL;
  the_display->window_menu = NULL;
  
  the_display->screens = NULL;
  the_display->active_screen = NULL;
  
#ifdef HAVE_STARTUP_NOTIFICATION
  the_display->sn_display = sn_display_new (the_display->xdisplay,
                                        sn_error_trap_push,
                                        sn_error_trap_pop);
#endif

  /* Get events */
  meta_ui_add_event_func (the_display->xdisplay,
                          xevent_callback,
                          the_display);
  the_display->clutter_event_filter = clutter_event_add_filter (NULL,
                                                                event_callback,
                                                                NULL,
                                                                the_display);

  the_display->xids = g_hash_table_new (meta_unsigned_long_hash,
                                        meta_unsigned_long_equal);
  the_display->wayland_windows = g_hash_table_new (NULL, NULL);

  i = 0;
  while (i < N_IGNORED_CROSSING_SERIALS)
    {
      the_display->ignored_crossing_serials[i] = 0;
      ++i;
    }
  the_display->ungrab_should_not_cause_focus_window = None;
  
  the_display->current_time = CurrentTime;
  the_display->sentinel_counter = 0;

  the_display->grab_resize_timeout_id = 0;
  the_display->grab_have_keyboard = FALSE;
  
#ifdef HAVE_XKB  
  the_display->last_bell_time = 0;
#endif

  the_display->grab_op = META_GRAB_OP_NONE;
  the_display->grab_window = NULL;
  the_display->grab_screen = NULL;
  the_display->grab_resize_popup = NULL;
  the_display->grab_tile_mode = META_TILE_NONE;
  the_display->grab_tile_monitor_number = -1;

  the_display->grab_edge_resistance_data = NULL;

#ifdef HAVE_XSYNC
  {
    int major, minor;

    the_display->have_xsync = FALSE;
    
    the_display->xsync_error_base = 0;
    the_display->xsync_event_base = 0;

    /* I don't think we really have to fill these in */
    major = SYNC_MAJOR_VERSION;
    minor = SYNC_MINOR_VERSION;
    
    if (!XSyncQueryExtension (the_display->xdisplay,
                              &the_display->xsync_event_base,
                              &the_display->xsync_error_base) ||
        !XSyncInitialize (the_display->xdisplay,
                          &major, &minor))
      {
        the_display->xsync_error_base = 0;
        the_display->xsync_event_base = 0;
      }
    else
      {
        the_display->have_xsync = TRUE;
        XSyncSetPriority (the_display->xdisplay, None, 10);
      }

    meta_verbose ("Attempted to init Xsync, found version %d.%d error base %d event base %d\n",
                  major, minor,
                  the_display->xsync_error_base,
                  the_display->xsync_event_base);
  }
#else  /* HAVE_XSYNC */
  meta_verbose ("Not compiled with Xsync support\n");
#endif /* !HAVE_XSYNC */


#ifdef HAVE_SHAPE
  {
    the_display->have_shape = FALSE;
    
    the_display->shape_error_base = 0;
    the_display->shape_event_base = 0;
    
    if (!XShapeQueryExtension (the_display->xdisplay,
                               &the_display->shape_event_base,
                               &the_display->shape_error_base))
      {
        the_display->shape_error_base = 0;
        the_display->shape_event_base = 0;
      }
    else
      the_display->have_shape = TRUE;
    
    meta_verbose ("Attempted to init Shape, found error base %d event base %d\n",
                  the_display->shape_error_base,
                  the_display->shape_event_base);
  }
#else  /* HAVE_SHAPE */
  meta_verbose ("Not compiled with Shape support\n");
#endif /* !HAVE_SHAPE */

  {
    the_display->have_render = FALSE;
    
    the_display->render_error_base = 0;
    the_display->render_event_base = 0;
    
    if (!XRenderQueryExtension (the_display->xdisplay,
                                &the_display->render_event_base,
                                &the_display->render_error_base))
      {
        the_display->render_error_base = 0;
        the_display->render_event_base = 0;
      }
    else
      the_display->have_render = TRUE;
    
    meta_verbose ("Attempted to init Render, found error base %d event base %d\n",
                  the_display->render_error_base,
                  the_display->render_event_base);
  }

  {
    the_display->have_composite = FALSE;

    the_display->composite_error_base = 0;
    the_display->composite_event_base = 0;

    if (!XCompositeQueryExtension (the_display->xdisplay,
                                   &the_display->composite_event_base,
                                   &the_display->composite_error_base))
      {
        the_display->composite_error_base = 0;
        the_display->composite_event_base = 0;
      } 
    else
      {
        the_display->composite_major_version = 0;
        the_display->composite_minor_version = 0;
        if (XCompositeQueryVersion (the_display->xdisplay,
                                    &the_display->composite_major_version,
                                    &the_display->composite_minor_version))
          {
            the_display->have_composite = TRUE;
          }
        else
          {
            the_display->composite_major_version = 0;
            the_display->composite_minor_version = 0;
          }
      }

    meta_verbose ("Attempted to init Composite, found error base %d event base %d "
                  "extn ver %d %d\n",
                  the_display->composite_error_base, 
                  the_display->composite_event_base,
                  the_display->composite_major_version,
                  the_display->composite_minor_version);

    the_display->have_damage = FALSE;

    the_display->damage_error_base = 0;
    the_display->damage_event_base = 0;

    if (!XDamageQueryExtension (the_display->xdisplay,
                                &the_display->damage_event_base,
                                &the_display->damage_error_base))
      {
        the_display->damage_error_base = 0;
        the_display->damage_event_base = 0;
      } 
    else
      the_display->have_damage = TRUE;

    meta_verbose ("Attempted to init Damage, found error base %d event base %d\n",
                  the_display->damage_error_base, 
                  the_display->damage_event_base);

    the_display->xfixes_error_base = 0;
    the_display->xfixes_event_base = 0;

    if (XFixesQueryExtension (the_display->xdisplay,
                              &the_display->xfixes_event_base,
                              &the_display->xfixes_error_base))
      {
        int xfixes_major, xfixes_minor;

        XFixesQueryVersion (the_display->xdisplay, &xfixes_major, &xfixes_minor);

        if (xfixes_major * 100 + xfixes_minor < 500)
          meta_fatal ("Mutter requires XFixes 5.0");
      }
    else
      {
        meta_fatal ("Mutter requires XFixes 5.0");
      }

    meta_verbose ("Attempted to init XFixes, found error base %d event base %d\n",
                  the_display->xfixes_error_base, 
                  the_display->xfixes_event_base);
  }

  {
    int major = 2, minor = 3;
    gboolean has_xi = FALSE;

    if (XQueryExtension (the_display->xdisplay,
                         "XInputExtension",
                         &the_display->xinput_opcode,
                         &the_display->xinput_error_base,
                         &the_display->xinput_event_base))
      {
        if (XIQueryVersion (the_display->xdisplay, &major, &minor) == Success)
          {
            int version = (major * 10) + minor;
            if (version >= 22)
              has_xi = TRUE;

#ifdef HAVE_XI23
            if (version >= 23)
              the_display->have_xinput_23 = TRUE;
#endif /* HAVE_XI23 */
          }
      }

    if (!has_xi)
      meta_fatal ("X server doesn't have the XInput extension, version 2.2 or newer\n");
  }

  {
    XcursorSetTheme (the_display->xdisplay, meta_prefs_get_cursor_theme ());
    XcursorSetDefaultSize (the_display->xdisplay, meta_prefs_get_cursor_size ());
  }

  /* Create the leader window here. Set its properties and
   * use the timestamp from one of the PropertyNotify events
   * that will follow.
   */
  {
    gulong data[1];
    XEvent event;

    /* We only care about the PropertyChangeMask in the next 30 or so lines of
     * code.  Note that gdk will at some point unset the PropertyChangeMask for
     * this window, so we can't rely on it still being set later.  See bug
     * 354213 for details.
     */
    the_display->leader_window =
      meta_create_offscreen_window (the_display->xdisplay,
                                    DefaultRootWindow (the_display->xdisplay),
                                    PropertyChangeMask);

    meta_prop_set_utf8_string_hint (the_display,
                                    the_display->leader_window,
                                    the_display->atom__NET_WM_NAME,
                                    net_wm_name);

    meta_prop_set_utf8_string_hint (the_display,
                                    the_display->leader_window,
                                    the_display->atom__GNOME_WM_KEYBINDINGS,
                                    gnome_wm_keybindings);
    
    meta_prop_set_utf8_string_hint (the_display,
                                    the_display->leader_window,
                                    the_display->atom__MUTTER_VERSION,
                                    VERSION);

    data[0] = the_display->leader_window;
    XChangeProperty (the_display->xdisplay,
                     the_display->leader_window,
                     the_display->atom__NET_SUPPORTING_WM_CHECK,
                     XA_WINDOW,
                     32, PropModeReplace, (guchar*) data, 1);

    XWindowEvent (the_display->xdisplay,
                  the_display->leader_window,
                  PropertyChangeMask,
                  &event);

    timestamp = event.xproperty.time;

    /* Make it painfully clear that we can't rely on PropertyNotify events on
     * this window, as per bug 354213.
     */
    XSelectInput(the_display->xdisplay,
                 the_display->leader_window,
                 NoEventMask);
  }

  /* Make a little window used only for pinging the server for timestamps; note
   * that meta_create_offscreen_window already selects for PropertyChangeMask.
   */
  the_display->timestamp_pinging_window =
    meta_create_offscreen_window (the_display->xdisplay,
                                  DefaultRootWindow (the_display->xdisplay),
                                  PropertyChangeMask);

  the_display->last_focus_time = timestamp;
  the_display->last_user_time = timestamp;
  the_display->compositor = NULL;
  
  /* Mutter used to manage all X screens of the display in a single process, but
   * now it always manages exactly one screen as specified by the DISPLAY
   * environment variable. The screens GSList is left for simplicity.
   */
  screens = NULL;

  i = meta_ui_get_screen_number ();

  screen = meta_screen_new (the_display, i, timestamp);

  if (!screen)
    {
      /* This would typically happen because all the screens already
       * have window managers.
       */
      meta_display_close (the_display, timestamp);
      return FALSE;
    }

  screens = g_slist_prepend (screens, screen);

  the_display->screens = screens;

  enable_compositor (the_display);

  meta_screen_create_guard_window (screen);

  /* We know that if mutter is running as a Wayland compositor,
   * we start out with no windows.
   */
  if (!meta_is_wayland_compositor ())
    meta_screen_manage_all_windows (screen);

  {
    Window focus;
    int ret_to;

    /* kinda bogus because GetInputFocus has no possible errors */
    meta_error_trap_push (the_display);

    /* FIXME: This is totally broken; see comment 9 of bug 88194 about this */
    focus = None;
    ret_to = RevertToPointerRoot;
    XGetInputFocus (the_display->xdisplay, &focus, &ret_to);

    /* Force a new FocusIn (does this work?) */

    /* Use the same timestamp that was passed to meta_screen_new(),
     * as it is the most recent timestamp.
     */
    if (focus == None || focus == PointerRoot)
      /* Just focus the no_focus_window on the first screen */
      meta_display_focus_the_no_focus_window (the_display,
                                              the_display->screens->data,
                                              timestamp);
    else
      {
        MetaWindow * window;
        window  = meta_display_lookup_x_window (the_display, focus);
        if (window)
          meta_display_set_input_focus_window (the_display, window, FALSE, timestamp);
        else
          /* Just focus the no_focus_window on the first screen */
          meta_display_focus_the_no_focus_window (the_display,
                                                  the_display->screens->data,
                                                  timestamp);
      }

    meta_error_trap_pop (the_display);
  }

  meta_idle_monitor_init_dbus ();

  /* Done opening new display */
  the_display->display_opening = FALSE;

  return TRUE;
}

static gint
ptrcmp (gconstpointer a, gconstpointer b)
{
  if (a < b)
    return -1;
  else if (a > b)
    return 1;
  else
    return 0;
}

/**
 * meta_display_list_windows:
 * @display: a #MetaDisplay
 * @flags: options for listing
 *
 * Lists windows for the display, the @flags parameter for
 * now determines whether override-redirect windows will be
 * included.
 *
 * Return value: (transfer container): the list of windows.
 */
GSList*
meta_display_list_windows (MetaDisplay          *display,
                           MetaListWindowsFlags  flags)
{
  GSList *winlist;
  GSList *tmp;
  GSList *prev;
  GHashTableIter iter;
  gpointer key, value;

  winlist = NULL;

  g_hash_table_iter_init (&iter, display->xids);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      MetaWindow *window = value;

      if (!META_IS_WINDOW (window))
        continue;

      if (!window->override_redirect ||
          (flags & META_LIST_INCLUDE_OVERRIDE_REDIRECT) != 0)
        winlist = g_slist_prepend (winlist, window);
    }

  g_hash_table_iter_init (&iter, display->wayland_windows);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      MetaWindow *window = value;

      if (!META_IS_WINDOW (window))
        continue;

      if (!window->override_redirect ||
          (flags & META_LIST_INCLUDE_OVERRIDE_REDIRECT) != 0)
        winlist = g_slist_prepend (winlist, window);
    }

  /* Uniquify the list, since both frame windows and plain
   * windows are in the hash
   */
  winlist = g_slist_sort (winlist, ptrcmp);

  prev = NULL;
  tmp = winlist;
  while (tmp != NULL)
    {
      GSList *next;

      next = tmp->next;
      
      if (next &&
          next->data == tmp->data)
        {
          /* Delete tmp from list */

          if (prev)
            prev->next = next;

          if (tmp == winlist)
            winlist = next;
          
          g_slist_free_1 (tmp);

          /* leave prev unchanged */
        }
      else
        {
          prev = tmp;
        }
      
      tmp = next;
    }

  return winlist;
}

void
meta_display_close (MetaDisplay *display,
                    guint32      timestamp)
{
  GSList *tmp;

  g_assert (display != NULL);

  if (display->closing != 0)
    {
      /* The display's already been closed. */
      return;
    }

  if (display->error_traps > 0)
    meta_bug ("Display closed with error traps pending\n");

  display->closing += 1;

  meta_prefs_remove_listener (prefs_changed_callback, display);
  
  meta_display_remove_autoraise_callback (display);

  if (display->focus_timeout_id)
    g_source_remove (display->focus_timeout_id);
  display->focus_timeout_id = 0;

  if (display->grab_old_window_stacking)
    g_list_free (display->grab_old_window_stacking);
  
  /* Stop caring about events */
  meta_ui_remove_event_func (display->xdisplay,
                             xevent_callback,
                             display);
  clutter_event_remove_filter (display->clutter_event_filter);
  display->clutter_event_filter = 0;
  
  /* Free all screens */
  tmp = display->screens;
  while (tmp != NULL)
    {
      MetaScreen *screen = tmp->data;
      meta_screen_free (screen, timestamp);
      tmp = tmp->next;
    }

  g_slist_free (display->screens);
  display->screens = NULL;

#ifdef HAVE_STARTUP_NOTIFICATION
  if (display->sn_display)
    {
      sn_display_unref (display->sn_display);
      display->sn_display = NULL;
    }
#endif
  
  /* Must be after all calls to meta_window_unmanage() since they
   * unregister windows
   */
  g_hash_table_destroy (display->xids);

  if (display->leader_window != None)
    XDestroyWindow (display->xdisplay, display->leader_window);

  XFlush (display->xdisplay);

  meta_display_free_window_prop_hooks (display);
  meta_display_free_group_prop_hooks (display);
  
  g_free (display->name);

  meta_display_shutdown_keys (display);

  if (display->compositor)
    meta_compositor_destroy (display->compositor);
  
  g_object_unref (display);
  the_display = NULL;

  meta_quit (META_EXIT_SUCCESS);
}

/**
 * meta_display_screen_for_root:
 * @display: a #MetaDisplay
 * @xroot: a X window
 *
 * Return the #MetaScreen corresponding to a specified X root window ID.
 *
 * Return Value: (transfer none): the screen for the specified root window ID, or %NULL
 */
MetaScreen*
meta_display_screen_for_root (MetaDisplay *display,
                              Window       xroot)
{
  GSList *tmp;

  tmp = display->screens;
  while (tmp != NULL)
    {
      MetaScreen *screen = tmp->data;

      if (xroot == screen->xroot)
        return screen;

      tmp = tmp->next;
    }

  return NULL;
}

MetaScreen*
meta_display_screen_for_xwindow (MetaDisplay *display,
                                 Window       xwindow)
{
  XWindowAttributes attr;
  int result;
  
  meta_error_trap_push (display);
  attr.screen = NULL;
  result = XGetWindowAttributes (display->xdisplay, xwindow, &attr);
  meta_error_trap_pop (display);

  /* Note, XGetWindowAttributes is on all kinds of crack
   * and returns 1 on success 0 on failure, rather than Success
   * on success.
   */
  if (result == 0 || attr.screen == NULL)
    return NULL;
  
  return meta_display_screen_for_x_screen (display, attr.screen);
}

MetaScreen*
meta_display_screen_for_x_screen (MetaDisplay *display,
                                  Screen      *xscreen)
{
  GSList *tmp;

  tmp = display->screens;
  while (tmp != NULL)
    {
      MetaScreen *screen = tmp->data;

      if (xscreen == screen->xscreen)
        return screen;

      tmp = tmp->next;
    }

  return NULL;
}

/* Grab/ungrab routines taken from fvwm.
 * Calling this function will cause X to ignore all other clients until
 * you ungrab. This may not be quite as bad as it sounds, yet there is
 * agreement that avoiding server grabs except when they are clearly needed
 * is a good thing.
 *
 * If you do use such grabs, please clearly explain the necessity for their
 * usage in a comment. Try to keep their scope extremely limited. In
 * particular, try to avoid emitting any signals or notifications while
 * a grab is active (if the signal receiver tries to block on an X request
 * from another client at this point, you will have a deadlock).
 */
void
meta_display_grab (MetaDisplay *display)
{
  if (display->server_grab_count == 0)
    {
      XGrabServer (display->xdisplay);
    }
  display->server_grab_count += 1;
  meta_verbose ("Grabbing display, grab count now %d\n",
                display->server_grab_count);
}

void
meta_display_ungrab (MetaDisplay *display)
{
  if (display->server_grab_count == 0)
    meta_bug ("Ungrabbed non-grabbed server\n");
  
  display->server_grab_count -= 1;
  if (display->server_grab_count == 0)
    {
      /* FIXME we want to purge all pending "queued" stuff
       * at this point, such as window hide/show
       */
      XUngrabServer (display->xdisplay);
      XFlush (display->xdisplay);
    }

  meta_verbose ("Ungrabbing display, grab count now %d\n",
                display->server_grab_count);
}

/**
 * meta_display_for_x_display:
 * @xdisplay: An X display
 *
 * Returns the singleton MetaDisplay if @xdisplay matches the X display it's
 * managing; otherwise gives a warning and returns %NULL.  When we were claiming
 * to be able to manage multiple displays, this was supposed to find the
 * display out of the list which matched that display.  Now it's merely an
 * extra sanity check.
 *
 * Returns: The singleton X display, or %NULL if @xdisplay isn't the one
 *          we're managing.
 */
MetaDisplay*
meta_display_for_x_display (Display *xdisplay)
{
  if (the_display->xdisplay == xdisplay)
    return the_display;

  meta_warning ("Could not find display for X display %p, probably going to crash\n",
                xdisplay);
  
  return NULL;
}

/**
 * meta_get_display:
 *
 * Accessor for the singleton MetaDisplay.
 *
 * Returns: The only #MetaDisplay there is.  This can be %NULL, but only
 *          during startup.
 */
MetaDisplay*
meta_get_display (void)
{
  return the_display;
}

#ifdef WITH_VERBOSE_MODE
static gboolean dump_events = TRUE;
#endif

static gboolean
grab_op_is_mouse_only (MetaGrabOp op)
{
  switch (op)
    {
    case META_GRAB_OP_MOVING:
    case META_GRAB_OP_RESIZING_SE:
    case META_GRAB_OP_RESIZING_S:      
    case META_GRAB_OP_RESIZING_SW:      
    case META_GRAB_OP_RESIZING_N:
    case META_GRAB_OP_RESIZING_NE:
    case META_GRAB_OP_RESIZING_NW:
    case META_GRAB_OP_RESIZING_W:
    case META_GRAB_OP_RESIZING_E:
      return TRUE;

    default:
      return FALSE;
    }
}

gboolean
meta_grab_op_is_mouse (MetaGrabOp op)
{
  switch (op)
    {
    case META_GRAB_OP_MOVING:
    case META_GRAB_OP_RESIZING_SE:
    case META_GRAB_OP_RESIZING_S:      
    case META_GRAB_OP_RESIZING_SW:      
    case META_GRAB_OP_RESIZING_N:
    case META_GRAB_OP_RESIZING_NE:
    case META_GRAB_OP_RESIZING_NW:
    case META_GRAB_OP_RESIZING_W:
    case META_GRAB_OP_RESIZING_E:
    case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
    case META_GRAB_OP_KEYBOARD_RESIZING_S:
    case META_GRAB_OP_KEYBOARD_RESIZING_N:
    case META_GRAB_OP_KEYBOARD_RESIZING_W:
    case META_GRAB_OP_KEYBOARD_RESIZING_E:
    case META_GRAB_OP_KEYBOARD_RESIZING_SE:
    case META_GRAB_OP_KEYBOARD_RESIZING_NE:
    case META_GRAB_OP_KEYBOARD_RESIZING_SW:
    case META_GRAB_OP_KEYBOARD_RESIZING_NW:
    case META_GRAB_OP_KEYBOARD_MOVING:
    case META_GRAB_OP_COMPOSITOR:
      return TRUE;

    default:
      return FALSE;
    }
}

static gboolean
grab_op_is_keyboard (MetaGrabOp op)
{
  switch (op)
    {
    case META_GRAB_OP_KEYBOARD_MOVING:
    case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
    case META_GRAB_OP_KEYBOARD_RESIZING_S:
    case META_GRAB_OP_KEYBOARD_RESIZING_N:
    case META_GRAB_OP_KEYBOARD_RESIZING_W:
    case META_GRAB_OP_KEYBOARD_RESIZING_E:
    case META_GRAB_OP_KEYBOARD_RESIZING_SE:
    case META_GRAB_OP_KEYBOARD_RESIZING_NE:
    case META_GRAB_OP_KEYBOARD_RESIZING_SW:
    case META_GRAB_OP_KEYBOARD_RESIZING_NW:
    case META_GRAB_OP_KEYBOARD_TABBING_NORMAL:
    case META_GRAB_OP_KEYBOARD_TABBING_DOCK:
    case META_GRAB_OP_KEYBOARD_TABBING_GROUP:
    case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL:
    case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK:
    case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP:
    case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING:
    case META_GRAB_OP_COMPOSITOR:
      return TRUE;

    default:
      return FALSE;
    }
}

gboolean
meta_grab_op_is_resizing (MetaGrabOp op)
{
  switch (op)
    {
    case META_GRAB_OP_RESIZING_SE:
    case META_GRAB_OP_RESIZING_S:      
    case META_GRAB_OP_RESIZING_SW:      
    case META_GRAB_OP_RESIZING_N:
    case META_GRAB_OP_RESIZING_NE:
    case META_GRAB_OP_RESIZING_NW:
    case META_GRAB_OP_RESIZING_W:
    case META_GRAB_OP_RESIZING_E:
    case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
    case META_GRAB_OP_KEYBOARD_RESIZING_S:
    case META_GRAB_OP_KEYBOARD_RESIZING_N:
    case META_GRAB_OP_KEYBOARD_RESIZING_W:
    case META_GRAB_OP_KEYBOARD_RESIZING_E:
    case META_GRAB_OP_KEYBOARD_RESIZING_SE:
    case META_GRAB_OP_KEYBOARD_RESIZING_NE:
    case META_GRAB_OP_KEYBOARD_RESIZING_SW:
    case META_GRAB_OP_KEYBOARD_RESIZING_NW:
      return TRUE;

    default:
      return FALSE;
    }
}

gboolean
meta_grab_op_is_moving (MetaGrabOp op)
{
  switch (op)
    {
    case META_GRAB_OP_MOVING:
    case META_GRAB_OP_KEYBOARD_MOVING:
      return TRUE;
      
    default:
      return FALSE;
    }
}

/**
 * meta_display_xserver_time_is_before:
 * @display: a #MetaDisplay
 * @time1: An event timestamp
 * @time2: An event timestamp
 *
 * Xserver time can wraparound, thus comparing two timestamps needs to take
 * this into account. If no wraparound has occurred, this is equivalent to
 *   time1 < time2
 * Otherwise, we need to account for the fact that wraparound can occur
 * and the fact that a timestamp of 0 must be special-cased since it
 * means "older than anything else".
 *
 * Note that this is NOT an equivalent for time1 <= time2; if that's what
 * you need then you'll need to swap the order of the arguments and negate
 * the result.
 */
gboolean
meta_display_xserver_time_is_before (MetaDisplay   *display,
                                     guint32        time1,
                                     guint32        time2)
{
  return XSERVER_TIME_IS_BEFORE(time1, time2);
}

/**
 * meta_display_get_last_user_time:
 * @display: a #MetaDisplay
 *
 * Returns: Timestamp of the last user interaction event with a window
 */
guint32
meta_display_get_last_user_time (MetaDisplay *display)
{
  return display->last_user_time;
}

/* Get time of current event, or CurrentTime if none. */
guint32
meta_display_get_current_time (MetaDisplay *display)
{
  return display->current_time;
}

static Bool
find_timestamp_predicate (Display  *xdisplay,
                          XEvent   *ev,
                          XPointer  arg)
{
  MetaDisplay *display = (MetaDisplay *) arg;

  return (ev->type == PropertyNotify &&
          ev->xproperty.atom == display->atom__MUTTER_TIMESTAMP_PING);
}

/* Get a timestamp, even if it means a roundtrip */
guint32
meta_display_get_current_time_roundtrip (MetaDisplay *display)
{
  guint32 timestamp;
  
  timestamp = meta_display_get_current_time (display);
  if (timestamp == CurrentTime)
    {
      XEvent property_event;

      XChangeProperty (display->xdisplay, display->timestamp_pinging_window,
                       display->atom__MUTTER_TIMESTAMP_PING,
                       XA_STRING, 8, PropModeAppend, NULL, 0);
      XIfEvent (display->xdisplay,
                &property_event,
                find_timestamp_predicate,
                (XPointer) display);
      timestamp = property_event.xproperty.time;
    }

  sanity_check_timestamps (display, timestamp);

  return timestamp;
}

/**
 * meta_display_get_ignored_modifier_mask:
 * @display: a #MetaDisplay
 *
 * Returns: a mask of modifiers that should be ignored
 *          when matching keybindings to events
 */
unsigned int
meta_display_get_ignored_modifier_mask (MetaDisplay *display)
{
  return display->ignored_modifier_mask;
}

/**
 * meta_display_add_ignored_crossing_serial:
 * @display: a #MetaDisplay
 * @serial: the serial to ignore
 *
 * Save the specified serial and ignore crossing events with that
 * serial for the purpose of focus-follows-mouse. This can be used
 * for certain changes to the window hierarchy that we don't want
 * to change the focus window, even if they cause the pointer to
 * end up in a new window.
 */
void
meta_display_add_ignored_crossing_serial (MetaDisplay  *display,
                                          unsigned long serial)
{
  int i;

  /* don't add the same serial more than once */
  if (display->ignored_crossing_serials[N_IGNORED_CROSSING_SERIALS-1] == serial)
    return;
  
  /* shift serials to the left */
  i = 0;
  while (i < (N_IGNORED_CROSSING_SERIALS - 1))
    {
      display->ignored_crossing_serials[i] = display->ignored_crossing_serials[i+1];
      ++i;
    }
  /* put new one on the end */
  display->ignored_crossing_serials[i] = serial;
}

static gboolean
crossing_serial_is_ignored (MetaDisplay  *display,
                            unsigned long serial)
{
  int i;

  i = 0;
  while (i < N_IGNORED_CROSSING_SERIALS)
    {
      if (display->ignored_crossing_serials[i] == serial)
        return TRUE;
      ++i;
    }
  return FALSE;
}

static gboolean 
window_raise_with_delay_callback (void *data)
{
  MetaWindow *window = data;

  window->display->autoraise_timeout_id = 0;
  window->display->autoraise_window = NULL;

  /* If we aren't already on top, check whether the pointer is inside
   * the window and raise the window if so.
   */      
  if (meta_stack_get_top (window->screen->stack) != window) 
    {
      int x, y, root_x, root_y;
      Window root, child;
      MetaRectangle frame_rect;
      unsigned int mask;
      gboolean same_screen;
      gboolean point_in_window;

      meta_error_trap_push (window->display);
      same_screen = XQueryPointer (window->display->xdisplay,
				   window->xwindow,
				   &root, &child,
				   &root_x, &root_y, &x, &y, &mask);
      meta_error_trap_pop (window->display);

      meta_window_get_frame_rect (window, &frame_rect);
      point_in_window = POINT_IN_RECT (root_x, root_y, frame_rect);
      if (same_screen && point_in_window)
	meta_window_raise (window);
      else
	meta_topic (META_DEBUG_FOCUS, 
		    "Pointer not inside window, not raising %s\n", 
		    window->desc);
    }

  return FALSE;
}

void
meta_display_queue_autoraise_callback (MetaDisplay *display,
                                       MetaWindow  *window)
{
  meta_topic (META_DEBUG_FOCUS, 
              "Queuing an autoraise timeout for %s with delay %d\n", 
              window->desc, 
              meta_prefs_get_auto_raise_delay ());
  
  if (display->autoraise_timeout_id != 0)
    g_source_remove (display->autoraise_timeout_id);

  display->autoraise_timeout_id = 
    g_timeout_add_full (G_PRIORITY_DEFAULT,
                        meta_prefs_get_auto_raise_delay (),
                        window_raise_with_delay_callback,
                        window, NULL);
  display->autoraise_window = window;
}

#if 0
static void
handle_net_restack_window (MetaDisplay* display,
                           XEvent *event)
{
  MetaWindow *window;

  window = meta_display_lookup_x_window (display,
                                         event->xclient.window);

  if (window)
    {
      /* FIXME: The EWMH includes a sibling for the restack request, but we
       * (stupidly) don't currently support these types of raises.
       *
       * Also, unconditionally following these is REALLY stupid--we should
       * combine this code with the stuff in
       * meta_window_x11_configure_request() which is smart about whether to
       * follow the request or do something else (though not smart enough
       * and is also too stupid to handle the sibling stuff).
       */
      switch (event->xclient.data.l[2])
        {
        case Above:
          meta_window_raise (window);
          break;
        case Below:
          meta_window_lower (window);
          break;
        case TopIf:
        case BottomIf:
        case Opposite:
          break;          
        }
    }
}
#endif

static MetaWindow *
get_window_for_event (MetaDisplay        *display,
                      const ClutterEvent *event)
{
  ClutterActor *source;

  if (display->grab_op != META_GRAB_OP_NONE)
    return display->grab_window;

  source = clutter_event_get_source (event);
  if (META_IS_SURFACE_ACTOR (source))
    {
      MetaWaylandSurface *surface = meta_surface_actor_get_surface (META_SURFACE_ACTOR (source));
      g_assert (surface != NULL);
      return surface->window;
    }

  return NULL;
}

static XIEvent *
get_input_event (MetaDisplay *display,
                 XEvent      *event)
{
  if (event->type == GenericEvent &&
      event->xcookie.extension == display->xinput_opcode)
    {
      XIEvent *input_event;

      /* NB: GDK event filters already have generic events
       * allocated, so no need to do XGetEventData() on our own
       */
      input_event = (XIEvent *) event->xcookie.data;

      switch (input_event->evtype)
        {
        case XI_Motion:
        case XI_ButtonPress:
        case XI_ButtonRelease:
          if (((XIDeviceEvent *) input_event)->deviceid == META_VIRTUAL_CORE_POINTER_ID)
            return input_event;
          break;
        case XI_KeyPress:
        case XI_KeyRelease:
          if (((XIDeviceEvent *) input_event)->deviceid == META_VIRTUAL_CORE_KEYBOARD_ID)
            return input_event;
          break;
        case XI_FocusIn:
        case XI_FocusOut:
          if (((XIEnterEvent *) input_event)->deviceid == META_VIRTUAL_CORE_KEYBOARD_ID)
            return input_event;
          break;
        case XI_Enter:
        case XI_Leave:
          if (((XIEnterEvent *) input_event)->deviceid == META_VIRTUAL_CORE_POINTER_ID)
            return input_event;
          break;
#ifdef HAVE_XI23
        case XI_BarrierHit:
        case XI_BarrierLeave:
          if (((XIBarrierEvent *) input_event)->deviceid == META_VIRTUAL_CORE_POINTER_ID)
            return input_event;
          break;
#endif /* HAVE_XI23 */
        default:
          break;
        }
    }

  return NULL;
}

static void
update_focus_window (MetaDisplay   *display,
                     MetaFocusType  type,
                     MetaWindow    *window,
                     Window         xwindow,
                     gulong         serial,
                     gboolean       focused_by_us)
{
  MetaWaylandCompositor *compositor;

  display->focus_serial = serial;
  display->focused_by_us = focused_by_us;

  if (display->focus_xwindow == xwindow &&
      display->focus_type == type &&
      display->focus_window == window)
    return;

  if (display->focus_window)
    {
      MetaWindow *previous;

      meta_topic (META_DEBUG_FOCUS,
                  "%s is now the previous focus window due to being focused out or unmapped\n",
                  display->focus_window->desc);

      /* Make sure that signals handlers invoked by
       * meta_window_set_focused_internal() don't see
       * display->focus_window->has_focus == FALSE
       */
      previous = display->focus_window;
      display->focus_window = NULL;
      display->focus_xwindow = None;

      meta_window_set_focused_internal (previous, FALSE);
    }

  display->focus_type = type;
  display->focus_window = window;
  display->focus_xwindow = xwindow;

  if (display->focus_window)
    {
      ClutterActor *window_actor;

      meta_topic (META_DEBUG_FOCUS, "* Focus --> %s with serial %lu\n",
                  display->focus_window->desc, serial);
      meta_window_set_focused_internal (display->focus_window, TRUE);

      /* XXX -- this is sort of a layer violation, but because we
       * rely on the compositor for event delivery anyway, I don't
       * think it's too bad... */

      window_actor = CLUTTER_ACTOR (display->focus_window->compositor_private);
      if (window_actor)
        clutter_actor_grab_key_focus (window_actor);
    }
  else
    meta_topic (META_DEBUG_FOCUS, "* Focus --> NULL with serial %lu\n", serial);

  if (meta_is_wayland_compositor ())
    {
      compositor = meta_wayland_compositor_get_default ();

      if (display->focus_type == META_FOCUS_NO_FOCUS_WINDOW ||
          display->focus_type == META_FOCUS_STAGE)
        meta_wayland_compositor_set_input_focus (compositor, NULL);
      else if (window && window->surface)
        meta_wayland_compositor_set_input_focus (compositor, window);
      else
        meta_topic (META_DEBUG_FOCUS, "Focus change has no effect, because there is no matching wayland surface");
     }

  g_object_notify (G_OBJECT (display), "focus-window");
  meta_display_update_active_window_hint (display);
}

static gboolean
timestamp_too_old (MetaDisplay *display,
                   guint32     *timestamp)
{
  /* FIXME: If Soeren's suggestion in bug 151984 is implemented, it will allow
   * us to sanity check the timestamp here and ensure it doesn't correspond to
   * a future time (though we would want to rename to
   * timestamp_too_old_or_in_future).
   */

  if (*timestamp == CurrentTime)
    {
      *timestamp = meta_display_get_current_time_roundtrip (display);
      return FALSE;
    }
  else if (XSERVER_TIME_IS_BEFORE (*timestamp, display->last_focus_time))
    {
      if (XSERVER_TIME_IS_BEFORE (*timestamp, display->last_user_time))
        return TRUE;
      else
        {
          *timestamp = display->last_focus_time;
          return FALSE;
        }
    }

  return FALSE;
}

static void
request_xserver_input_focus_change (MetaDisplay   *display,
                                    MetaScreen    *screen,
                                    MetaFocusType  type,
                                    MetaWindow    *meta_window,
                                    Window         xwindow,
                                    guint32        timestamp)
{
  gulong serial;

  if (timestamp_too_old (display, &timestamp))
    return;

  meta_error_trap_push (display);

  /* In order for mutter to know that the focus request succeeded, we track
   * the serial of the "focus request" we made, but if we take the serial
   * of the XSetInputFocus request, then there's no way to determine the
   * difference between focus events as a result of the SetInputFocus and
   * focus events that other clients send around the same time. Ensure that
   * we know which is which by making two requests that the server will
   * process at the same time.
   */
  meta_display_grab (display);

  serial = XNextRequest (display->xdisplay);

  XSetInputFocus (display->xdisplay,
                  xwindow,
                  RevertToPointerRoot,
                  timestamp);

  XChangeProperty (display->xdisplay, display->timestamp_pinging_window,
                   display->atom__MUTTER_FOCUS_SET,
                   XA_STRING, 8, PropModeAppend, NULL, 0);

  meta_display_ungrab (display);

  update_focus_window (display,
                       type,
                       meta_window,
                       xwindow,
                       serial,
                       TRUE);

  meta_error_trap_pop (display);

  display->last_focus_time = timestamp;
  display->active_screen = screen;

  if (meta_window == NULL || meta_window != display->autoraise_window)
    meta_display_remove_autoraise_callback (display);
}

static void
handle_window_focus_event (MetaDisplay  *display,
                           MetaWindow   *window,
                           XIEnterEvent *event,
                           unsigned long serial)
{
  MetaWindow *focus_window;
  MetaFocusType type;
#ifdef WITH_VERBOSE_MODE
  const char *window_type;

  type = META_FOCUS_NONE;

  /* Note the event can be on either the window or the frame,
   * we focus the frame for shaded windows
   */
  if (window)
    {
      if (event->event == window->xwindow)
        window_type = "client window";
      else if (window->frame && event->event == window->frame->xwindow)
        window_type = "frame window";
      else
        window_type = "unknown client window";

      if (window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND)
        type = META_FOCUS_WAYLAND_CLIENT;
      else
        type = META_FOCUS_X_CLIENT;
    }
  else if (meta_display_xwindow_is_a_no_focus_window (display, event->event))
    {
      window_type = "no_focus_window";
      type = META_FOCUS_NO_FOCUS_WINDOW;
    }
  else if (meta_display_screen_for_root (display, event->event))
    window_type = "root window";
  else
    window_type = "unknown window";

  /* Don't change type if we don't know the new window */
  if (type == META_FOCUS_NONE)
    type = display->focus_type;

  meta_topic (META_DEBUG_FOCUS,
              "Focus %s event received on %s 0x%lx (%s) "
              "mode %s detail %s serial %lu\n",
              event->evtype == XI_FocusIn ? "in" :
              event->evtype == XI_FocusOut ? "out" :
              "???",
              window ? window->desc : "",
              event->event, window_type,
              meta_event_mode_to_string (event->mode),
              meta_event_detail_to_string (event->mode),
              event->serial);
#endif

  /* FIXME our pointer tracking is broken; see how
   * gtk+/gdk/x11/gdkevents-x11.c or XFree86/xc/programs/xterm/misc.c
   * for how to handle it the correct way.  In brief you need to track
   * pointer focus and regular focus, and handle EnterNotify in
   * PointerRoot mode with no window manager.  However as noted above,
   * accurate focus tracking will break things because we want to keep
   * windows "focused" when using keybindings on them, and also we
   * sometimes "focus" a window by focusing its frame or
   * no_focus_window; so this all needs rethinking massively.
   *
   * My suggestion is to change it so that we clearly separate
   * actual keyboard focus tracking using the xterm algorithm,
   * and mutter's "pretend" focus window, and go through all
   * the code and decide which one should be used in each place;
   * a hard bit is deciding on a policy for that.
   *
   * http://bugzilla.gnome.org/show_bug.cgi?id=90382
   */

  /* We ignore grabs, though this is questionable. It may be better to
   * increase the intelligence of the focus window tracking.
   *
   * The problem is that keybindings for windows are done with
   * XGrabKey, which means focus_window disappears and the front of
   * the MRU list gets confused from what the user expects once a
   * keybinding is used.
   */

  if (event->mode == XINotifyGrab ||
      event->mode == XINotifyUngrab ||
      /* From WindowMaker, ignore all funky pointer root events */
      event->detail > XINotifyNonlinearVirtual)
    {
      meta_topic (META_DEBUG_FOCUS,
                  "Ignoring focus event generated by a grab or other weirdness\n");
      return;
    }

  if (event->evtype == XI_FocusIn)
    {
      display->server_focus_window = event->event;
      display->server_focus_serial = serial;
      focus_window = window;
    }
  else if (event->evtype == XI_FocusOut)
    {
      if (event->detail == XINotifyInferior)
        {
          /* This event means the client moved focus to a subwindow */
          meta_topic (META_DEBUG_FOCUS,
                      "Ignoring focus out with NotifyInferior\n");
          return;
        }

      display->server_focus_window = None;
      display->server_focus_serial = serial;
      focus_window = NULL;
    }
  else
    g_return_if_reached ();

  /* If display->focused_by_us, then the focus_serial will be used only
   * for a focus change we made and have already accounted for.
   * (See request_xserver_input_focus_change().) Otherwise, we can get
   * multiple focus events with the same serial.
   */
  if (display->server_focus_serial > display->focus_serial ||
      (!display->focused_by_us &&
       display->server_focus_serial == display->focus_serial))
    {
      update_focus_window (display,
                           type,
                           focus_window,
                           focus_window ? focus_window->xwindow : None,
                           display->server_focus_serial,
                           FALSE);
    }
}

static gboolean
window_has_xwindow (MetaWindow *window,
                    Window      xwindow)
{
  if (window->xwindow == xwindow)
    return TRUE;

  if (window->frame && window->frame->xwindow == xwindow)
    return TRUE;

  return FALSE;
}

static gboolean
meta_display_handle_event (MetaDisplay        *display,
                           const ClutterEvent *event)
{
  MetaWindow *window;
  gboolean bypass_clutter = FALSE, bypass_wayland = FALSE;
  MetaWaylandCompositor *compositor = NULL;

  /* XXX -- we need to fill this in properly at some point... */
  gboolean frame_was_receiver = FALSE;

  if (meta_is_wayland_compositor ())
    {
      compositor = meta_wayland_compositor_get_default ();
      meta_wayland_compositor_update (compositor, event);
    }

  window = get_window_for_event (display, event);

  display->current_time = event->any.time;

  if (window && !window->override_redirect &&
      (event->type == CLUTTER_KEY_PRESS || event->type == CLUTTER_BUTTON_PRESS))
    {
      if (CurrentTime == display->current_time)
        {
          /* We can't use missing (i.e. invalid) timestamps to set user time,
           * nor do we want to use them to sanity check other timestamps.
           * See bug 313490 for more details.
           */
          meta_warning ("Event has no timestamp! You may be using a broken "
                        "program such as xse.  Please ask the authors of that "
                        "program to fix it.\n");
        }
      else
        {
          meta_window_set_user_time (window, display->current_time);
          sanity_check_timestamps (display, display->current_time);
        }
    }

  switch (event->type)
    {
    case CLUTTER_BUTTON_PRESS:
      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
        break;

      display->overlay_key_only_pressed = FALSE;

      if ((window &&
           meta_grab_op_is_mouse (display->grab_op) &&
           (event->button.modifier_state & display->window_grab_modifiers) &&
           display->grab_button != (int) event->button.button &&
           display->grab_window == window) ||
          grab_op_is_keyboard (display->grab_op))
        {
          meta_topic (META_DEBUG_WINDOW_OPS,
                      "Ending grab op %u on window %s due to button press\n",
                      display->grab_op,
                      (display->grab_window ?
                       display->grab_window->desc :
                       "none"));
          if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op))
            {
              meta_topic (META_DEBUG_WINDOW_OPS,
                          "Syncing to old stack positions.\n");

              /* XXX: I'm not sure if this is the right thing to do.
                 The pre-Wayland code was only calling
                 meta_stack_set_positions if the modified window was a
                 root window */
              if (event->any.source == CLUTTER_ACTOR (event->any.stage) && window && window->screen)
                meta_stack_set_positions (window->screen->stack,
                                          display->grab_old_window_stacking);
            }
          meta_display_end_grab_op (display, event->any.time);
          bypass_clutter = TRUE;
          bypass_wayland = TRUE;
        }
      else if (window && display->grab_op == META_GRAB_OP_NONE)
        {
          gboolean begin_move = FALSE;
          ClutterModifierType grab_mask;
          gboolean unmodified;

          grab_mask = display->window_grab_modifiers;
          if (g_getenv ("MUTTER_DEBUG_BUTTON_GRABS"))
            grab_mask |= CLUTTER_CONTROL_MASK;

          /* Two possible sources of an unmodified event; one is a
           * client that's letting button presses pass through to the
           * frame, the other is our focus_window_grab on unmodified
           * button 1.  So for all such events we focus the window.
           */
          unmodified = (event->button.modifier_state & grab_mask) == 0;

          if (unmodified ||
              event->button.button == 1)
            {
              /* don't focus if frame received, will be lowered in
               * frames.c or special-cased if the click was on a
               * minimize/close button.
               */
              if (!frame_was_receiver)
                {
                  if (meta_prefs_get_raise_on_click ())
                    meta_window_raise (window);
                  else
                    meta_topic (META_DEBUG_FOCUS,
                                "Not raising window on click due to don't-raise-on-click option\n");

                  /* Don't focus panels--they must explicitly request focus.
                   * See bug 160470
                   */
                  if (window->type != META_WINDOW_DOCK)
                    {
                      meta_topic (META_DEBUG_FOCUS,
                                  "Focusing %s due to unmodified button %u press (display.c)\n",
                                  window->desc, event->button.button);
                      meta_window_focus (window, event->any.time);
                    }
                  else
                    /* However, do allow terminals to lose focus due to new
                     * window mappings after the user clicks on a panel.
                     */
                    display->allow_terminal_deactivation = TRUE;
                }

              /* you can move on alt-click but not on
               * the click-to-focus
               */
              if (!unmodified)
                begin_move = TRUE;
            }
          else if (!unmodified && ((int) event->button.button == meta_prefs_get_mouse_button_resize ()))
            {
              if (window->has_resize_func)
                {
                  gboolean north, south;
                  gboolean west, east;
                  MetaRectangle frame_rect;
                  MetaGrabOp op;

                  meta_window_get_frame_rect (window, &frame_rect);

                  west = event->button.x < (frame_rect.x + 1 * frame_rect.width / 3);
                  east = event->button.x > (frame_rect.x + 2 * frame_rect.width / 3);
                  north = event->button.y < (frame_rect.y + 1 * frame_rect.height / 3);
                  south = event->button.y > (frame_rect.y + 2 * frame_rect.height / 3);

                  if (north && west)
                    op = META_GRAB_OP_RESIZING_NW;
                  else if (north && east)
                    op = META_GRAB_OP_RESIZING_NE;
                  else if (south && west)
                    op = META_GRAB_OP_RESIZING_SW;
                  else if (south && east)
                    op = META_GRAB_OP_RESIZING_SE;
                  else if (north)
                    op = META_GRAB_OP_RESIZING_N;
                  else if (west)
                    op = META_GRAB_OP_RESIZING_W;
                  else if (east)
                    op = META_GRAB_OP_RESIZING_E;
                  else if (south)
                    op = META_GRAB_OP_RESIZING_S;
                  else /* Middle region is no-op to avoid user triggering wrong action */
                    op = META_GRAB_OP_NONE;

                  if (op != META_GRAB_OP_NONE)
                    meta_display_begin_grab_op (display,
                                                window->screen,
                                                window,
                                                op,
                                                TRUE,
                                                FALSE,
                                                event->button.button,
                                                0,
                                                event->any.time,
                                                event->button.x,
                                                event->button.y);
                }
            }
          else if ((int) event->button.button == meta_prefs_get_mouse_button_menu ())
            {
              if (meta_prefs_get_raise_on_click ())
                meta_window_raise (window);
              meta_window_show_menu (window,
                                     event->button.x,
                                     event->button.y,
                                     event->button.button,
                                     event->any.time);
              bypass_clutter = TRUE;
              bypass_wayland = TRUE;
            }

          if (begin_move && window->has_move_func)
            {
              meta_display_begin_grab_op (display,
                                          window->screen,
                                          window,
                                          META_GRAB_OP_MOVING,
                                          TRUE,
                                          FALSE,
                                          event->button.button,
                                          0,
                                          event->any.time,
                                          event->button.x,
                                          event->button.y);
              bypass_clutter = TRUE;
              bypass_wayland = TRUE;
            }
        }
      break;
    case CLUTTER_BUTTON_RELEASE:
      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
        break;

      display->overlay_key_only_pressed = FALSE;

      if (display->grab_window == window &&
          meta_grab_op_is_mouse (display->grab_op))
        {
          meta_window_handle_mouse_grab_op_event (window, event);
          bypass_clutter = TRUE;
          bypass_wayland = TRUE;
        }
      break;
    case CLUTTER_MOTION:
      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
        break;

      if (display->grab_window == window &&
          meta_grab_op_is_mouse (display->grab_op))
        {
          meta_window_handle_mouse_grab_op_event (window, event);
          bypass_clutter = TRUE;
          bypass_wayland = TRUE;
        }
      break;

    case CLUTTER_KEY_PRESS:
    case CLUTTER_KEY_RELEASE:
      /* For key events, it's important to enforce single-handling, or
       * we can get into a confused state. So if a keybinding is
       * handled (because it's one of our hot-keys, or because we are
       * in a keyboard-grabbed mode like moving a window, we don't
       * want to pass the key event to the compositor or Wayland at all.
       */
      if (meta_display_process_key_event (display, window, (ClutterKeyEvent *) event))
        {
          bypass_clutter = TRUE;
          bypass_wayland = TRUE;
        }

    default:
      break;
    }

  /* If the compositor has a grab, don't pass that through to Wayland */
  if (display->grab_op == META_GRAB_OP_COMPOSITOR)
    bypass_wayland = TRUE;

  if (compositor && !bypass_wayland)
    {
      if (meta_wayland_compositor_handle_event (compositor, event))
        bypass_clutter = TRUE;
    }

  return bypass_clutter;
}

static gboolean
handle_input_xevent (MetaDisplay *display,
                     XIEvent     *input_event,
                     gulong       serial)
{
  XIDeviceEvent *device_event = (XIDeviceEvent *) input_event;
  XIEnterEvent *enter_event = (XIEnterEvent *) input_event;
  Window modified;
  MetaWindow *window;
  gboolean frame_was_receiver;

  if (input_event == NULL)
    return FALSE;

  modified = xievent_get_modified_window (display, input_event);
  window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL;

  frame_was_receiver = FALSE;
  if (window &&
      window->frame &&
      modified == window->frame->xwindow)
    {
      /* Note that if the frame and the client both have an
       * XGrabButton (as is normal with our setup), the event
       * goes to the frame.
       */
      frame_was_receiver = TRUE;
      meta_topic (META_DEBUG_EVENTS, "Frame was receiver of event for %s\n",
                  window->desc);
    }

  if (window && !window->override_redirect &&
      (input_event->evtype == XI_KeyPress || input_event->evtype == XI_ButtonPress))
    {
      if (CurrentTime == display->current_time)
        {
          /* We can't use missing (i.e. invalid) timestamps to set user time,
           * nor do we want to use them to sanity check other timestamps.
           * See bug 313490 for more details.
           */
          meta_warning ("Event has no timestamp! You may be using a broken "
                        "program such as xse.  Please ask the authors of that "
                        "program to fix it.\n");
        }
      else
        {
          meta_window_set_user_time (window, display->current_time);
          sanity_check_timestamps (display, display->current_time);
        }
    }

  switch (input_event->evtype)
    {
    case XI_ButtonPress:
      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
        break;

      display->overlay_key_only_pressed = FALSE;

      if (device_event->detail == 4 || device_event->detail == 5)
        /* Scrollwheel event, do nothing and deliver event to compositor below */
        break;

      if ((window &&
           meta_grab_op_is_mouse (display->grab_op) &&
           (device_event->mods.effective & display->window_grab_modifiers) &&
           display->grab_button != device_event->detail &&
           display->grab_window == window) ||
          grab_op_is_keyboard (display->grab_op))
        {
          meta_topic (META_DEBUG_WINDOW_OPS,
                      "Ending grab op %u on window %s due to button press\n",
                      display->grab_op,
                      (display->grab_window ?
                       display->grab_window->desc :
                       "none"));
          if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op))
            {
              MetaScreen *screen;
              meta_topic (META_DEBUG_WINDOW_OPS,
                          "Syncing to old stack positions.\n");
              screen =
                meta_display_screen_for_root (display, device_event->event);

              if (screen!=NULL)
                meta_stack_set_positions (screen->stack,
                                          display->grab_old_window_stacking);
            }
          meta_display_end_grab_op (display,
                                    device_event->time);
        }
      else if (window && display->grab_op == META_GRAB_OP_NONE)
        {
          gboolean begin_move = FALSE;
          unsigned int grab_mask;
          gboolean unmodified;

          grab_mask = display->window_grab_modifiers;
          if (g_getenv ("MUTTER_DEBUG_BUTTON_GRABS"))
            grab_mask |= ControlMask;

          /* Two possible sources of an unmodified event; one is a
           * client that's letting button presses pass through to the
           * frame, the other is our focus_window_grab on unmodified
           * button 1.  So for all such events we focus the window.
           */
          unmodified = (device_event->mods.effective & grab_mask) == 0;

          if (unmodified ||
              device_event->detail == 1)
            {
              /* don't focus if frame received, will be lowered in
               * frames.c or special-cased if the click was on a
               * minimize/close button.
               */
              if (!frame_was_receiver)
                {
                  if (meta_prefs_get_raise_on_click ())
                    meta_window_raise (window);
                  else
                    meta_topic (META_DEBUG_FOCUS,
                                "Not raising window on click due to don't-raise-on-click option\n");

                  /* Don't focus panels--they must explicitly request focus.
                   * See bug 160470
                   */
                  if (window->type != META_WINDOW_DOCK)
                    {
                      meta_topic (META_DEBUG_FOCUS,
                                  "Focusing %s due to unmodified button %u press (display.c)\n",
                                  window->desc, device_event->detail);
                      meta_window_focus (window, device_event->time);
                    }
                  else
                    /* However, do allow terminals to lose focus due to new
                     * window mappings after the user clicks on a panel.
                     */
                    display->allow_terminal_deactivation = TRUE;
                }

              /* you can move on alt-click but not on
               * the click-to-focus
               */
              if (!unmodified)
                begin_move = TRUE;
            }
          else if (!unmodified && device_event->detail == meta_prefs_get_mouse_button_resize())
            {
              if (window->has_resize_func)
                {
                  gboolean north, south;
                  gboolean west, east;
                  MetaRectangle frame_rect;
                  MetaGrabOp op;

                  meta_window_get_frame_rect (window, &frame_rect);

                  west = device_event->root_x <  (frame_rect.x + 1 * frame_rect.width  / 3);
                  east = device_event->root_x >  (frame_rect.x + 2 * frame_rect.width  / 3);
                  north = device_event->root_y < (frame_rect.y + 1 * frame_rect.height / 3);
                  south = device_event->root_y > (frame_rect.y + 2 * frame_rect.height / 3);

                  if (north && west)
                    op = META_GRAB_OP_RESIZING_NW;
                  else if (north && east)
                    op = META_GRAB_OP_RESIZING_NE;
                  else if (south && west)
                    op = META_GRAB_OP_RESIZING_SW;
                  else if (south && east)
                    op = META_GRAB_OP_RESIZING_SE;
                  else if (north)
                    op = META_GRAB_OP_RESIZING_N;
                  else if (west)
                    op = META_GRAB_OP_RESIZING_W;
                  else if (east)
                    op = META_GRAB_OP_RESIZING_E;
                  else if (south)
                    op = META_GRAB_OP_RESIZING_S;
                  else /* Middle region is no-op to avoid user triggering wrong action */
                    op = META_GRAB_OP_NONE;

                  if (op != META_GRAB_OP_NONE)
                    meta_display_begin_grab_op (display,
                                                window->screen,
                                                window,
                                                op,
                                                TRUE,
                                                FALSE,
                                                device_event->detail,
                                                0,
                                                device_event->time,
                                                device_event->root_x,
                                                device_event->root_y);
                }
            }
          else if (device_event->detail == meta_prefs_get_mouse_button_menu())
            {
              if (meta_prefs_get_raise_on_click ())
                meta_window_raise (window);
              meta_window_show_menu (window,
                                     device_event->root_x,
                                     device_event->root_y,
                                     device_event->detail,
                                     device_event->time);
            }

          if (!frame_was_receiver && unmodified)
            {
              /* This is from our synchronous grab since
               * it has no modifiers and was on the client window
               */

              meta_verbose ("Allowing events time %u\n",
                            (unsigned int)device_event->time);

              XIAllowEvents (display->xdisplay, device_event->deviceid,
                             XIReplayDevice, device_event->time);
            }

          if (begin_move && window->has_move_func)
            {
              meta_display_begin_grab_op (display,
                                          window->screen,
                                          window,
                                          META_GRAB_OP_MOVING,
                                          TRUE,
                                          FALSE,
                                          device_event->detail,
                                          0,
                                          device_event->time,
                                          device_event->root_x,
                                          device_event->root_y);
            }
        }
      break;
    case XI_ButtonRelease:
      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
        break;

      display->overlay_key_only_pressed = FALSE;

      if (display->grab_window == window &&
          meta_grab_op_is_mouse (display->grab_op))
        meta_window_handle_mouse_grab_op_xevent (window, device_event);
      break;
    case XI_Motion:
      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
        break;

      if (display->grab_window == window &&
          meta_grab_op_is_mouse (display->grab_op))
        meta_window_handle_mouse_grab_op_xevent (window, device_event);
      break;
    case XI_Enter:
      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
        break;

      /* If the mouse switches screens, active the default window on the new
       * screen; this will make keybindings and workspace-launched items
       * actually appear on the right screen.
       */
      {
        MetaScreen *new_screen =
          meta_display_screen_for_root (display, enter_event->root);

        if (new_screen != NULL && display->active_screen != new_screen)
          meta_workspace_focus_default_window (new_screen->active_workspace,
                                               NULL,
                                               enter_event->time);
      }

      /* Check if we've entered a window; do this even if window->has_focus to
       * avoid races.
       */
      if (window && !crossing_serial_is_ignored (display, serial) &&
          enter_event->mode != XINotifyGrab &&
          enter_event->mode != XINotifyUngrab &&
          enter_event->detail != XINotifyInferior &&
          meta_display_focus_sentinel_clear (display))
        {
          meta_window_handle_enter (window,
                                    enter_event->time,
                                    enter_event->root_x,
                                    enter_event->root_y);

          if (window->type == META_WINDOW_DOCK)
            meta_window_raise (window);
        }
      break;
    case XI_Leave:
      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
        break;

      if (window != NULL)
        {
          if (window->type == META_WINDOW_DOCK &&
              enter_event->mode != XINotifyGrab &&
              enter_event->mode != XINotifyUngrab &&
              !window->has_focus)
            meta_window_lower (window);
        }
      break;
    case XI_FocusIn:
    case XI_FocusOut:
      handle_window_focus_event (display, window, enter_event, serial);
      if (!window)
        {
          /* Check if the window is a root window. */
          MetaScreen *screen =
            meta_display_screen_for_root(display,
                                         enter_event->event);
          if (screen == NULL)
            break;

          if (enter_event->evtype == XI_FocusIn &&
              enter_event->mode == XINotifyDetailNone)
            {
              meta_topic (META_DEBUG_FOCUS,
                          "Focus got set to None, probably due to "
                          "brain-damage in the X protocol (see bug "
                          "125492).  Setting the default focus window.\n");
              meta_workspace_focus_default_window (screen->active_workspace,
                                                   NULL,
                                                   meta_display_get_current_time_roundtrip (display));
            }
          else if (enter_event->evtype == XI_FocusIn &&
                   enter_event->mode == XINotifyNormal &&
                   enter_event->detail == XINotifyInferior)
            {
              meta_topic (META_DEBUG_FOCUS,
                          "Focus got set to root window, probably due to "
                          "gnome-session logout dialog usage (see bug "
                          "153220).  Setting the default focus window.\n");
              meta_workspace_focus_default_window (screen->active_workspace,
                                                   NULL,
                                                   meta_display_get_current_time_roundtrip (display));
            }

        }
      break;
    }

  return FALSE;
}

static void
reload_xkb_rules (MetaScreen  *screen)
{
  MetaWaylandCompositor *compositor;
  char **names;
  int n_names;
  gboolean ok;
  const char *rules, *model, *layout, *variant, *options;

  compositor = meta_wayland_compositor_get_default ();

  ok = meta_prop_get_latin1_list (screen->display, screen->xroot,
                                  screen->display->atom__XKB_RULES_NAMES,
                                  &names, &n_names);
  if (!ok)
    return;

  if (n_names != 5)
    goto out;

  rules = names[0];
  model = names[1];
  layout = names[2];
  variant = names[3];
  options = names[4];

  meta_wayland_keyboard_set_keymap_names (&compositor->seat->keyboard,
                                          rules, model, layout, variant, options,
                                          META_WAYLAND_KEYBOARD_SKIP_XCLIENTS);

 out:
  g_strfreev (names);
}

static gboolean
handle_other_xevent (MetaDisplay *display,
                     XEvent      *event)
{
  Window modified;
  MetaWindow *window;
  MetaWindow *property_for_window;
  gboolean frame_was_receiver;
  gboolean bypass_gtk = FALSE;

  modified = event_get_modified_window (display, event);
  window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL;
  frame_was_receiver = (window && window->frame && modified == window->frame->xwindow);

  /* We only want to respond to _NET_WM_USER_TIME property notify
   * events on _NET_WM_USER_TIME_WINDOW windows; in particular,
   * responding to UnmapNotify events is kind of bad.
   */
  property_for_window = NULL;
  if (window && modified == window->user_time_window)
    {
      property_for_window = window;
      window = NULL;
    }

#ifdef HAVE_XSYNC
  if (META_DISPLAY_HAS_XSYNC (display) &&
      event->type == (display->xsync_event_base + XSyncAlarmNotify))
    {
      MetaWindow *alarm_window = meta_display_lookup_sync_alarm (display,
                                                                 ((XSyncAlarmNotifyEvent*)event)->alarm);

      if (alarm_window != NULL)
        {
          XSyncValue value = ((XSyncAlarmNotifyEvent*)event)->counter_value;
          gint64 new_counter_value;
          new_counter_value = XSyncValueLow32 (value) + ((gint64)XSyncValueHigh32 (value) << 32);
          meta_window_update_sync_request_counter (alarm_window, new_counter_value);
          bypass_gtk = TRUE; /* GTK doesn't want to see this really */
        }
      else
        meta_idle_monitor_handle_xevent_all (event);

      goto out;
    }
#endif /* HAVE_XSYNC */

#ifdef HAVE_SHAPE
  if (META_DISPLAY_HAS_SHAPE (display) && 
      event->type == (display->shape_event_base + ShapeNotify))
    {
      bypass_gtk = TRUE; /* GTK doesn't want to see this really */
      
      if (window && !frame_was_receiver)
        {
          XShapeEvent *sev = (XShapeEvent*) event;

          if (sev->kind == ShapeBounding)
            meta_window_x11_update_shape_region (window);
          else if (sev->kind == ShapeInput)
            meta_window_x11_update_input_region (window);
        }
      else
        {
          meta_topic (META_DEBUG_SHAPES,
                      "ShapeNotify not on a client window (window %s frame_was_receiver = %d)\n",
                      window ? window->desc : "(none)",
                      frame_was_receiver);
        }

      goto out;
    }
#endif /* HAVE_SHAPE */

  switch (event->type)
    {
    case KeymapNotify:
      break;
    case Expose:
      break;
    case GraphicsExpose:
      break;
    case NoExpose:
      break;
    case VisibilityNotify:
      break;
    case CreateNotify:
      {
        MetaScreen *screen;

        screen = meta_display_screen_for_root (display,
                                               event->xcreatewindow.parent);
        if (screen)
          meta_stack_tracker_create_event (screen->stack_tracker,
                                           &event->xcreatewindow);
      }
      break;

    case DestroyNotify:
      {
        MetaScreen *screen;

        screen = meta_display_screen_for_root (display,
                                               event->xdestroywindow.event);
        if (screen)
          meta_stack_tracker_destroy_event (screen->stack_tracker,
                                            &event->xdestroywindow);
      }
      if (window)
        {
          /* FIXME: It sucks that DestroyNotify events don't come with
           * a timestamp; could we do something better here?  Maybe X
           * will change one day?
           */
          guint32 timestamp;
          timestamp = meta_display_get_current_time_roundtrip (display);

          if (display->grab_op != META_GRAB_OP_NONE &&
              display->grab_window == window)
            meta_display_end_grab_op (display, timestamp);

          if (frame_was_receiver)
            {
              meta_warning ("Unexpected destruction of frame 0x%lx, not sure if this should silently fail or be considered a bug\n",
                            window->frame->xwindow);
              meta_error_trap_push (display);
              meta_window_destroy_frame (window->frame->window);
              meta_error_trap_pop (display);
            }
          else
            {
              /* Unmanage destroyed window */
              meta_window_unmanage (window, timestamp);
              window = NULL;
            }
        }
      break;
    case UnmapNotify:
      if (window)
        {
          /* FIXME: It sucks that UnmapNotify events don't come with
           * a timestamp; could we do something better here?  Maybe X
           * will change one day?
           */
          guint32 timestamp;
          timestamp = meta_display_get_current_time_roundtrip (display);

          if (display->grab_op != META_GRAB_OP_NONE &&
              display->grab_window == window &&
              window->frame == NULL)
            meta_display_end_grab_op (display, timestamp);

          if (!frame_was_receiver)
            {
              if (window->unmaps_pending == 0)
                {
                  meta_topic (META_DEBUG_WINDOW_STATE,
                              "Window %s withdrawn\n",
                              window->desc);

                  /* Unmanage withdrawn window */
                  window->withdrawn = TRUE;
                  meta_window_unmanage (window, timestamp);
                  window = NULL;
                }
              else
                {
                  window->unmaps_pending -= 1;
                  meta_topic (META_DEBUG_WINDOW_STATE,
                              "Received pending unmap, %d now pending\n",
                              window->unmaps_pending);
                }
            }
        }
      break;
    case MapNotify:
      /* NB: override redirect windows wont cause a map request so we
       * watch out for map notifies against any root windows too if a
       * compositor is enabled: */
      if (display->compositor && window == NULL
          && meta_display_screen_for_root (display, event->xmap.event))
        {
          window = meta_window_x11_new (display, event->xmap.window,
                                        FALSE, META_COMP_EFFECT_CREATE);
        }
      break;
    case MapRequest:
      if (window == NULL)
        {
          window = meta_window_x11_new (display, event->xmaprequest.window,
                                        FALSE, META_COMP_EFFECT_CREATE);
        }
      /* if frame was receiver it's some malicious send event or something */
      else if (!frame_was_receiver && window)
        {
          meta_verbose ("MapRequest on %s mapped = %d minimized = %d\n",
                        window->desc, window->mapped, window->minimized);
          if (window->minimized)
            {
              meta_window_unminimize (window);
              if (window->workspace != window->screen->active_workspace)
                {
                  meta_verbose ("Changing workspace due to MapRequest mapped = %d minimized = %d\n",
                                window->mapped, window->minimized);
                  meta_window_change_workspace (window,
                                                window->screen->active_workspace);
                }
            }
        }
      break;
    case ReparentNotify:
      {
        MetaScreen *screen;

        screen = meta_display_screen_for_root (display,
                                               event->xconfigure.event);
        if (screen)
          meta_stack_tracker_reparent_event (screen->stack_tracker,
                                             &event->xreparent);
      }
      break;
    case ConfigureNotify:
      if (event->xconfigure.event != event->xconfigure.window)
        {
          MetaScreen *screen;

          screen = meta_display_screen_for_root (display,
                                                 event->xconfigure.event);
          if (screen)
            meta_stack_tracker_configure_event (screen->stack_tracker,
                                                &event->xconfigure);
        }

      if (window && window->override_redirect)
        meta_window_configure_notify (window, &event->xconfigure);

      break;
    case ConfigureRequest:
      /* This comment and code is found in both twm and fvwm */
      /*
       * According to the July 27, 1988 ICCCM draft, we should ignore size and
       * position fields in the WM_NORMAL_HINTS property when we map a window.
       * Instead, we'll read the current geometry.  Therefore, we should respond
       * to configuration requests for windows which have never been mapped.
       */
      if (window == NULL)
        {
          unsigned int xwcm;
          XWindowChanges xwc;

          xwcm = event->xconfigurerequest.value_mask &
            (CWX | CWY | CWWidth | CWHeight | CWBorderWidth);

          xwc.x = event->xconfigurerequest.x;
          xwc.y = event->xconfigurerequest.y;
          xwc.width = event->xconfigurerequest.width;
          xwc.height = event->xconfigurerequest.height;
          xwc.border_width = event->xconfigurerequest.border_width;

          meta_verbose ("Configuring withdrawn window to %d,%d %dx%d border %d (some values may not be in mask)\n",
                        xwc.x, xwc.y, xwc.width, xwc.height, xwc.border_width);
          meta_error_trap_push (display);
          XConfigureWindow (display->xdisplay, event->xconfigurerequest.window,
                            xwcm, &xwc);
          meta_error_trap_pop (display);
        }
      else
        {
          if (!frame_was_receiver)
            meta_window_x11_configure_request (window, event);
        }
      break;
    case GravityNotify:
      break;
    case ResizeRequest:
      break;
    case CirculateNotify:
      break;
    case CirculateRequest:
      break;
    case PropertyNotify:
      {
        MetaGroup *group;
        MetaScreen *screen;

        if (window && !frame_was_receiver)
          meta_window_x11_property_notify (window, event);
        else if (property_for_window && !frame_was_receiver)
          meta_window_x11_property_notify (property_for_window, event);

        group = meta_display_lookup_group (display,
                                           event->xproperty.window);
        if (group != NULL)
          meta_group_property_notify (group, event);

        screen = NULL;
        if (window == NULL &&
            group == NULL) /* window/group != NULL means it wasn't a root window */
          screen = meta_display_screen_for_root (display,
                                                 event->xproperty.window);

        if (screen != NULL)
          {
            if (event->xproperty.atom ==
                display->atom__NET_DESKTOP_LAYOUT)
              meta_screen_update_workspace_layout (screen);
            else if (event->xproperty.atom ==
                     display->atom__NET_DESKTOP_NAMES)
              meta_screen_update_workspace_names (screen);
            else if (meta_is_wayland_compositor () &&
                     event->xproperty.atom ==
                     display->atom__XKB_RULES_NAMES)
              reload_xkb_rules (screen);
#if 0
            else if (event->xproperty.atom ==
                     display->atom__NET_RESTACK_WINDOW)
              handle_net_restack_window (display, event);
#endif

            /* we just use this property as a sentinel to avoid
             * certain race conditions.  See the comment for the
             * sentinel_counter variable declaration in display.h
             */
            if (event->xproperty.atom ==
                display->atom__MUTTER_SENTINEL)
              {
                meta_display_decrement_focus_sentinel (display);
              }
          }
      }
      break;
    case SelectionClear:
      /* do this here instead of at end of function
       * so we can return
       */

      /* FIXME: Clearing display->current_time here makes no sense to
       * me; who put this here and why?
       */
      display->current_time = CurrentTime;

      process_selection_clear (display, event);
      /* Note that processing that may have resulted in
       * closing the display... so return right away.
       */
      return FALSE;
    case SelectionRequest:
      process_selection_request (display, event);
      break;
    case SelectionNotify:
      break;
    case ColormapNotify:
      if (window && !frame_was_receiver)
        window->colormap = event->xcolormap.colormap;
      break;
    case ClientMessage:
      if (window)
        {
          if (!frame_was_receiver)
            meta_window_x11_client_message (window, event);
        }
      else
        {
          MetaScreen *screen;

          screen = meta_display_screen_for_root (display,
                                                 event->xclient.window);

          if (screen)
            {
              if (event->xclient.message_type ==
                  display->atom__NET_CURRENT_DESKTOP)
                {
                  int space;
                  MetaWorkspace *workspace;
                  guint32 time;

                  space = event->xclient.data.l[0];
                  time = event->xclient.data.l[1];

                  meta_verbose ("Request to change current workspace to %d with "
                                "specified timestamp of %u\n",
                                space, time);

                  workspace =
                    meta_screen_get_workspace_by_index (screen,
                                                        space);

                  /* Handle clients using the older version of the spec... */
                  if (time == 0 && workspace)
                    {
                      meta_warning ("Received a NET_CURRENT_DESKTOP message "
                                    "from a broken (outdated) client who sent "
                                    "a 0 timestamp\n");
                      time = meta_display_get_current_time_roundtrip (display);
                    }

                  if (workspace)
                    meta_workspace_activate (workspace, time);
                  else
                    meta_verbose ("Don't know about workspace %d\n", space);
                }
              else if (event->xclient.message_type ==
                       display->atom__NET_NUMBER_OF_DESKTOPS)
                {
                  int num_spaces;

                  num_spaces = event->xclient.data.l[0];

                  meta_verbose ("Request to set number of workspaces to %d\n",
                                num_spaces);

                  meta_prefs_set_num_workspaces (num_spaces);
                }
              else if (event->xclient.message_type ==
                       display->atom__NET_SHOWING_DESKTOP)
                {
                  gboolean showing_desktop;
                  guint32  timestamp;

                  showing_desktop = event->xclient.data.l[0] != 0;
                  /* FIXME: Braindead protocol doesn't have a timestamp */
                  timestamp = meta_display_get_current_time_roundtrip (display);
                  meta_verbose ("Request to %s desktop\n",
                                showing_desktop ? "show" : "hide");

                  if (showing_desktop)
                    meta_screen_show_desktop (screen, timestamp);
                  else
                    {
                      meta_screen_unshow_desktop (screen);
                      meta_workspace_focus_default_window (screen->active_workspace, NULL, timestamp);
                    }
                }
              else if (event->xclient.message_type ==
                       display->atom_WM_PROTOCOLS)
                {
                  meta_verbose ("Received WM_PROTOCOLS message\n");

                  if ((Atom)event->xclient.data.l[0] == display->atom__NET_WM_PING)
                    {
                      guint32 timestamp = event->xclient.data.l[1];

                      meta_display_pong_for_serial (display, timestamp);

                      /* We don't want ping reply events going into
                       * the GTK+ event loop because gtk+ will treat
                       * them as ping requests and send more replies.
                       */
                      bypass_gtk = TRUE;
                    }
                }
            }

          if (event->xclient.message_type ==
              display->atom__NET_REQUEST_FRAME_EXTENTS)
            {
              meta_verbose ("Received _NET_REQUEST_FRAME_EXTENTS message\n");
              process_request_frame_extents (display, event);
            }
        }
      break;
    case MappingNotify:
      {
        gboolean ignore_current;

        ignore_current = FALSE;

        /* Check whether the next event is an identical MappingNotify
         * event.  If it is, ignore the current event, we'll update
         * when we get the next one.
         */
        if (XPending (display->xdisplay))
          {
            XEvent next_event;

            XPeekEvent (display->xdisplay, &next_event);

            if (next_event.type == MappingNotify &&
                next_event.xmapping.request == event->xmapping.request)
              ignore_current = TRUE;
          }

        if (!ignore_current)
          {
            /* Let XLib know that there is a new keyboard mapping.
             */
            XRefreshKeyboardMapping (&event->xmapping);
            meta_display_process_mapping_event (display, event);
          }
      }
      break;
    default:
#ifdef HAVE_XKB
      if (event->type == display->xkb_base_event_type)
        {
          XkbAnyEvent *xkb_ev = (XkbAnyEvent *) event;

          switch (xkb_ev->xkb_type)
            {
            case XkbBellNotify:
              if (XSERVER_TIME_IS_BEFORE(display->last_bell_time,
                                         xkb_ev->time - 100))
                {
                  display->last_bell_time = xkb_ev->time;
                  meta_bell_notify (display, xkb_ev);
                }
              break;
            case XkbNewKeyboardNotify:
            case XkbMapNotify:
              if (xkb_ev->device == META_VIRTUAL_CORE_KEYBOARD_ID)
                meta_display_process_mapping_event (display, event);
              break;
            }
        }
#endif
      break;
    }

 out:
  return bypass_gtk;
}

/**
 * meta_display_handle_xevent:
 * @display: The MetaDisplay that events are coming from
 * @event: The event that just happened
 *
 * This is the most important function in the whole program. It is the heart,
 * it is the nexus, it is the Grand Central Station of Mutter's world.
 * When we create a #MetaDisplay, we ask GDK to pass *all* events for *all*
 * windows to this function. So every time anything happens that we might
 * want to know about, this function gets called. You see why it gets a bit
 * busy around here. Most of this function is a ginormous switch statement
 * dealing with all the kinds of events that might turn up.
 */
static gboolean
meta_display_handle_xevent (MetaDisplay *display,
                            XEvent      *event)
{
  Window modified;
  gboolean bypass_compositor = FALSE, bypass_gtk = FALSE;
  XIEvent *input_event;
  MetaMonitorManager *monitor;
  MetaScreen *screen;

#ifdef WITH_VERBOSE_MODE
  if (dump_events)
    meta_spew_event (display, event);
#endif

#ifdef HAVE_STARTUP_NOTIFICATION
  sn_display_process_event (display->sn_display, event);
#endif

  /* Intercept XRandR events early and don't attempt any
     processing for them. We still let them through to Gdk though,
     so it can update its own internal state.
  */
  monitor = meta_monitor_manager_get ();
  if (meta_monitor_manager_handle_xevent (monitor, event))
    {
      bypass_compositor = TRUE;
      goto out;
    }

  display->current_time = event_get_time (display, event);
  display->monitor_cache_invalidated = TRUE;

  if (display->focused_by_us &&
      event->xany.serial > display->focus_serial &&
      display->focus_window &&
      !window_has_xwindow (display->focus_window, display->server_focus_window))
    {
      meta_topic (META_DEBUG_FOCUS, "Earlier attempt to focus %s failed\n",
                  display->focus_window->desc);
      update_focus_window (display,
                           META_FOCUS_NONE,
                           meta_display_lookup_x_window (display, display->server_focus_window),
                           display->server_focus_window,
                           display->server_focus_serial,
                           FALSE);
    }

  screen = meta_display_screen_for_root (display, event->xany.window);
  if (screen)
    {
      if (meta_screen_handle_xevent (screen, event))
        {
          bypass_gtk = bypass_compositor = TRUE;
          goto out;
        }
    }

  modified = event_get_modified_window (display, event);

  input_event = get_input_event (display, event);

  if (event->type == UnmapNotify)
    {
      if (meta_ui_window_should_not_cause_focus (display->xdisplay,
                                                 modified))
        {
          meta_display_add_ignored_crossing_serial (display, event->xany.serial);
          meta_topic (META_DEBUG_FOCUS,
                      "Adding EnterNotify serial %lu to ignored focus serials\n",
                      event->xany.serial);
        }
    }
  else if (input_event &&
           input_event->evtype == XI_Leave &&
           ((XILeaveEvent *)input_event)->mode == XINotifyUngrab &&
           modified == display->ungrab_should_not_cause_focus_window)
    {
      meta_display_add_ignored_crossing_serial (display, event->xany.serial);
      meta_topic (META_DEBUG_FOCUS,
                  "Adding LeaveNotify serial %lu to ignored focus serials\n",
                  event->xany.serial);
    }

#ifdef HAVE_XI23
  if (meta_display_process_barrier_event (display, input_event))
    {
      bypass_gtk = bypass_compositor = TRUE;
      goto out;
    }
#endif /* HAVE_XI23 */

  /* libXi does not properly copy the serial to XI2 events, so pull it
   * from the parent XAnyEvent and pass it to handle_input_xevent.
   * See: https://bugs.freedesktop.org/show_bug.cgi?id=64687
   */
  if (handle_input_xevent (display, input_event, event->xany.serial))
    {
      bypass_gtk = bypass_compositor = TRUE;
      goto out;
    }

  if (handle_other_xevent (display, event))
    {
      bypass_gtk = TRUE;
      goto out;
    }

 out:
  if (display->compositor && !bypass_compositor)
    {
      MetaWindow *window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL;

      if (meta_compositor_process_event (display->compositor, event, window))
        bypass_gtk = TRUE;
    }
  
  display->current_time = CurrentTime;
  return bypass_gtk;
}

static gboolean
xevent_callback (XEvent  *event,
                 gpointer data)
{
  MetaDisplay *display = data;

  return meta_display_handle_xevent (display, event);
}

static gboolean
event_callback (const ClutterEvent *event,
                gpointer            data)
{
  MetaDisplay *display = data;

  return meta_display_handle_event (display, event);
}

static Window
xievent_get_modified_window (MetaDisplay *display,
                             XIEvent *input_event)
{
  switch (input_event->evtype)
    {
    case XI_Motion:
    case XI_ButtonPress:
    case XI_ButtonRelease:
    case XI_KeyPress:
    case XI_KeyRelease:
      return ((XIDeviceEvent *) input_event)->event;
    case XI_FocusIn:
    case XI_FocusOut:
    case XI_Enter:
    case XI_Leave:
      return ((XIEnterEvent *) input_event)->event;
#ifdef HAVE_XI23
    case XI_BarrierHit:
    case XI_BarrierLeave:
      return ((XIBarrierEvent *) input_event)->event;
#endif /* HAVE_XI23 */
    }

  return None;
}

/* Return the window this has to do with, if any, rather
 * than the frame or root window that was selecting
 * for substructure
 */
static Window
event_get_modified_window (MetaDisplay *display,
                           XEvent *event)
{
  XIEvent *input_event = get_input_event (display, event);

  if (input_event)
    return xievent_get_modified_window (display, input_event);

  switch (event->type)
    {
    case KeymapNotify:
    case Expose:
    case GraphicsExpose:
    case NoExpose:
    case VisibilityNotify:
    case ResizeRequest:
    case PropertyNotify:
    case SelectionClear:
    case SelectionRequest:
    case SelectionNotify:
    case ColormapNotify:
    case ClientMessage:
      return event->xany.window;
      
    case CreateNotify:
      return event->xcreatewindow.window;
      
    case DestroyNotify:
      return event->xdestroywindow.window;

    case UnmapNotify:
      return event->xunmap.window;

    case MapNotify:
      return event->xmap.window;

    case MapRequest:
      return event->xmaprequest.window;

    case ReparentNotify:
     return event->xreparent.window;
      
    case ConfigureNotify:
      return event->xconfigure.window;
      
    case ConfigureRequest:
      return event->xconfigurerequest.window;

    case GravityNotify:
      return event->xgravity.window;

    case CirculateNotify:
      return event->xcirculate.window;

    case CirculateRequest:
      return event->xcirculaterequest.window;

    case MappingNotify:
      return None;

    default:
#ifdef HAVE_SHAPE
      if (META_DISPLAY_HAS_SHAPE (display) && 
          event->type == (display->shape_event_base + ShapeNotify))
        {
          XShapeEvent *sev = (XShapeEvent*) event;
          return sev->window;
        }
#endif

      return None;
    }
}

static guint32
event_get_time (MetaDisplay *display,
                XEvent      *event)
{
  XIEvent *input_event = get_input_event (display, event);

  if (input_event)
    return input_event->time;

  switch (event->type)
    {
    case PropertyNotify:
      return event->xproperty.time;

    case SelectionClear:
    case SelectionRequest:
    case SelectionNotify:
      return event->xselection.time;

    case KeymapNotify:      
    case Expose:
    case GraphicsExpose:
    case NoExpose:
    case MapNotify:
    case UnmapNotify:
    case VisibilityNotify:
    case ResizeRequest:
    case ColormapNotify:
    case ClientMessage:
    case CreateNotify:
    case DestroyNotify:
    case MapRequest:
    case ReparentNotify:
    case ConfigureNotify:
    case ConfigureRequest:
    case GravityNotify:
    case CirculateNotify:
    case CirculateRequest:
    case MappingNotify:
    default:
      return CurrentTime;
    }
}

#ifdef WITH_VERBOSE_MODE
const char*
meta_event_detail_to_string (int d)
{
  const char *detail = "???";
  switch (d)
    {
      /* We are an ancestor in the A<->B focus change relationship */
    case XINotifyAncestor:
      detail = "NotifyAncestor";
      break;
    case XINotifyDetailNone:
      detail = "NotifyDetailNone";
      break;
      /* We are a descendant in the A<->B focus change relationship */
    case XINotifyInferior:
      detail = "NotifyInferior";
      break;
    case XINotifyNonlinear:
      detail = "NotifyNonlinear";
      break;
    case XINotifyNonlinearVirtual:
      detail = "NotifyNonlinearVirtual";
      break;
    case XINotifyPointer:
      detail = "NotifyPointer";
      break;
    case XINotifyPointerRoot:
      detail = "NotifyPointerRoot";
      break;
    case XINotifyVirtual:
      detail = "NotifyVirtual";
      break;
    }

  return detail;
}
#endif /* WITH_VERBOSE_MODE */

#ifdef WITH_VERBOSE_MODE
const char*
meta_event_mode_to_string (int m)
{
  const char *mode = "???";
  switch (m)
    {
    case XINotifyNormal:
      mode = "NotifyNormal";
      break;
    case XINotifyGrab:
      mode = "NotifyGrab";
      break;
    case XINotifyUngrab:
      mode = "NotifyUngrab";
      break;
    case XINotifyWhileGrabbed:
      mode = "NotifyWhileGrabbed";
      break;
    }

  return mode;
}
#endif /* WITH_VERBOSE_MODE */

#ifdef WITH_VERBOSE_MODE
static const char*
stack_mode_to_string (int mode)
{
  switch (mode)
    {
    case Above:
      return "Above";
    case Below:
      return "Below";
    case TopIf:
      return "TopIf";
    case BottomIf:
      return "BottomIf";
    case Opposite:
      return "Opposite";      
    }

  return "Unknown";
}
#endif /* WITH_VERBOSE_MODE */

#ifdef HAVE_XSYNC
#ifdef WITH_VERBOSE_MODE
static gint64
sync_value_to_64 (const XSyncValue *value)
{
  gint64 v;

  v = XSyncValueLow32 (*value);
  v |= (((gint64)XSyncValueHigh32 (*value)) << 32);
  
  return v;
}
#endif /* WITH_VERBOSE_MODE */

#ifdef WITH_VERBOSE_MODE
static const char*
alarm_state_to_string (XSyncAlarmState state)
{
  switch (state)
    {
    case XSyncAlarmActive:
      return "Active";
    case XSyncAlarmInactive:
      return "Inactive";
    case XSyncAlarmDestroyed:
      return "Destroyed";
    default:
      return "(unknown)";
    }
}
#endif /* WITH_VERBOSE_MODE */

#endif /* HAVE_XSYNC */

#ifdef WITH_VERBOSE_MODE
static void
meta_spew_xi2_event (MetaDisplay *display,
                     XIEvent     *input_event,
                     const char **name_p,
                     char       **extra_p)
{
  const char *name = NULL;
  char *extra = NULL;

  XIDeviceEvent *device_event = (XIDeviceEvent *) input_event;
  XIEnterEvent *enter_event = (XIEnterEvent *) input_event;

  switch (input_event->evtype)
    {
    case XI_Motion:
      name = "XI_Motion";
      break;
    case XI_ButtonPress:
      name = "XI_ButtonPress";
      break;
    case XI_ButtonRelease:
      name = "XI_ButtonRelease";
      break;
    case XI_KeyPress:
      name = "XI_KeyPress";
      break;
    case XI_KeyRelease:
      name = "XI_KeyRelease";
      break;
    case XI_FocusIn:
      name = "XI_FocusIn";
      break;
    case XI_FocusOut:
      name = "XI_FocusOut";
      break;
    case XI_Enter:
      name = "XI_Enter";
      break;
    case XI_Leave:
      name = "XI_Leave";
      break;
#ifdef HAVE_XI23
    case XI_BarrierHit:
      name = "XI_BarrierHit";
      break;
    case XI_BarrierLeave:
      name = "XI_BarrierLeave";
      break;
#endif /* HAVE_XI23 */
    }

  switch (input_event->evtype)
    {
    case XI_Motion:
      extra = g_strdup_printf ("win: 0x%lx x: %g y: %g",
                               device_event->event,
                               device_event->root_x,
                               device_event->root_y);
      break;
    case XI_ButtonPress:
    case XI_ButtonRelease:
      extra = g_strdup_printf ("button %u x %g y %g root 0x%lx",
                               device_event->detail,
                               device_event->root_x,
                               device_event->root_y,
                               device_event->root);
      break;
    case XI_KeyPress:
    case XI_KeyRelease:
      {
        KeySym keysym;
        const char *str;
  
        keysym = XKeycodeToKeysym (display->xdisplay, device_event->detail, 0);

        str = XKeysymToString (keysym);

        extra = g_strdup_printf ("Key '%s' state 0x%x",
                                 str ? str : "none", device_event->mods.effective);
      }
      break;
    case XI_FocusIn:
    case XI_FocusOut:
      extra = g_strdup_printf ("detail: %s mode: %s\n",
                               meta_event_detail_to_string (enter_event->detail),
                               meta_event_mode_to_string (enter_event->mode));
      break;
    case XI_Enter:
    case XI_Leave:
      extra = g_strdup_printf ("win: 0x%lx root: 0x%lx mode: %s detail: %s focus: %d x: %g y: %g",
                               enter_event->event,
                               enter_event->root,
                               meta_event_mode_to_string (enter_event->mode),
                               meta_event_detail_to_string (enter_event->detail),
                               enter_event->focus,
                               enter_event->root_x,
                               enter_event->root_y);
      break;
    }

  *name_p = name;
  *extra_p = extra;
}

static void
meta_spew_core_event (MetaDisplay *display,
                      XEvent      *event,
                      const char **name_p,
                      char       **extra_p)
{
  const char *name = NULL;
  char *extra = NULL;

  switch (event->type)
    {
    case KeymapNotify:
      name = "KeymapNotify";
      break;
    case Expose:
      name = "Expose";
      break;
    case GraphicsExpose:
      name = "GraphicsExpose";
      break;
    case NoExpose:
      name = "NoExpose";
      break;
    case VisibilityNotify:
      name = "VisibilityNotify";
      break;
    case CreateNotify:
      name = "CreateNotify";
      extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx",
                               event->xcreatewindow.parent,
                               event->xcreatewindow.window);
      break;
    case DestroyNotify:
      name = "DestroyNotify";
      extra = g_strdup_printf ("event: 0x%lx window: 0x%lx",
                               event->xdestroywindow.event,
                               event->xdestroywindow.window);
      break;
    case UnmapNotify:
      name = "UnmapNotify";
      extra = g_strdup_printf ("event: 0x%lx window: 0x%lx from_configure: %d",
                               event->xunmap.event,
                               event->xunmap.window,
                               event->xunmap.from_configure);
      break;
    case MapNotify:
      name = "MapNotify";
      extra = g_strdup_printf ("event: 0x%lx window: 0x%lx override_redirect: %d",
                               event->xmap.event,
                               event->xmap.window,
                               event->xmap.override_redirect);
      break;
    case MapRequest:
      name = "MapRequest";
      extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx\n",
                               event->xmaprequest.window,
                               event->xmaprequest.parent);
      break;
    case ReparentNotify:
      name = "ReparentNotify";
      extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx event: 0x%lx\n",
                               event->xreparent.window,
                               event->xreparent.parent,
                               event->xreparent.event);
      break;
    case ConfigureNotify:
      name = "ConfigureNotify";
      extra = g_strdup_printf ("x: %d y: %d w: %d h: %d above: 0x%lx override_redirect: %d",
                               event->xconfigure.x,
                               event->xconfigure.y,
                               event->xconfigure.width,
                               event->xconfigure.height,
                               event->xconfigure.above,
                               event->xconfigure.override_redirect);
      break;
    case ConfigureRequest:
      name = "ConfigureRequest";
      extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx x: %d %sy: %d %sw: %d %sh: %d %sborder: %d %sabove: %lx %sstackmode: %s %s",
                               event->xconfigurerequest.parent,
                               event->xconfigurerequest.window,
                               event->xconfigurerequest.x,
                               event->xconfigurerequest.value_mask &
                               CWX ? "" : "(unset) ",
                               event->xconfigurerequest.y,
                               event->xconfigurerequest.value_mask &
                               CWY ? "" : "(unset) ",
                               event->xconfigurerequest.width,
                               event->xconfigurerequest.value_mask &
                               CWWidth ? "" : "(unset) ",
                               event->xconfigurerequest.height,
                               event->xconfigurerequest.value_mask &
                               CWHeight ? "" : "(unset) ",
                               event->xconfigurerequest.border_width,
                               event->xconfigurerequest.value_mask &
                               CWBorderWidth ? "" : "(unset)",
                               event->xconfigurerequest.above,
                               event->xconfigurerequest.value_mask &
                               CWSibling ? "" : "(unset)",
                               stack_mode_to_string (event->xconfigurerequest.detail),
                               event->xconfigurerequest.value_mask &
                               CWStackMode ? "" : "(unset)");
      break;
    case GravityNotify:
      name = "GravityNotify";
      break;
    case ResizeRequest:
      name = "ResizeRequest";
      extra = g_strdup_printf ("width = %d height = %d",
                               event->xresizerequest.width,
                               event->xresizerequest.height);
      break;
    case CirculateNotify:
      name = "CirculateNotify";
      break;
    case CirculateRequest:
      name = "CirculateRequest";
      break;
    case PropertyNotify:
      {
        char *str;
        const char *state;
            
        name = "PropertyNotify";
            
        meta_error_trap_push (display);
        str = XGetAtomName (display->xdisplay,
                            event->xproperty.atom);
        meta_error_trap_pop (display);

        if (event->xproperty.state == PropertyNewValue)
          state = "PropertyNewValue";
        else if (event->xproperty.state == PropertyDelete)
          state = "PropertyDelete";
        else
          state = "???";
            
        extra = g_strdup_printf ("atom: %s state: %s",
                                 str ? str : "(unknown atom)",
                                 state);
        meta_XFree (str);
      }
      break;
    case SelectionClear:
      name = "SelectionClear";
      break;
    case SelectionRequest:
      name = "SelectionRequest";
      break;
    case SelectionNotify:
      name = "SelectionNotify";
      break;
    case ColormapNotify:
      name = "ColormapNotify";
      break;
    case ClientMessage:
      {
        char *str;
        name = "ClientMessage";
        meta_error_trap_push (display);
        str = XGetAtomName (display->xdisplay,
                            event->xclient.message_type);
        meta_error_trap_pop (display);
        extra = g_strdup_printf ("type: %s format: %d\n",
                                 str ? str : "(unknown atom)",
                                 event->xclient.format);
        meta_XFree (str);
      }
      break;
    case MappingNotify:
      name = "MappingNotify";
      break;
    default:
#ifdef HAVE_XSYNC
      if (META_DISPLAY_HAS_XSYNC (display) && 
          event->type == (display->xsync_event_base + XSyncAlarmNotify))
        {
          XSyncAlarmNotifyEvent *aevent = (XSyncAlarmNotifyEvent*) event;
          
          name = "XSyncAlarmNotify";
          extra =
            g_strdup_printf ("alarm: 0x%lx"
                             " counter_value: %" G_GINT64_FORMAT
                             " alarm_value: %" G_GINT64_FORMAT
                             " time: %u alarm state: %s",
                             aevent->alarm,
                             (gint64) sync_value_to_64 (&aevent->counter_value),
                             (gint64) sync_value_to_64 (&aevent->alarm_value),
                             (unsigned int)aevent->time,
                             alarm_state_to_string (aevent->state));
        }
      else
#endif /* HAVE_XSYNC */
#ifdef HAVE_SHAPE
        if (META_DISPLAY_HAS_SHAPE (display) && 
            event->type == (display->shape_event_base + ShapeNotify))
          {
            XShapeEvent *sev = (XShapeEvent*) event;

            name = "ShapeNotify";

            extra =
              g_strdup_printf ("kind: %s "
                               "x: %d y: %d w: %u h: %u "
                               "shaped: %d",
                               sev->kind == ShapeBounding ?
                               "ShapeBounding" :
                               (sev->kind == ShapeClip ?
                                "ShapeClip" : "(unknown)"),
                               sev->x, sev->y, sev->width, sev->height,
                               sev->shaped);
          }
        else
#endif /* HAVE_SHAPE */      
          {
            name = "(Unknown event)";
            extra = g_strdup_printf ("type: %d", event->xany.type);
          }
      break;
    }

  *name_p = name;
  *extra_p = extra;
}

static void
meta_spew_event (MetaDisplay *display,
                 XEvent      *event)
{
  const char *name = NULL;
  char *extra = NULL;
  char *winname;
  MetaScreen *screen;
  XIEvent *input_event;

  if (!meta_is_verbose())
    return;
  
  /* filter overnumerous events */
  if (event->type == Expose || event->type == MotionNotify ||
      event->type == NoExpose)
    return;

  if (event->type == (display->damage_event_base + XDamageNotify))
    return;

  if (event->type == (display->xsync_event_base + XSyncAlarmNotify))
    return;

  if (event->type == PropertyNotify && event->xproperty.atom == display->atom__NET_WM_USER_TIME)
    return;

  input_event = get_input_event (display, event);

  if (input_event)
    meta_spew_xi2_event (display, input_event, &name, &extra);
  else
    meta_spew_core_event (display, event, &name, &extra);

  screen = meta_display_screen_for_root (display, event->xany.window);
      
  if (screen)
    winname = g_strdup_printf ("root %d", screen->number);
  else
    winname = g_strdup_printf ("0x%lx", event->xany.window);

  meta_topic (META_DEBUG_EVENTS,
              "%s on %s%s %s %sserial %lu\n", name, winname,
              extra ? ":" : "", extra ? extra : "",
              event->xany.send_event ? "SEND " : "",
              event->xany.serial);

  g_free (winname);

  if (extra)
    g_free (extra);
}
#endif /* WITH_VERBOSE_MODE */

MetaWindow*
meta_display_lookup_x_window (MetaDisplay *display,
                              Window       xwindow)
{
  return g_hash_table_lookup (display->xids, &xwindow);
}

void
meta_display_register_x_window (MetaDisplay *display,
                                Window      *xwindowp,
                                MetaWindow  *window)
{
  g_return_if_fail (g_hash_table_lookup (display->xids, xwindowp) == NULL);
  
  g_hash_table_insert (display->xids, xwindowp, window);
}

void
meta_display_unregister_x_window (MetaDisplay *display,
                                  Window       xwindow)
{
  g_return_if_fail (g_hash_table_lookup (display->xids, &xwindow) != NULL);

  g_hash_table_remove (display->xids, &xwindow);

  /* Remove any pending pings */
  remove_pending_pings_for_window (display, xwindow);
}

void
meta_display_register_wayland_window (MetaDisplay *display,
                                      MetaWindow  *window)
{
  g_hash_table_add (display->wayland_windows, window);
}

void
meta_display_unregister_wayland_window (MetaDisplay *display,
                                        MetaWindow  *window)
{
  g_hash_table_remove (display->wayland_windows, window);
}

#ifdef HAVE_XSYNC
/* We store sync alarms in the window ID hash table, because they are
 * just more types of XIDs in the same global space, but we have
 * typesafe functions to register/unregister for readability.
 */

MetaWindow*
meta_display_lookup_sync_alarm (MetaDisplay *display,
                                XSyncAlarm   alarm)
{
  return g_hash_table_lookup (display->xids, &alarm);
}

void
meta_display_register_sync_alarm (MetaDisplay *display,
                                  XSyncAlarm  *alarmp,
                                  MetaWindow  *window)
{
  g_return_if_fail (g_hash_table_lookup (display->xids, alarmp) == NULL);

  g_hash_table_insert (display->xids, alarmp, window);
}

void
meta_display_unregister_sync_alarm (MetaDisplay *display,
                                    XSyncAlarm   alarm)
{
  g_return_if_fail (g_hash_table_lookup (display->xids, &alarm) != NULL);

  g_hash_table_remove (display->xids, &alarm);
}
#endif /* HAVE_XSYNC */

void
meta_display_notify_window_created (MetaDisplay  *display,
                                    MetaWindow   *window)
{
  g_signal_emit (display, display_signals[WINDOW_CREATED], 0, window);
}

/**
 * meta_display_xwindow_is_a_no_focus_window:
 * @display: A #MetaDisplay
 * @xwindow: An X11 window
 *
 * Returns: %TRUE iff window is one of mutter's internal "no focus" windows
 * (there is one per screen) which will have the focus when there is no
 * actual client window focused.
 */
gboolean
meta_display_xwindow_is_a_no_focus_window (MetaDisplay *display,
                                           Window xwindow)
{
  gboolean is_a_no_focus_window = FALSE;
  GSList *temp = display->screens;
  while (temp != NULL) {
    MetaScreen *screen = temp->data;
    if (screen->no_focus_window == xwindow) {
      is_a_no_focus_window = TRUE;
      break;
    }
    temp = temp->next;
  }

  return is_a_no_focus_window;
}

static MetaCursor
meta_cursor_for_grab_op (MetaGrabOp op)
{
  switch (op)
    {
    case META_GRAB_OP_RESIZING_SE:
    case META_GRAB_OP_KEYBOARD_RESIZING_SE:
      return META_CURSOR_SE_RESIZE;
      break;
    case META_GRAB_OP_RESIZING_S:
    case META_GRAB_OP_KEYBOARD_RESIZING_S:
      return META_CURSOR_SOUTH_RESIZE;
      break;
    case META_GRAB_OP_RESIZING_SW:
    case META_GRAB_OP_KEYBOARD_RESIZING_SW:
      return META_CURSOR_SW_RESIZE;
      break;
    case META_GRAB_OP_RESIZING_N:
    case META_GRAB_OP_KEYBOARD_RESIZING_N:
      return META_CURSOR_NORTH_RESIZE;
      break;
    case META_GRAB_OP_RESIZING_NE:
    case META_GRAB_OP_KEYBOARD_RESIZING_NE:
      return META_CURSOR_NE_RESIZE;
      break;
    case META_GRAB_OP_RESIZING_NW:
    case META_GRAB_OP_KEYBOARD_RESIZING_NW:
      return META_CURSOR_NW_RESIZE;
      break;
    case META_GRAB_OP_RESIZING_W:
    case META_GRAB_OP_KEYBOARD_RESIZING_W:
      return META_CURSOR_WEST_RESIZE;
      break;
    case META_GRAB_OP_RESIZING_E:
    case META_GRAB_OP_KEYBOARD_RESIZING_E:
      return META_CURSOR_EAST_RESIZE;
      break;
    case META_GRAB_OP_MOVING:
    case META_GRAB_OP_KEYBOARD_MOVING:
    case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
      return META_CURSOR_MOVE_OR_RESIZE_WINDOW;
      break;
    default:
      break;
    }

  return META_CURSOR_DEFAULT;
}

void
meta_display_set_grab_op_cursor (MetaDisplay *display,
                                 MetaScreen  *screen,
                                 MetaGrabOp   op,
                                 Window       grab_xwindow,
                                 guint32      timestamp)
{
  unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 };
  XIEventMask mask = { XIAllMasterDevices, sizeof (mask_bits), mask_bits };
  MetaCursor cursor = meta_cursor_for_grab_op (op);

  XISetMask (mask.mask, XI_ButtonPress);
  XISetMask (mask.mask, XI_ButtonRelease);
  XISetMask (mask.mask, XI_Enter);
  XISetMask (mask.mask, XI_Leave);
  XISetMask (mask.mask, XI_Motion);

  g_assert (screen != NULL);

  meta_error_trap_push (display);
  if (XIGrabDevice (display->xdisplay,
                    META_VIRTUAL_CORE_POINTER_ID,
                    grab_xwindow,
                    timestamp,
                    meta_display_create_x_cursor (display, cursor),
                    XIGrabModeAsync, XIGrabModeAsync,
                    False, /* owner_events */
                    &mask) == Success)
    {
      display->grab_have_pointer = TRUE;
      meta_topic (META_DEBUG_WINDOW_OPS,
                  "XIGrabDevice() returned GrabSuccess time %u\n",
                  timestamp);
    }
  else
    {
      meta_topic (META_DEBUG_WINDOW_OPS,
                  "XIGrabDevice() failed time %u\n",
                  timestamp);
    }

  meta_error_trap_pop (display);

  meta_cursor_tracker_set_grab_cursor (screen->cursor_tracker, cursor);
}

gboolean
meta_display_begin_grab_op (MetaDisplay *display,
			    MetaScreen  *screen,
                            MetaWindow  *window,
                            MetaGrabOp   op,
                            gboolean     pointer_already_grabbed,
                            gboolean     frame_action,
                            int          button,
                            gulong       modmask,
                            guint32      timestamp,
                            int          root_x,
                            int          root_y)
{
  MetaWindow *grab_window = NULL;
  Window grab_xwindow;
  
  meta_topic (META_DEBUG_WINDOW_OPS,
              "Doing grab op %u on window %s button %d pointer already grabbed: %d pointer pos %d,%d\n",
              op, window ? window->desc : "none", button, pointer_already_grabbed,
              root_x, root_y);
  
  if (display->grab_op != META_GRAB_OP_NONE)
    {
      if (window)
        meta_warning ("Attempt to perform window operation %u on window %s when operation %u on %s already in effect\n",
                      op, window->desc, display->grab_op,
                      display->grab_window ? display->grab_window->desc : "none");
      return FALSE;
    }

  if (window &&
      (meta_grab_op_is_moving (op) || meta_grab_op_is_resizing (op)))
    {
      if (meta_prefs_get_raise_on_click ())
        meta_window_raise (window);
      else
        {
          display->grab_initial_x = root_x;
          display->grab_initial_y = root_y;
          display->grab_threshold_movement_reached = FALSE;
        }
    }

  /* If window is a modal dialog attached to its parent,
   * grab the parent instead for moving.
   */
  if (window && meta_window_is_attached_dialog (window) &&
      meta_grab_op_is_moving (op))
    grab_window = meta_window_get_transient_for (window);

  if (grab_window == NULL)
    grab_window = window;

  /* FIXME:
   *   If we have no MetaWindow we do our best
   *   and try to do the grab on the RootWindow.
   *   This will fail if anyone else has any
   *   key grab on the RootWindow.
   */
  if (grab_window)
    grab_xwindow = meta_window_get_toplevel_xwindow (grab_window);
  else
    grab_xwindow = screen->xroot;

  display->grab_have_pointer = FALSE;
  
  if (pointer_already_grabbed)
    display->grab_have_pointer = TRUE;
  
  meta_display_set_grab_op_cursor (display, screen, op, grab_xwindow, timestamp);

  if (!display->grab_have_pointer && !grab_op_is_keyboard (op))
    {
      meta_topic (META_DEBUG_WINDOW_OPS,
                  "XIGrabDevice() failed\n");
      return FALSE;
    }

  /* Grab keys for keyboard ops and mouse move/resizes; see #126497 */
  if (grab_op_is_keyboard (op) || grab_op_is_mouse_only (op))
    {
      if (grab_window)
        display->grab_have_keyboard =
                     meta_window_grab_all_keys (grab_window, timestamp);

      else
        display->grab_have_keyboard =
                     meta_screen_grab_all_keys (screen, timestamp);
      
      if (!display->grab_have_keyboard)
        {
          meta_topic (META_DEBUG_WINDOW_OPS,
                      "grabbing all keys failed, ungrabbing pointer\n");
          XIUngrabDevice (display->xdisplay, META_VIRTUAL_CORE_POINTER_ID, timestamp);
          display->grab_have_pointer = FALSE;
          return FALSE;
        }
    }
  
  display->grab_op = op;
  display->grab_window = grab_window;
  display->grab_screen = screen;
  display->grab_xwindow = grab_xwindow;
  display->grab_button = button;
  display->grab_mask = modmask;
  if (window)
    {
      display->grab_tile_mode = window->tile_mode;
      display->grab_tile_monitor_number = window->tile_monitor_number;
    }
  else
    {
      display->grab_tile_mode = META_TILE_NONE;
      display->grab_tile_monitor_number = -1;
    }
  display->grab_anchor_root_x = root_x;
  display->grab_anchor_root_y = root_y;
  display->grab_latest_motion_x = root_x;
  display->grab_latest_motion_y = root_y;
  display->grab_last_moveresize_time.tv_sec = 0;
  display->grab_last_moveresize_time.tv_usec = 0;
  display->grab_motion_notify_time = 0;
  display->grab_old_window_stacking = NULL;
#ifdef HAVE_XSYNC
  display->grab_last_user_action_was_snap = FALSE;
#endif
  display->grab_frame_action = frame_action;
  display->grab_resize_unmaximize = 0;
  display->grab_timestamp = timestamp;

  if (display->grab_resize_timeout_id)
    {
      g_source_remove (display->grab_resize_timeout_id);
      display->grab_resize_timeout_id = 0;
    }
	
  if (display->grab_window)
    {
      meta_window_get_client_root_coords (display->grab_window,
                                          &display->grab_initial_window_pos);
      display->grab_anchor_window_pos = display->grab_initial_window_pos;

#ifdef HAVE_XSYNC
      if ( meta_grab_op_is_resizing (display->grab_op) &&
           display->grab_window->sync_request_counter != None)
        {
          meta_window_create_sync_request_alarm (display->grab_window);
        }
#endif
    }
  
  meta_topic (META_DEBUG_WINDOW_OPS,
              "Grab op %u on window %s successful\n",
              display->grab_op, window ? window->desc : "(null)");

  g_assert (display->grab_window != NULL || display->grab_screen != NULL);
  g_assert (display->grab_op != META_GRAB_OP_NONE);

  /* Save the old stacking */
  if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op))
    {
      meta_topic (META_DEBUG_WINDOW_OPS,
                  "Saving old stack positions; old pointer was %p.\n",
                  display->grab_old_window_stacking);
      display->grab_old_window_stacking = 
        meta_stack_get_positions (screen->stack);
    }

  if (display->grab_window)
    {
      meta_window_refresh_resize_popup (display->grab_window);
    }

  g_signal_emit (display, display_signals[GRAB_OP_BEGIN], 0,
                 screen, display->grab_window, display->grab_op);
  
  return TRUE;
}

void
meta_display_end_grab_op (MetaDisplay *display,
                          guint32      timestamp)
{
  meta_topic (META_DEBUG_WINDOW_OPS,
              "Ending grab op %u at time %u\n", display->grab_op, timestamp);
  
  if (display->grab_op == META_GRAB_OP_NONE)
    return;

  g_signal_emit (display, display_signals[GRAB_OP_END], 0,
                 display->grab_screen, display->grab_window, display->grab_op);

  if (display->grab_window != NULL)
    display->grab_window->shaken_loose = FALSE;
  
  if (display->grab_window != NULL &&
      !meta_prefs_get_raise_on_click () &&
      (meta_grab_op_is_moving (display->grab_op) ||
       meta_grab_op_is_resizing (display->grab_op)))
    {
      /* Only raise the window in orthogonal raise
       * ('do-not-raise-on-click') mode if the user didn't try to move
       * or resize the given window by at least a threshold amount.
       * For raise on click mode, the window was raised at the
       * beginning of the grab_op.
       */
      if (!display->grab_threshold_movement_reached)
        meta_window_raise (display->grab_window);
    }

  if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op) ||
      display->grab_op == META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING)
    {
      if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op))
        meta_screen_tab_popup_destroy (display->grab_screen);
      else
        meta_screen_workspace_popup_destroy (display->grab_screen);

      /* If the ungrab here causes an EnterNotify, ignore it for
       * sloppy focus
       */
      display->ungrab_should_not_cause_focus_window = display->grab_xwindow;
    }
  
  /* If this was a move or resize clear out the edge cache */
  if (meta_grab_op_is_resizing (display->grab_op) || 
      meta_grab_op_is_moving (display->grab_op))
    {
      meta_topic (META_DEBUG_WINDOW_OPS,
                  "Clearing out the edges for resistance/snapping");
      meta_display_cleanup_edges (display);
    }

  if (display->grab_old_window_stacking != NULL)
    {
      meta_topic (META_DEBUG_WINDOW_OPS,
                  "Clearing out the old stack position, which was %p.\n",
                  display->grab_old_window_stacking);
      g_list_free (display->grab_old_window_stacking);
      display->grab_old_window_stacking = NULL;
    }

  if (display->grab_have_pointer)
    {
      meta_topic (META_DEBUG_WINDOW_OPS,
                  "Ungrabbing pointer with timestamp %u\n", timestamp);
      XIUngrabDevice (display->xdisplay, META_VIRTUAL_CORE_POINTER_ID, timestamp);
    }

  if (display->grab_have_keyboard)
    {
      meta_topic (META_DEBUG_WINDOW_OPS,
                  "Ungrabbing all keys timestamp %u\n", timestamp);
      if (display->grab_window)
        meta_window_ungrab_all_keys (display->grab_window, timestamp);
      else
        meta_screen_ungrab_all_keys (display->grab_screen, timestamp);
    }

  meta_cursor_tracker_set_grab_cursor (display->grab_screen->cursor_tracker, META_CURSOR_DEFAULT);

  display->grab_timestamp = 0;
  display->grab_window = NULL;
  display->grab_screen = NULL;
  display->grab_xwindow = None;
  display->grab_tile_mode = META_TILE_NONE;
  display->grab_tile_monitor_number = -1;
  display->grab_op = META_GRAB_OP_NONE;

  if (display->grab_resize_popup)
    {
      meta_ui_resize_popup_free (display->grab_resize_popup);
      display->grab_resize_popup = NULL;
    }

  if (display->grab_resize_timeout_id)
    {
      g_source_remove (display->grab_resize_timeout_id);
      display->grab_resize_timeout_id = 0;
    }
}

/**
 * meta_display_get_grab_op:
 * @display: The #MetaDisplay that the window is on

 * Gets the current grab operation, if any.
 *
 * Return value: the current grab operation, or %META_GRAB_OP_NONE if
 * Mutter doesn't currently have a grab. %META_GRAB_OP_COMPOSITOR will
 * be returned if a compositor-plugin modal operation is in effect
 * (See mutter_begin_modal_for_plugin())
 */
MetaGrabOp
meta_display_get_grab_op (MetaDisplay *display)
{
  return display->grab_op;
}

void
meta_display_check_threshold_reached (MetaDisplay *display,
                                      int          x,
                                      int          y)
{
  /* Don't bother doing the check again if we've already reached the threshold */
  if (meta_prefs_get_raise_on_click () ||
      display->grab_threshold_movement_reached)
    return;

  if (ABS (display->grab_initial_x - x) >= 8 ||
      ABS (display->grab_initial_y - y) >= 8)
    display->grab_threshold_movement_reached = TRUE;
}

static void
meta_change_button_grab (MetaDisplay *display,
                         Window       xwindow,
                         gboolean     grab,
                         gboolean     sync,
                         int          button,
                         int          modmask)
{
  unsigned int ignored_mask;

  unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 };
  XIEventMask mask = { XIAllMasterDevices, sizeof (mask_bits), mask_bits };

  XISetMask (mask.mask, XI_ButtonPress);
  XISetMask (mask.mask, XI_ButtonRelease);
  XISetMask (mask.mask, XI_Motion);

  meta_verbose ("%s 0x%lx sync = %d button = %d modmask 0x%x\n",
                grab ? "Grabbing" : "Ungrabbing",
                xwindow,
                sync, button, modmask);
  
  meta_error_trap_push (display);
  
  ignored_mask = 0;
  while (ignored_mask <= display->ignored_modifier_mask)
    {
      XIGrabModifiers mods;

      if (ignored_mask & ~(display->ignored_modifier_mask))
        {
          /* Not a combination of ignored modifiers
           * (it contains some non-ignored modifiers)
           */
          ++ignored_mask;
          continue;
        }

      mods = (XIGrabModifiers) { modmask | ignored_mask, 0 };

      if (meta_is_debugging ())
        meta_error_trap_push_with_return (display);

      /* GrabModeSync means freeze until XAllowEvents */
      
      if (grab)
        XIGrabButton (display->xdisplay,
                      META_VIRTUAL_CORE_POINTER_ID,
                      button, xwindow, None,
                      sync ? XIGrabModeSync : XIGrabModeAsync,
                      XIGrabModeAsync, False,
                      &mask, 1, &mods);
      else
        XIUngrabButton (display->xdisplay,
                        META_VIRTUAL_CORE_POINTER_ID,
                        button, xwindow, 1, &mods);

      if (meta_is_debugging ())
        {
          int result;
          
          result = meta_error_trap_pop_with_return (display);
          
          if (result != Success)
            meta_verbose ("Failed to %s button %d with mask 0x%x for window 0x%lx error code %d\n",
                          grab ? "grab" : "ungrab",
                          button, modmask | ignored_mask, xwindow, result);
        }
      
      ++ignored_mask;
    }

  meta_error_trap_pop (display);
}

void
meta_display_grab_window_buttons (MetaDisplay *display,
                                  Window       xwindow)
{  
  /* Grab Alt + button1 for moving window.
   * Grab Alt + button2 for resizing window.
   * Grab Alt + button3 for popping up window menu.
   * Grab Alt + Shift + button1 for snap-moving window.
   */
  meta_verbose ("Grabbing window buttons for 0x%lx\n", xwindow);
  
  /* FIXME If we ignored errors here instead of spewing, we could
   * put one big error trap around the loop and avoid a bunch of
   * XSync()
   */

  if (display->window_grab_modifiers != 0)
    {
      gboolean debug = g_getenv ("MUTTER_DEBUG_BUTTON_GRABS") != NULL;
      int i;
      for (i = 1; i < 4; i++)
        {
          meta_change_button_grab (display, xwindow,
                                   TRUE,
                                   FALSE,
                                   i, display->window_grab_modifiers);  
          
          /* This is for debugging, since I end up moving the Xnest
           * otherwise ;-)
           */
          if (debug)
            meta_change_button_grab (display, xwindow,
                                     TRUE,
                                     FALSE,
                                     i, ControlMask);
        }

      /* In addition to grabbing Alt+Button1 for moving the window,
       * grab Alt+Shift+Button1 for snap-moving the window.  See bug
       * 112478.  Unfortunately, this doesn't work with
       * Shift+Alt+Button1 for some reason; so at least part of the
       * order still matters, which sucks (please FIXME).
       */
      meta_change_button_grab (display, xwindow,
                               TRUE,
                               FALSE,
                               1, display->window_grab_modifiers | ShiftMask);
    }
}

void
meta_display_ungrab_window_buttons  (MetaDisplay *display,
                                     Window       xwindow)
{
  gboolean debug;
  int i;

  if (display->window_grab_modifiers == 0)
    return;
  
  debug = g_getenv ("MUTTER_DEBUG_BUTTON_GRABS") != NULL;
  i = 1;
  while (i < 4)
    {
      meta_change_button_grab (display, xwindow,
                               FALSE, FALSE, i,
                               display->window_grab_modifiers);
      
      if (debug)
        meta_change_button_grab (display, xwindow,
                                 FALSE, FALSE, i, ControlMask);
      
      ++i;
    }
}

/* Grab buttons we only grab while unfocused in click-to-focus mode */
#define MAX_FOCUS_BUTTON 4
void
meta_display_grab_focus_window_button (MetaDisplay *display,
                                       MetaWindow  *window)
{
  /* Grab button 1 for activating unfocused windows */
  meta_verbose ("Grabbing unfocused window buttons for %s\n", window->desc);

#if 0
  /* FIXME:115072 */
  /* Don't grab at all unless in click to focus mode. In click to
   * focus, we may sometimes be clever about intercepting and eating
   * the focus click. But in mouse focus, we never do that since the
   * focus window may not be raised, and who wants to think about
   * mouse focus anyway.
   */
  if (meta_prefs_get_focus_mode () != G_DESKTOP_FOCUS_MODE_CLICK)
    {
      meta_verbose (" (well, not grabbing since not in click to focus mode)\n");
      return;
    }
#endif
  
  if (window->have_focus_click_grab)
    {
      meta_verbose (" (well, not grabbing since we already have the grab)\n");
      return;
    }
  
  /* FIXME If we ignored errors here instead of spewing, we could
   * put one big error trap around the loop and avoid a bunch of
   * XSync()
   */
  
  {
    int i = 1;
    while (i < MAX_FOCUS_BUTTON)
      {
        meta_change_button_grab (display,
                                 window->xwindow,
                                 TRUE, TRUE,
                                 i, 0);
        
        ++i;
      }

    window->have_focus_click_grab = TRUE;
  }
}

void
meta_display_ungrab_focus_window_button (MetaDisplay *display,
                                         MetaWindow  *window)
{
  meta_verbose ("Ungrabbing unfocused window buttons for %s\n", window->desc);

  if (!window->have_focus_click_grab)
    return;
  
  {
    int i = 1;
    while (i < MAX_FOCUS_BUTTON)
      {
        meta_change_button_grab (display, window->xwindow,
                                 FALSE, FALSE, i, 0);
        
        ++i;
      }

    window->have_focus_click_grab = FALSE;
  }
}

void
meta_display_increment_event_serial (MetaDisplay *display)
{
  /* We just make some random X request */
  XDeleteProperty (display->xdisplay, display->leader_window,
                   display->atom__MOTIF_WM_HINTS);
}

void
meta_display_update_active_window_hint (MetaDisplay *display)
{
  GSList *tmp;
  
  gulong data[1];

  if (display->focus_window)
    data[0] = display->focus_window->xwindow;
  else
    data[0] = None;
  
  tmp = display->screens;
  while (tmp != NULL)
    {
      MetaScreen *screen = tmp->data;
      
      meta_error_trap_push (display);
      XChangeProperty (display->xdisplay, screen->xroot,
                       display->atom__NET_ACTIVE_WINDOW,
                       XA_WINDOW,
                       32, PropModeReplace, (guchar*) data, 1);

      meta_error_trap_pop (display);

      tmp = tmp->next;
    }
}

void
meta_display_queue_retheme_all_windows (MetaDisplay *display)
{
  GSList* windows;
  GSList *tmp;

  windows = meta_display_list_windows (display, META_LIST_DEFAULT);
  tmp = windows;
  while (tmp != NULL)
    {
      MetaWindow *window = tmp->data;
      
      meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
      meta_window_frame_size_changed (window);
      if (window->frame)
        {
          meta_frame_queue_draw (window->frame);
        }
      
      tmp = tmp->next;
    }

  g_slist_free (windows);
}

void
meta_display_retheme_all (void)
{
  meta_display_queue_retheme_all_windows (meta_get_display ());
}

void 
meta_display_set_cursor_theme (const char *theme, 
			       int         size)
{
  GSList *tmp;

  MetaDisplay *display = meta_get_display ();

  XcursorSetTheme (display->xdisplay, theme);
  XcursorSetDefaultSize (display->xdisplay, size);

  tmp = display->screens;
  while (tmp != NULL)
    {
      MetaScreen *screen = tmp->data;
	  	  
      meta_screen_update_cursor (screen);

      tmp = tmp->next;
    }
}

/*
 * Stores whether syncing is currently enabled.
 */
static gboolean is_syncing = FALSE;

/**
 * meta_is_syncing:
 *
 * Returns whether X synchronisation is currently enabled.
 *
 * FIXME: This is *only* called by meta_display_open(), but by that time
 * we have already turned syncing on or off on startup, and we don't
 * have any way to do so while Mutter is running, so it's rather
 * pointless.
 *
 * Returns: %TRUE if we must wait for events whenever we send X requests;
 * %FALSE otherwise.
 */
gboolean
meta_is_syncing (void)
{
  return is_syncing;
}

/**
 * meta_set_syncing:
 * @setting: whether to turn syncing on or off
 *
 * A handy way to turn on synchronisation on or off for every display.
 */
void
meta_set_syncing (gboolean setting)
{
  if (setting != is_syncing)
    {
      is_syncing = setting;
      if (meta_get_display ())
        XSynchronize (meta_get_display ()->xdisplay, is_syncing);
    }
}

/*
 * How long, in milliseconds, we should wait after pinging a window
 * before deciding it's not going to get back to us.
 */
#define PING_TIMEOUT_DELAY 5000

/**
 * meta_display_ping_timeout:
 * @data: All the information about this ping. It is a #MetaPingData
 *        cast to a #gpointer in order to be passable to a timeout function.
 *        This function will also free this parameter.
 *
 * Does whatever it is we decided to do when a window didn't respond
 * to a ping. We also remove the ping from the display's list of
 * pending pings. This function is called by the event loop when the timeout
 * times out which we created at the start of the ping.
 *
 * Returns: Always returns %FALSE, because this function is called as a
 *          timeout and we don't want to run the timer again.
 */
static gboolean
meta_display_ping_timeout (gpointer data)
{
  MetaPingData *ping_data = data;
  MetaDisplay *display = ping_data->window->display;

  ping_data->ping_timeout_id = 0;

  meta_topic (META_DEBUG_PING,
              "Ping %u on window %s timed out\n",
              ping_data->timestamp, ping_data->window->desc);

  (* ping_data->ping_timeout_func) (ping_data->window, ping_data->timestamp, ping_data->user_data);

  display->pending_pings = g_slist_remove (display->pending_pings, ping_data);
  ping_data_free (ping_data);

  return FALSE;
}

/**
 * meta_display_ping_window:
 * @display: The #MetaDisplay that the window is on
 * @window: The #MetaWindow to send the ping to
 * @timestamp: The timestamp of the ping. Used for uniqueness.
 *             Cannot be CurrentTime; use a real timestamp!
 * @ping_reply_func: The callback to call if we get a response.
 * @ping_timeout_func: The callback to call if we don't get a response.
 * @user_data: Arbitrary data that will be passed to the callback
 *             function. (In practice it's often a pointer to
 *             the window.)
 *
 * Sends a ping request to a window. The window must respond to
 * the request within a certain amount of time. If it does, we
 * will call one callback; if the time passes and we haven't had
 * a response, we call a different callback. The window must have
 * the hint showing that it can respond to a ping; if it doesn't,
 * we call the "got a response" callback immediately and return.
 * This function returns straight away after setting things up;
 * the callbacks will be called from the event loop.
 */
void
meta_display_ping_window (MetaWindow        *window,
			  guint32            timestamp,
			  MetaWindowPingFunc ping_reply_func,
			  MetaWindowPingFunc ping_timeout_func,
			  gpointer           user_data)
{
  MetaDisplay *display = window->display;
  MetaPingData *ping_data;

  if (timestamp == CurrentTime)
    {
      meta_warning ("Tried to ping a window with CurrentTime! Not allowed.\n");
      return;
    }

  if (!window->can_ping)
    {
      if (ping_reply_func)
        (* ping_reply_func) (window, timestamp, user_data);

      return;
    }

  ping_data = g_new (MetaPingData, 1);
  ping_data->window = window;
  ping_data->timestamp = timestamp;
  ping_data->ping_reply_func = ping_reply_func;
  ping_data->ping_timeout_func = ping_timeout_func;
  ping_data->user_data = user_data;
  ping_data->ping_timeout_id = g_timeout_add (PING_TIMEOUT_DELAY,
					      meta_display_ping_timeout,
					      ping_data);

  display->pending_pings = g_slist_prepend (display->pending_pings, ping_data);

  meta_topic (META_DEBUG_PING,
              "Sending ping with timestamp %u to window %s\n",
              timestamp, window->desc);

  if (window->client_type == META_WINDOW_CLIENT_TYPE_X11)
    meta_window_send_icccm_message (window,
                                    display->atom__NET_WM_PING,
                                    timestamp);
  else
    meta_wayland_surface_ping (window->surface, timestamp);
}

static void
process_request_frame_extents (MetaDisplay    *display,
                               XEvent         *event)
{
  /* The X window whose frame extents will be set. */
  Window xwindow = event->xclient.window;
  unsigned long data[4] = { 0, 0, 0, 0 };

  MotifWmHints *hints = NULL;
  gboolean hints_set = FALSE;

  meta_verbose ("Setting frame extents for 0x%lx\n", xwindow);

  /* See if the window is decorated. */
  hints_set = meta_prop_get_motif_hints (display,
                                         xwindow,
                                         display->atom__MOTIF_WM_HINTS,
                                         &hints);
  if ((hints_set && hints->decorations) || !hints_set)
    {
      MetaFrameBorders borders;
      MetaScreen *screen;

      screen = meta_display_screen_for_xwindow (display,
                                                event->xclient.window);
      if (screen == NULL)
        {
          meta_warning ("Received request to set _NET_FRAME_EXTENTS "
                        "on 0x%lx which is on a screen we are not managing\n",
                        event->xclient.window);
          meta_XFree (hints);
          return;
        }

      /* Return estimated frame extents for a normal window. */
      meta_ui_theme_get_frame_borders (screen->ui,
                                       META_FRAME_TYPE_NORMAL,
                                       0,
                                       &borders);
      data[0] = borders.visible.left;
      data[1] = borders.visible.right;
      data[2] = borders.visible.top;
      data[3] = borders.visible.bottom;
    }

  meta_topic (META_DEBUG_GEOMETRY,
              "Setting _NET_FRAME_EXTENTS on unmanaged window 0x%lx "
              "to top = %lu, left = %lu, bottom = %lu, right = %lu\n",
              xwindow, data[0], data[1], data[2], data[3]);

  meta_error_trap_push (display);
  XChangeProperty (display->xdisplay, xwindow,
                   display->atom__NET_FRAME_EXTENTS,
                   XA_CARDINAL,
                   32, PropModeReplace, (guchar*) data, 4);
  meta_error_trap_pop (display);

  meta_XFree (hints);
}

/**
 * meta_display_pong_for_serial:
 * @display: the display we got the pong from
 * @serial: the serial in the pong repsonse
 *
 * Process the pong (the response message) from the ping we sent
 * to the window. This involves removing the timeout, calling the
 * reply handler function, and freeing memory.
 */
void
meta_display_pong_for_serial (MetaDisplay    *display,
                              guint32         serial)
{
  GSList *tmp;

  meta_topic (META_DEBUG_PING, "Received a pong with serial %u\n", serial);

  for (tmp = display->pending_pings; tmp; tmp = tmp->next)
    {
      MetaPingData *ping_data = tmp->data;

      if (serial == ping_data->timestamp)
        {
          meta_topic (META_DEBUG_PING,
                      "Matching ping found for pong %u\n",
                      ping_data->timestamp);

          /* Remove the ping data from the list */
          display->pending_pings = g_slist_remove (display->pending_pings,
                                                   ping_data);

          /* Remove the timeout */
          if (ping_data->ping_timeout_id != 0)
            {
              g_source_remove (ping_data->ping_timeout_id);
              ping_data->ping_timeout_id = 0;
            }

          /* Call callback */
          (* ping_data->ping_reply_func) (ping_data->window,
                                          ping_data->timestamp,
                                          ping_data->user_data);

          ping_data_free (ping_data);

          break;
        }
    }
}

MetaGroup*
get_focussed_group (MetaDisplay *display)
{
  if (display->focus_window)
    return display->focus_window->group;
  else
    return NULL;
}

#define IN_TAB_CHAIN(w,t) (((t) == META_TAB_LIST_NORMAL && META_WINDOW_IN_NORMAL_TAB_CHAIN (w)) \
    || ((t) == META_TAB_LIST_DOCKS && META_WINDOW_IN_DOCK_TAB_CHAIN (w)) \
    || ((t) == META_TAB_LIST_GROUP && META_WINDOW_IN_GROUP_TAB_CHAIN (w, get_focussed_group(w->display))) \
    || ((t) == META_TAB_LIST_NORMAL_ALL && META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE (w)))

static MetaWindow*
find_tab_forward (MetaDisplay   *display,
                  MetaTabList    type,
                  MetaScreen    *screen, 
                  MetaWorkspace *workspace,
                  GList         *start,
                  gboolean       skip_first)
{
  GList *tmp;

  g_return_val_if_fail (start != NULL, NULL);
  g_return_val_if_fail (workspace != NULL, NULL);

  tmp = start;
  if (skip_first)
    tmp = tmp->next;

  while (tmp != NULL)
    {
      MetaWindow *window = tmp->data;

      if (window->screen == screen &&
	  IN_TAB_CHAIN (window, type))
        return window;

      tmp = tmp->next;
    }

  tmp = workspace->mru_list;
  while (tmp != start)
    {
      MetaWindow *window = tmp->data;

      if (IN_TAB_CHAIN (window, type))
        return window;

      tmp = tmp->next;
    }  

  return NULL;
}

static MetaWindow*
find_tab_backward (MetaDisplay   *display,
                   MetaTabList    type,
                   MetaScreen    *screen, 
                   MetaWorkspace *workspace,
                   GList         *start,
                   gboolean       skip_last)
{
  GList *tmp;

  g_return_val_if_fail (start != NULL, NULL);
  g_return_val_if_fail (workspace != NULL, NULL);

  tmp = start;
  if (skip_last)  
    tmp = tmp->prev;
  while (tmp != NULL)
    {
      MetaWindow *window = tmp->data;

      if (window->screen == screen &&
	  IN_TAB_CHAIN (window, type))
        return window;

      tmp = tmp->prev;
    }

  tmp = g_list_last (workspace->mru_list);
  while (tmp != start)
    {
      MetaWindow *window = tmp->data;

      if (IN_TAB_CHAIN (window, type))
        return window;

      tmp = tmp->prev;
    }

  return NULL;
}

static int
mru_cmp (gconstpointer a,
         gconstpointer b)
{
  guint32 time_a, time_b;

  time_a = meta_window_get_user_time ((MetaWindow *)a);
  time_b = meta_window_get_user_time ((MetaWindow *)b);

  if (time_a > time_b)
    return -1;
  else if (time_a < time_b)
    return 1;
  else
    return 0;
}

/**
 * meta_display_get_tab_list:
 * @display: a #MetaDisplay
 * @type: type of tab list
 * @screen: a #MetaScreen
 * @workspace: (allow-none): origin workspace
 *
 * Determine the list of windows that should be displayed for Alt-TAB
 * functionality.  The windows are returned in most recently used order.
 * If @workspace is not %NULL, the list only conains windows that are on
 * @workspace or have the demands-attention hint set; otherwise it contains
 * all windows on @screen.
 *
 * Returns: (transfer container) (element-type Meta.Window): List of windows
 */
GList*
meta_display_get_tab_list (MetaDisplay   *display,
                           MetaTabList    type,
                           MetaScreen    *screen,
                           MetaWorkspace *workspace)
{
  GList *tab_list = NULL;
  GList *global_mru_list = NULL;
  GList *mru_list, *tmp;
  GSList *windows = meta_display_list_windows (display, META_LIST_DEFAULT);
  GSList *w;

  if (workspace == NULL)
    {
      /* Yay for mixing GList and GSList in the API */
      for (w = windows; w; w = w->next)
        global_mru_list = g_list_prepend (global_mru_list, w->data);
      global_mru_list = g_list_sort (global_mru_list, mru_cmp);
    }

  mru_list = workspace ? workspace->mru_list : global_mru_list;

  /* Windows sellout mode - MRU order.
   */
  for (tmp = mru_list; tmp; tmp = tmp->next)
    {
      MetaWindow *window = tmp->data;

      if (window->screen == screen &&
          IN_TAB_CHAIN (window, type))
        tab_list = g_list_prepend (tab_list, window);
    }

  tab_list = g_list_reverse (tab_list);

  /* If filtering by workspace, include windows from
   * other workspaces that demand attention
   */
  if (workspace)
    for (w = windows; w; w = w->next)
      {
        MetaWindow *l_window = w->data;

        if (l_window->wm_state_demands_attention &&
            l_window->workspace != workspace &&
            IN_TAB_CHAIN (l_window, type))
          tab_list = g_list_prepend (tab_list, l_window);
      }

  g_list_free (global_mru_list);
  g_slist_free (windows);
  
  return tab_list;
}

/**
 * meta_display_get_tab_next:
 * @display: a #MetaDisplay
 * @type: type of tab list
 * @screen: a #MetaScreen
 * @workspace: origin workspace
 * @window: (allow-none): starting window 
 * @backward: If %TRUE, look for the previous window.  
 *
 * Determine the next window that should be displayed for Alt-TAB
 * functionality.
 *
 * Returns: (transfer none): Next window
 *
 */
MetaWindow*
meta_display_get_tab_next (MetaDisplay   *display,
                           MetaTabList    type,
                           MetaScreen    *screen,
                           MetaWorkspace *workspace,
                           MetaWindow    *window,
                           gboolean       backward)
{
  gboolean skip;
  GList *tab_list;
  MetaWindow *ret;
  tab_list = meta_display_get_tab_list(display,
                                       type,
                                       screen,
                                       workspace);

  if (tab_list == NULL)
    return NULL;
  
  if (window != NULL)
    {
      g_assert (window->display == display);
      
      if (backward)
        ret = find_tab_backward (display, type, screen, workspace,
                                 g_list_find (tab_list,
                                              window),
                                 TRUE);
      else
        ret = find_tab_forward (display, type, screen, workspace,
                                g_list_find (tab_list,
                                             window),
                                TRUE);
    }
  else
    {
      skip = display->focus_window != NULL && 
             tab_list->data == display->focus_window;
      if (backward)
        ret = find_tab_backward (display, type, screen, workspace,
                                 tab_list, skip);
      else
        ret = find_tab_forward (display, type, screen, workspace,
                                tab_list, skip);
    }

  g_list_free (tab_list);
  return ret;
}

/**
 * meta_display_get_tab_current:
 * @display: a #MetaDisplay
 * @type: type of tab list
 * @screen: a #MetaScreen
 * @workspace: origin workspace
 *
 * Determine the active window that should be displayed for Alt-TAB.
 *
 * Returns: (transfer none): Current window
 *
 */
MetaWindow*
meta_display_get_tab_current (MetaDisplay   *display,
                              MetaTabList    type,
                              MetaScreen    *screen,
                              MetaWorkspace *workspace)
{
  MetaWindow *window;

  window = display->focus_window;
  
  if (window != NULL &&
      window->screen == screen &&
      IN_TAB_CHAIN (window, type) &&
      (workspace == NULL ||
       meta_window_located_on_workspace (window, workspace)))
    return window;
  else
    return NULL;
}

int
meta_resize_gravity_from_grab_op (MetaGrabOp op)
{
  int gravity;
  
  gravity = -1;
  switch (op)
    {
    case META_GRAB_OP_RESIZING_SE:
    case META_GRAB_OP_KEYBOARD_RESIZING_SE:
      gravity = NorthWestGravity;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_S:
    case META_GRAB_OP_RESIZING_S:
      gravity = NorthGravity;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_SW:
    case META_GRAB_OP_RESIZING_SW:
      gravity = NorthEastGravity;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_N:
    case META_GRAB_OP_RESIZING_N:
      gravity = SouthGravity;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_NE:
    case META_GRAB_OP_RESIZING_NE:
      gravity = SouthWestGravity;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_NW:
    case META_GRAB_OP_RESIZING_NW:
      gravity = SouthEastGravity;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_E:
    case META_GRAB_OP_RESIZING_E:
      gravity = WestGravity;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_W:
    case META_GRAB_OP_RESIZING_W:
      gravity = EastGravity;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
      gravity = CenterGravity;
      break;
    default:
      break;
    }

  return gravity;
}

static MetaScreen*
find_screen_for_selection (MetaDisplay *display,
                           Window       owner,
                           Atom         selection)
{  
  GSList *tmp;  
  
  tmp = display->screens;
  while (tmp != NULL)
    {
      MetaScreen *screen = tmp->data;
      
      if (screen->wm_sn_selection_window == owner &&
          screen->wm_sn_atom == selection)
        return screen;
  
      tmp = tmp->next;
    }

  return NULL;
}

/* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */
static gboolean
convert_property (MetaDisplay *display,
                  MetaScreen  *screen,
                  Window       w,
                  Atom         target,
                  Atom         property)
{
#define N_TARGETS 4
  Atom conversion_targets[N_TARGETS];
  long icccm_version[] = { 2, 0 };

  conversion_targets[0] = display->atom_TARGETS;
  conversion_targets[1] = display->atom_MULTIPLE;
  conversion_targets[2] = display->atom_TIMESTAMP;
  conversion_targets[3] = display->atom_VERSION;

  meta_error_trap_push_with_return (display);
  if (target == display->atom_TARGETS)
    XChangeProperty (display->xdisplay, w, property,
		     XA_ATOM, 32, PropModeReplace,
		     (unsigned char *)conversion_targets, N_TARGETS);
  else if (target == display->atom_TIMESTAMP)
    XChangeProperty (display->xdisplay, w, property,
		     XA_INTEGER, 32, PropModeReplace,
		     (unsigned char *)&screen->wm_sn_timestamp, 1);
  else if (target == display->atom_VERSION)
    XChangeProperty (display->xdisplay, w, property,
		     XA_INTEGER, 32, PropModeReplace,
		     (unsigned char *)icccm_version, 2);
  else
    {
      meta_error_trap_pop_with_return (display);
      return FALSE;
    }
  
  if (meta_error_trap_pop_with_return (display) != Success)
    return FALSE;

  /* Be sure the PropertyNotify has arrived so we
   * can send SelectionNotify
   */
  /* FIXME the error trap pop synced anyway, right? */
  meta_topic (META_DEBUG_SYNC, "Syncing on %s\n", G_STRFUNC);
  XSync (display->xdisplay, False);

  return TRUE;
}

/* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */
static void
process_selection_request (MetaDisplay   *display,
                           XEvent        *event)
{
  XSelectionEvent reply;
  MetaScreen *screen;

  screen = find_screen_for_selection (display,
                                      event->xselectionrequest.owner,
                                      event->xselectionrequest.selection);

  if (screen == NULL)
    {
      char *str;
      
      meta_error_trap_push (display);
      str = XGetAtomName (display->xdisplay,
                          event->xselectionrequest.selection);
      meta_error_trap_pop (display);
      
      meta_verbose ("Selection request with selection %s window 0x%lx not a WM_Sn selection we recognize\n",
                    str ? str : "(bad atom)", event->xselectionrequest.owner);
      
      meta_XFree (str);

      return;
    }
  
  reply.type = SelectionNotify;
  reply.display = display->xdisplay;
  reply.requestor = event->xselectionrequest.requestor;
  reply.selection = event->xselectionrequest.selection;
  reply.target = event->xselectionrequest.target;
  reply.property = None;
  reply.time = event->xselectionrequest.time;

  if (event->xselectionrequest.target == display->atom_MULTIPLE)
    {
      if (event->xselectionrequest.property != None)
        {
          Atom type, *adata;
          int i, format;
          unsigned long num, rest;
          unsigned char *data;

          meta_error_trap_push_with_return (display);
          if (XGetWindowProperty (display->xdisplay,
                                  event->xselectionrequest.requestor,
                                  event->xselectionrequest.property, 0, 256, False,
                                  display->atom_ATOM_PAIR,
                                  &type, &format, &num, &rest, &data) != Success)
            {
              meta_error_trap_pop_with_return (display);
              return;
            }
          
          if (meta_error_trap_pop_with_return (display) == Success)
            {              
              /* FIXME: to be 100% correct, should deal with rest > 0,
               * but since we have 4 possible targets, we will hardly ever
               * meet multiple requests with a length > 8
               */
              adata = (Atom*)data;
              i = 0;
              while (i < (int) num)
                {
                  if (!convert_property (display, screen,
                                         event->xselectionrequest.requestor,
                                         adata[i], adata[i+1]))
                    adata[i+1] = None;
                  i += 2;
                }

              meta_error_trap_push (display);
              XChangeProperty (display->xdisplay,
                               event->xselectionrequest.requestor,
                               event->xselectionrequest.property,
                               display->atom_ATOM_PAIR,
                               32, PropModeReplace, data, num);
              meta_error_trap_pop (display);
              meta_XFree (data);
            }
        }
    }
  else
    {
      if (event->xselectionrequest.property == None)
        event->xselectionrequest.property = event->xselectionrequest.target;
      
      if (convert_property (display, screen,
                            event->xselectionrequest.requestor,
                            event->xselectionrequest.target,
                            event->xselectionrequest.property))
        reply.property = event->xselectionrequest.property;
    }

  XSendEvent (display->xdisplay,
              event->xselectionrequest.requestor,
              False, 0L, (XEvent*)&reply);

  meta_verbose ("Handled selection request\n");
}

static void
process_selection_clear (MetaDisplay   *display,
                         XEvent        *event)
{
  /* We need to unmanage the screen on which we lost the selection */
  MetaScreen *screen;

  screen = find_screen_for_selection (display,
                                      event->xselectionclear.window,
                                      event->xselectionclear.selection);
  

  if (screen != NULL)
    {
      meta_verbose ("Got selection clear for screen %d on display %s\n",
                    screen->number, display->name);
      
      meta_display_unmanage_screen (display, 
                                    screen,
                                    event->xselectionclear.time);

      /* display and screen may both be invalid memory... */
      
      return;
    }

  {
    char *str;
            
    meta_error_trap_push (display);
    str = XGetAtomName (display->xdisplay,
                        event->xselectionclear.selection);
    meta_error_trap_pop (display);

    meta_verbose ("Selection clear with selection %s window 0x%lx not a WM_Sn selection we recognize\n",
                  str ? str : "(bad atom)", event->xselectionclear.window);

    meta_XFree (str);
  }
}

void
meta_display_unmanage_screen (MetaDisplay *display,
                              MetaScreen  *screen,
                              guint32      timestamp)
{
  meta_verbose ("Unmanaging screen %d on display %s\n",
                screen->number, display->name);
  
  g_return_if_fail (g_slist_find (display->screens, screen) != NULL);
  
  meta_screen_free (screen, timestamp);
  display->screens = g_slist_remove (display->screens, screen);

  if (display->screens == NULL)
    meta_display_close (display, timestamp);
}

void
meta_display_unmanage_windows_for_screen (MetaDisplay *display,
                                          MetaScreen  *screen,
                                          guint32      timestamp)
{
  GSList *tmp;
  GSList *winlist;

  winlist = meta_display_list_windows (display,
                                       META_LIST_INCLUDE_OVERRIDE_REDIRECT);
  winlist = g_slist_sort (winlist, meta_display_stack_cmp);
  g_slist_foreach (winlist, (GFunc)g_object_ref, NULL);

  /* Unmanage all windows */
  tmp = winlist;
  while (tmp != NULL)
    {
      MetaWindow *window = tmp->data;

      /* Check if already unmanaged for safety - in particular, catch
       * the case where unmanaging a parent window can cause attached
       * dialogs to be (temporarily) unmanaged.
       */
      if (!window->unmanaging)
        meta_window_unmanage (window, timestamp);
      g_object_unref (window);
      
      tmp = tmp->next;
    }
  g_slist_free (winlist);
}

int
meta_display_stack_cmp (const void *a,
                        const void *b)
{
  MetaWindow *aw = (void*) a;
  MetaWindow *bw = (void*) b;

  if (aw->screen == bw->screen)
    return meta_stack_windows_cmp (aw->screen->stack, aw, bw);
  /* Then assume screens are stacked by number */
  else if (aw->screen->number < bw->screen->number)
    return -1;
  else if (aw->screen->number > bw->screen->number)
    return 1;
  else
    return 0; /* not reached in theory, if windows on same display */
}

/**
 * meta_display_sort_windows_by_stacking:
 * @display: a #MetaDisplay
 * @windows: (element-type MetaWindow): Set of windows
 *
 * Sorts a set of windows according to their current stacking order. If windows
 * from multiple screens are present in the set of input windows, then all the
 * windows on screen 0 are sorted below all the windows on screen 1, and so forth.
 * Since the stacking order of override-redirect windows isn't controlled by
 * Metacity, if override-redirect windows are in the input, the result may not
 * correspond to the actual stacking order in the X server.
 *
 * An example of using this would be to sort the list of transient dialogs for a
 * window into their current stacking order.
 *
 * Returns: (transfer container) (element-type MetaWindow): Input windows sorted by stacking order, from lowest to highest
 */
GSList *
meta_display_sort_windows_by_stacking (MetaDisplay *display,
                                       GSList      *windows)
{
  GSList *copy = g_slist_copy (windows);

  copy = g_slist_sort (copy, meta_display_stack_cmp);

  return copy;
}

void
meta_display_devirtualize_modifiers (MetaDisplay        *display,
                                     MetaVirtualModifier modifiers,
                                     unsigned int       *mask)
{
  *mask = 0;
  
  if (modifiers & META_VIRTUAL_SHIFT_MASK)
    *mask |= ShiftMask;
  if (modifiers & META_VIRTUAL_CONTROL_MASK)
    *mask |= ControlMask;
  if (modifiers & META_VIRTUAL_ALT_MASK)
    *mask |= Mod1Mask;
  if (modifiers & META_VIRTUAL_META_MASK)
    *mask |= display->meta_mask;
  if (modifiers & META_VIRTUAL_HYPER_MASK)
    *mask |= display->hyper_mask;
  if (modifiers & META_VIRTUAL_SUPER_MASK)
    *mask |= display->super_mask;
  if (modifiers & META_VIRTUAL_MOD2_MASK)
    *mask |= Mod2Mask;
  if (modifiers & META_VIRTUAL_MOD3_MASK)
    *mask |= Mod3Mask;
  if (modifiers & META_VIRTUAL_MOD4_MASK)
    *mask |= Mod4Mask;
  if (modifiers & META_VIRTUAL_MOD5_MASK)
    *mask |= Mod5Mask;  
}

static void
update_window_grab_modifiers (MetaDisplay *display)
     
{
  MetaVirtualModifier virtual_mods;
  unsigned int mods;
    
  virtual_mods = meta_prefs_get_mouse_button_mods ();
  meta_display_devirtualize_modifiers (display, virtual_mods,
                                       &mods);
    
  display->window_grab_modifiers = mods;
}

static void
prefs_changed_callback (MetaPreference pref,
                        void          *data)
{
  MetaDisplay *display = data;
  
  /* It may not be obvious why we regrab on focus mode
   * change; it's because we handle focus clicks a
   * bit differently for the different focus modes.
   */
  if (pref == META_PREF_MOUSE_BUTTON_MODS ||
      pref == META_PREF_FOCUS_MODE)
    {
      MetaDisplay *display = data;
      GSList *windows;
      GSList *tmp;
      
      windows = meta_display_list_windows (display, META_LIST_DEFAULT);
      
      /* Ungrab all */
      tmp = windows;
      while (tmp != NULL)
        {
          MetaWindow *w = tmp->data;
          meta_display_ungrab_window_buttons (display, w->xwindow);
          meta_display_ungrab_focus_window_button (display, w);
          tmp = tmp->next;
        }

      /* change our modifier */
      if (pref == META_PREF_MOUSE_BUTTON_MODS)
        update_window_grab_modifiers (display);

      /* Grab all */
      tmp = windows;
      while (tmp != NULL)
        {
          MetaWindow *w = tmp->data;
          if (w->type != META_WINDOW_DOCK)
            {
              meta_display_grab_focus_window_button (display, w);
              meta_display_grab_window_buttons (display, w->xwindow);
            }
          tmp = tmp->next;
        }

      g_slist_free (windows);
    }
  else if (pref == META_PREF_AUDIBLE_BELL)
    {
      meta_bell_set_audible (display, meta_prefs_bell_is_audible ());
    }
}

void
meta_display_increment_focus_sentinel (MetaDisplay *display)
{
  unsigned long data[1];

  data[0] = meta_display_get_current_time (display);
  
  XChangeProperty (display->xdisplay,
                   ((MetaScreen*) display->screens->data)->xroot,
                   display->atom__MUTTER_SENTINEL,
                   XA_CARDINAL,
                   32, PropModeReplace, (guchar*) data, 1);
  
  display->sentinel_counter += 1;
}

void
meta_display_decrement_focus_sentinel (MetaDisplay *display)
{
  display->sentinel_counter -= 1;

  if (display->sentinel_counter < 0)
    display->sentinel_counter = 0;
}

gboolean
meta_display_focus_sentinel_clear (MetaDisplay *display)
{
  return (display->sentinel_counter == 0);
}

static void
sanity_check_timestamps (MetaDisplay *display,
                         guint32      timestamp)
{
  if (XSERVER_TIME_IS_BEFORE (timestamp, display->last_focus_time))
    {
      meta_warning ("last_focus_time (%u) is greater than comparison "
                    "timestamp (%u).  This most likely represents a buggy "
                    "client sending inaccurate timestamps in messages such as "
                    "_NET_ACTIVE_WINDOW.  Trying to work around...\n",
                    display->last_focus_time, timestamp);
      display->last_focus_time = timestamp;
    }
  if (XSERVER_TIME_IS_BEFORE (timestamp, display->last_user_time))
    {
      GSList *windows;
      GSList *tmp;

      meta_warning ("last_user_time (%u) is greater than comparison "
                    "timestamp (%u).  This most likely represents a buggy "
                    "client sending inaccurate timestamps in messages such as "
                    "_NET_ACTIVE_WINDOW.  Trying to work around...\n",
                    display->last_user_time, timestamp);
      display->last_user_time = timestamp;

      windows = meta_display_list_windows (display, META_LIST_DEFAULT);
      tmp = windows;
      while (tmp != NULL)
        {
          MetaWindow *window = tmp->data;
          
          if (XSERVER_TIME_IS_BEFORE (timestamp, window->net_wm_user_time))
            {
              meta_warning ("%s appears to be one of the offending windows "
                            "with a timestamp of %u.  Working around...\n",
                            window->desc, window->net_wm_user_time);
              meta_window_set_user_time (window, timestamp);
            }

          tmp = tmp->next;
        }

      g_slist_free (windows);
    }
}

void
meta_display_set_input_focus_window (MetaDisplay *display, 
                                     MetaWindow  *window,
                                     gboolean     focus_frame,
                                     guint32      timestamp)
{
  request_xserver_input_focus_change (display,
                                      window->screen,
                                      window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND ?
                                      META_FOCUS_WAYLAND_CLIENT : META_FOCUS_X_CLIENT,
                                      window,
                                      focus_frame ? window->frame->xwindow : window->xwindow,
                                      timestamp);
}

void
meta_display_request_take_focus (MetaDisplay *display,
                                 MetaWindow  *window,
                                 guint32      timestamp)
{
  if (timestamp_too_old (display, &timestamp))
    return;

  meta_topic (META_DEBUG_FOCUS, "WM_TAKE_FOCUS(%s, %u)\n",
              window->desc, timestamp);

  meta_window_send_icccm_message (window,
                                  display->atom_WM_TAKE_FOCUS,
                                  timestamp);
}

void
meta_display_set_input_focus_xwindow (MetaDisplay   *display,
                                      MetaScreen    *screen,
                                      MetaFocusType  type,
                                      Window         window,
                                      guint32        timestamp)
{
  request_xserver_input_focus_change (display,
                                      screen,
                                      type,
                                      NULL,
                                      window,
                                      timestamp);
}

void
meta_display_focus_the_no_focus_window (MetaDisplay *display,
                                        MetaScreen  *screen,
                                        guint32      timestamp)
{
  request_xserver_input_focus_change (display,
                                      screen,
                                      META_FOCUS_NO_FOCUS_WINDOW,
                                      NULL,
                                      screen->no_focus_window,
                                      timestamp);
}

void
meta_display_remove_autoraise_callback (MetaDisplay *display)
{
  if (display->autoraise_timeout_id != 0)
    {
      g_source_remove (display->autoraise_timeout_id);
      display->autoraise_timeout_id = 0;
      display->autoraise_window = NULL;
    }
}

void
meta_display_overlay_key_activate (MetaDisplay *display)
{
  g_signal_emit (display, display_signals[OVERLAY_KEY], 0);
}

void
meta_display_accelerator_activate (MetaDisplay     *display,
                                   guint            action,
                                   ClutterKeyEvent *event)
{
  g_signal_emit (display, display_signals[ACCELERATOR_ACTIVATED],
                 0, action,
                 clutter_input_device_get_device_id (event->device),
                 event->time);
}

gboolean
meta_display_modifiers_accelerator_activate (MetaDisplay *display)
{
  gboolean freeze;

  g_signal_emit (display, display_signals[MODIFIERS_ACCELERATOR_ACTIVATED], 0, &freeze);

  return freeze;
}

void
meta_display_get_compositor_version (MetaDisplay *display,
                                     int         *major,
                                     int         *minor)
{
  *major = display->composite_major_version;
  *minor = display->composite_minor_version;
}

/**
 * meta_display_get_xinput_opcode: (skip)
 * @display: a #MetaDisplay
 *
 */
int
meta_display_get_xinput_opcode (MetaDisplay *display)
{
  return display->xinput_opcode;
}

/**
 * meta_display_supports_extended_barriers:
 * @display: a #MetaDisplay
 *
 * Returns: whether the X server supports extended barrier
 * features as defined in version 2.3 of the XInput 2
 * specification.
 *
 * Clients should use this method to determine whether their
 * interfaces should depend on new barrier features.
 */
gboolean
meta_display_supports_extended_barriers (MetaDisplay *display)
{
  return META_DISPLAY_HAS_XINPUT_23 (display) && !meta_is_wayland_compositor ();
}

/**
 * meta_display_get_xdisplay: (skip)
 * @display: a #MetaDisplay
 *
 */
Display *
meta_display_get_xdisplay (MetaDisplay *display)
{
  return display->xdisplay;
}

/**
 * meta_display_get_compositor: (skip)
 * @display: a #MetaDisplay
 *
 */
MetaCompositor *
meta_display_get_compositor (MetaDisplay *display)
{
  return display->compositor;
}

/**
 * meta_display_get_screens:
 * @display: a #MetaDisplay
 *
 * Returns: (transfer none) (element-type Meta.Screen): Screens for this display
 */
GSList *
meta_display_get_screens (MetaDisplay *display)
{
  return display->screens;
}

gboolean
meta_display_has_shape (MetaDisplay *display)
{
  return META_DISPLAY_HAS_SHAPE (display);
}

/**
 * meta_display_get_focus_window:
 * @display: a #MetaDisplay
 *
 * Get our best guess as to the "currently" focused window (that is,
 * the window that we expect will be focused at the point when the X
 * server processes our next request).
 *
 * Return Value: (transfer none): The current focus window
 */
MetaWindow *
meta_display_get_focus_window (MetaDisplay *display)
{
  return display->focus_window;
}

int 
meta_display_get_damage_event_base (MetaDisplay *display)
{
  return display->damage_event_base;
}

#ifdef HAVE_SHAPE
int
meta_display_get_shape_event_base (MetaDisplay *display)
{
  return display->shape_event_base;
}
#endif

/**
 * meta_display_get_leader_window:
 * @display: a #MetaDisplay
 *
 * Returns the window manager's leader window (as defined by the
 * _NET_SUPPORTING_WM_CHECK mechanism of EWMH). For use by plugins that wish
 * to attach additional custom properties to this window.
 *
 * Return value: (transfer none): xid of the leader window.
 **/
Window
meta_display_get_leader_window (MetaDisplay *display)
{
  return display->leader_window;
}

/**
 * meta_display_clear_mouse_mode:
 * @display: a #MetaDisplay
 *
 * Sets the mouse-mode flag to %FALSE, which means that motion events are
 * no longer ignored in mouse or sloppy focus.
 * This is an internal function. It should be used only for reimplementing
 * keybindings, and only in a manner compatible with core code.
 */
void
meta_display_clear_mouse_mode (MetaDisplay *display)
{
  display->mouse_mode = FALSE;
}
