/* -*- 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/>.
 */

#include "config.h"
#include "events.h"

#include <X11/Xatom.h>
#include <X11/extensions/Xdamage.h>
#ifdef HAVE_SHAPE
#include <X11/extensions/shape.h>
#endif

#include <meta/errors.h>
#include "display-private.h"
#include "window-private.h"
#include "bell.h"
#include "workspace-private.h"
#include "meta-idle-monitor-private.h"

#include "x11/window-x11.h"
#include "x11/xprops.h"
#include "wayland/meta-wayland-private.h"
#include "meta-surface-actor-wayland.h"

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;

  /* Always use the key focused window for key events. */
  switch (event->type)
    {
    case CLUTTER_KEY_PRESS:
    case CLUTTER_KEY_RELEASE:
      return display->focus_window;
    default:
      break;
    }

  source = clutter_event_get_source (event);
  if (META_IS_SURFACE_ACTOR (source))
    return meta_surface_actor_get_window (META_SURFACE_ACTOR (source));

  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 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;
    }
}

G_GNUC_UNUSED 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;
}

G_GNUC_UNUSED 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;
}

G_GNUC_UNUSED 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";
}

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

  v = XSyncValueLow32 (*value);
  v |= (((gint64)XSyncValueHigh32 (*value)) << 32);

  return v;
}

G_GNUC_UNUSED 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 /* HAVE_XSYNC */

G_GNUC_UNUSED 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;

  XIEnterEvent *enter_event = (XIEnterEvent *) input_event;

  switch (input_event->evtype)
    {
    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_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;
}

G_GNUC_UNUSED 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;
}

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

  /* 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);

  g_print ("%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);
}

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

  /* 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";
    }
  else if (meta_display_xwindow_is_a_no_focus_window (display, event->event))
    window_type = "no_focus_window";
  else if (meta_display_screen_for_root (display, event->event))
    window_type = "root window";
  else
    window_type = "unknown window";

  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))
    {
      meta_display_update_focus_window (display,
                                        focus_window,
                                        focus_window ? focus_window->xwindow : None,
                                        display->server_focus_serial,
                                        FALSE);
    }
}

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
handle_input_xevent (MetaDisplay *display,
                     XIEvent     *input_event,
                     gulong       serial)
{
  XIEnterEvent *enter_event = (XIEnterEvent *) input_event;
  Window modified;
  MetaWindow *window;
  MetaScreen *screen;

  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;

  switch (input_event->evtype)
    {
    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. */
          if (enter_event->root != enter_event->event)
            break;

          screen = meta_display_screen_for_root (display, enter_event->root);

          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));
            }

        }

      /* Don't send FocusIn / FocusOut to Clutter */
      return TRUE;
    }

  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 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);
}

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);
  }
}

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 (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_x11_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:
      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;
}

static gboolean
grab_op_should_block_mouse_events (MetaGrabOp op)
{
  switch (op)
    {
    case META_GRAB_OP_WAYLAND_CLIENT:
    case META_GRAB_OP_COMPOSITOR:
      return TRUE;

    default:
      return 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;
}

/**
 * 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;

#if 0
  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);
      meta_display_update_focus_window (display,
                                        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 (!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
meta_display_handle_event (MetaDisplay        *display,
                           const ClutterEvent *event)
{
  MetaWindow *window;
  gboolean bypass_clutter = FALSE, bypass_wayland = FALSE;
  MetaWaylandCompositor *compositor = NULL;

  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);
          meta_display_sanity_check_timestamps (display, display->current_time);
        }
    }

  switch (event->type)
    {
    case CLUTTER_BUTTON_PRESS:
      if (grab_op_should_block_mouse_events (display->grab_op))
        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) ||
          meta_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"));
          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)
        {
          ClutterModifierType grab_mask;
          gboolean unmodified;
          gboolean fully_modified;

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

          /* We have three passive button grabs:
           * - on any button, without modifiers => focuses and maybe raises the window
           * - on resize button, with modifiers => start an interactive resizing
           *   (normally <Super>middle)
           * - on move button, with modifiers => start an interactive move
           *   (normally <Super>left)
           * - on menu button, with modifiers => show the window menu
           *   (normally <Super>right)
           *
           * We may get here because we actually have a button
           * grab on the window, or because we're a wayland
           * compositor and thus we see all the events, so we
           * need to check if the event is interesting.
           * We want an event that is not modified, for a window
           * that has (or would have, the wayland case) the
           * button grab active.
           *
           * We may have other events on the window, for example
           * a click on a frame button, but that's not for us to
           * care about. Just let the event through.
           */
          unmodified = (event->button.modifier_state & grab_mask) == 0;
          fully_modified = grab_mask && (event->button.modifier_state & grab_mask) == grab_mask;

          if (unmodified && window && window->have_focus_click_grab)
            {
              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;

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

              XIAllowEvents (display->xdisplay, clutter_input_device_get_device_id (event->button.device),
                             XIReplayDevice, event->button.time);
              bypass_clutter = TRUE;
            }
          else if (fully_modified && (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);
                }
              bypass_clutter = TRUE;
              bypass_wayland = TRUE;
            }
          else if (fully_modified && (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;
            }
          else if (fully_modified && (int) event->button.button == 1)
            {
              if (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 (grab_op_should_block_mouse_events (display->grab_op))
        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 (grab_op_should_block_mouse_events (display->grab_op))
        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 a Wayland client has a grab, don't pass that through to Clutter */
  if (display->grab_op == META_GRAB_OP_WAYLAND_CLIENT)
    bypass_clutter = TRUE;

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

  return bypass_clutter;
}

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);
}

void
meta_display_init_events (MetaDisplay *display)
{
  meta_ui_add_event_func (display->xdisplay,
                          xevent_callback,
                          display);
  display->clutter_event_filter = clutter_event_add_filter (NULL,
                                                            event_callback,
                                                            NULL,
                                                            display);
}

void
meta_display_free_events (MetaDisplay *display)
{
  meta_ui_remove_event_func (display->xdisplay,
                             xevent_callback,
                             display);
  clutter_event_remove_filter (display->clutter_event_filter);
  display->clutter_event_filter = 0;
}
