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

/**
 * MetaDisplay:
 *
 * Mutter display representation
 *
 * The display is represented as a #MetaDisplay struct.
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "backends/meta-backend-private.h"
#include "backends/meta-cursor-sprite-xcursor.h"
#include "backends/meta-cursor-tracker-private.h"
#include "backends/meta-input-capture.h"
#include "backends/meta-input-device-private.h"
#include "backends/meta-input-mapper-private.h"
#include "backends/meta-stage-private.h"
#include "compositor/compositor-private.h"
#include "cogl/cogl.h"
#include "core/bell.h"
#include "core/boxes-private.h"
#include "core/display-private.h"
#include "core/events.h"
#include "core/frame.h"
#include "core/keybindings-private.h"
#include "core/meta-clipboard-manager.h"
#include "core/meta-workspace-manager-private.h"
#include "core/util-private.h"
#include "core/window-private.h"
#include "core/workspace-private.h"
#include "meta/compositor-mutter.h"
#include "meta/compositor.h"
#include "meta/main.h"
#include "meta/meta-backend.h"
#include "meta/meta-enum-types.h"
#include "meta/meta-sound-player.h"
#include "meta/prefs.h"

#ifdef HAVE_X11_CLIENT
#include "backends/x11/meta-backend-x11.h"
#include "backends/x11/meta-clutter-backend-x11.h"
#include "backends/x11/meta-event-x11.h"
#include "backends/x11/cm/meta-backend-x11-cm.h"
#include "backends/x11/nested/meta-backend-x11-nested.h"
#include "compositor/meta-compositor-x11.h"
#include "meta/group.h"
#include "x11/meta-startup-notification-x11.h"
#include "x11/meta-x11-display-private.h"
#include "x11/window-x11.h"
#include "x11/xprops.h"
#endif

#ifdef HAVE_WAYLAND
#include "compositor/meta-compositor-native.h"
#include "compositor/meta-compositor-server.h"
#include "wayland/meta-wayland.h"
#include "wayland/meta-wayland-input-device.h"
#include "wayland/meta-wayland-private.h"
#include "wayland/meta-wayland-tablet-seat.h"
#include "wayland/meta-wayland-tablet-pad.h"
#include "wayland/meta-wayland-tablet-manager.h"
#include "wayland/meta-wayland-touch.h"
#endif

#ifdef HAVE_XWAYLAND
#include "wayland/meta-xwayland-private.h"
#endif

#ifdef HAVE_NATIVE_BACKEND
#include "backends/native/meta-backend-native.h"
#endif

/*
 * 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     serial;
  guint       ping_timeout_id;
} MetaPingData;

typedef struct
{
  MetaDisplay *display;
  int queue_idx;
} MetaQueueRunData;

typedef struct _MetaDisplayPrivate
{
  MetaContext *context;

  guint queue_later_ids[META_N_QUEUE_TYPES];
  GList *queue_windows[META_N_QUEUE_TYPES];

  gboolean enable_input_capture;

  struct {
    MetaWindow *window;
    gulong unmanaging_handler_id;
  } focus_on_grab_dismissed;
} MetaDisplayPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (MetaDisplay, meta_display, G_TYPE_OBJECT)

/* Signals */
enum
{
  CURSOR_UPDATED,
  X11_DISPLAY_SETUP,
  X11_DISPLAY_OPENED,
  X11_DISPLAY_CLOSING,
  OVERLAY_KEY,
  ACCELERATOR_ACTIVATED,
  MODIFIERS_ACCELERATOR_ACTIVATED,
  FOCUS_WINDOW,
  WINDOW_CREATED,
  WINDOW_DEMANDS_ATTENTION,
  WINDOW_MARKED_URGENT,
  GRAB_OP_BEGIN,
  GRAB_OP_END,
  SHOW_RESTART_MESSAGE,
  RESTART,
  SHOW_RESIZE_POPUP,
  GL_VIDEO_MEMORY_PURGED,
  SHOW_PAD_OSD,
  SHOW_OSD,
  PAD_MODE_SWITCH,
  WINDOW_ENTERED_MONITOR,
  WINDOW_LEFT_MONITOR,
  WORKSPACE_ADDED,
  WORKSPACE_REMOVED,
  WORKSPACE_SWITCHED,
  ACTIVE_WORKSPACE_CHANGED,
  IN_FULLSCREEN_CHANGED,
  SHOWING_DESKTOP_CHANGED,
  RESTACKED,
  WORKAREAS_CHANGED,
  CLOSING,
  INIT_XSERVER,
  WINDOW_VISIBILITY_UPDATED,
  LAST_SIGNAL
};

enum
{
  PROP_0,

  PROP_COMPOSITOR_MODIFIERS,
  PROP_FOCUS_WINDOW
};

static guint display_signals [LAST_SIGNAL] = { 0 };

static void on_monitors_changed_internal (MetaMonitorManager *monitor_manager,
                                          MetaDisplay        *display);

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

static int mru_cmp (gconstpointer a,
                    gconstpointer b);

static void meta_display_reload_cursor (MetaDisplay *display);

static void meta_display_unmanage_windows (MetaDisplay *display,
                                           guint32      timestamp);

static void
meta_display_show_osd (MetaDisplay *display,
                       gint         monitor_idx,
                       const gchar *icon_name,
                       const gchar *message);

static void on_is_grabbed_changed (ClutterStage *stage,
                                   GParamSpec   *pspec,
                                   MetaDisplay  *display);

static MetaBackend *
backend_from_display (MetaDisplay *display)
{
  MetaContext *context = meta_display_get_context (display);

  return meta_context_get_backend (context);
}

#ifdef HAVE_WAYLAND
static MetaWaylandCompositor *
wayland_compositor_from_display (MetaDisplay *display)
{
  MetaContext *context = meta_display_get_context (display);

  return meta_context_get_wayland_compositor (context);
}
#endif

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_COMPOSITOR_MODIFIERS:
      g_value_set_flags (value, meta_display_get_compositor_modifiers (display));
      break;
    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[CURSOR_UPDATED] =
    g_signal_new ("cursor-updated",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  display_signals[X11_DISPLAY_SETUP] =
    g_signal_new ("x11-display-setup",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  display_signals[X11_DISPLAY_OPENED] =
    g_signal_new ("x11-display-opened",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  display_signals[X11_DISPLAY_CLOSING] =
    g_signal_new ("x11-display-closing",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  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, CLUTTER_TYPE_INPUT_DEVICE, 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[FOCUS_WINDOW] =
    g_signal_new ("focus-window",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 2, META_TYPE_WINDOW, G_TYPE_INT64);

  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, 2,
                  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, 2,
                  META_TYPE_WINDOW,
                  META_TYPE_GRAB_OP);

  /**
   * MetaDisplay::show-restart-message:
   * @display: the #MetaDisplay instance
   * @message: (allow-none): The message to display, or %NULL
   *  to clear a previous restart message.
   *
   * The signal will be emitted to indicate that the compositor 
   * should show a message during restart.
   *
   * This is emitted when [func@Meta.restart] is called, either by Mutter
   * internally or by the embedding compositor. The message should be
   * immediately added to the Clutter stage in its final form -
   * [signal@Meta.Display::restart] will be emitted to exit the application and leave the
   * stage contents frozen as soon as the the stage is painted again.
   *
   * On case of failure to restart, this signal will be emitted again
   * with %NULL for @message.
   *
   * Returns: %TRUE means the message was added to the stage; %FALSE
   *   indicates that the compositor did not show the message.
   */
  display_signals[SHOW_RESTART_MESSAGE] =
    g_signal_new ("show-restart-message",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  g_signal_accumulator_true_handled,
                  NULL, NULL,
                  G_TYPE_BOOLEAN, 1,
                  G_TYPE_STRING);

  /**
   * MetaDisplay::restart:
   * @display: the #MetaDisplay instance
   *
   * The signal is emitted to indicate that compositor
   * should reexec the process.
   *
   * This is emitted when [func@Meta.restart] is called,
   * either by Mutter internally or by the embedding compositor.
   *
   * See also [signal@Meta.Display::show-restart-message].
   *
   * Returns: %FALSE to indicate that the compositor could not
   *  be restarted. When the compositor is restarted, the signal
   *  should not return.
   */
  display_signals[RESTART] =
    g_signal_new ("restart",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  g_signal_accumulator_true_handled,
                  NULL, NULL,
                  G_TYPE_BOOLEAN, 0);

  display_signals[SHOW_RESIZE_POPUP] =
    g_signal_new ("show-resize-popup",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  g_signal_accumulator_true_handled,
                  NULL, NULL,
                  G_TYPE_BOOLEAN, 4,
                  G_TYPE_BOOLEAN, MTK_TYPE_RECTANGLE, G_TYPE_INT, G_TYPE_INT);

  display_signals[GL_VIDEO_MEMORY_PURGED] =
    g_signal_new ("gl-video-memory-purged",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  /**
   * MetaDisplay::show-pad-osd:
   * @display: the #MetaDisplay instance
   * @pad: the pad device
   * @settings: the pad device settings
   * @layout_path: path to the layout image
   * @edition_mode: Whether the OSD should be shown in edition mode
   * @monitor_idx: Monitor to show the OSD on
   *
   * Requests the pad button mapping OSD to be shown.
   *
   * Returns: (transfer none) (nullable): The OSD actor
   */
  display_signals[SHOW_PAD_OSD] =
    g_signal_new ("show-pad-osd",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  CLUTTER_TYPE_ACTOR, 5, CLUTTER_TYPE_INPUT_DEVICE,
                  G_TYPE_SETTINGS, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT);

  display_signals[SHOW_OSD] =
    g_signal_new ("show-osd",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING);

  display_signals[PAD_MODE_SWITCH] =
    g_signal_new ("pad-mode-switch",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 3, CLUTTER_TYPE_INPUT_DEVICE,
                  G_TYPE_UINT, G_TYPE_UINT);

  display_signals[WINDOW_ENTERED_MONITOR] =
    g_signal_new ("window-entered-monitor",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 2,
                  G_TYPE_INT,
                  META_TYPE_WINDOW);

  display_signals[WINDOW_LEFT_MONITOR] =
    g_signal_new ("window-left-monitor",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 2,
                  G_TYPE_INT,
                  META_TYPE_WINDOW);

  display_signals[IN_FULLSCREEN_CHANGED] =
    g_signal_new ("in-fullscreen-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  display_signals[SHOWING_DESKTOP_CHANGED] =
    g_signal_new ("showing-desktop-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  display_signals[RESTACKED] =
    g_signal_new ("restacked",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  display_signals[WORKAREAS_CHANGED] =
    g_signal_new ("workareas-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 0);
  display_signals[CLOSING] =
    g_signal_new ("closing",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  display_signals[INIT_XSERVER] =
    g_signal_new ("init-xserver",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, g_signal_accumulator_first_wins,
                  NULL, NULL,
                  G_TYPE_BOOLEAN, 1, G_TYPE_TASK);

  display_signals[WINDOW_VISIBILITY_UPDATED] =
    g_signal_new ("window-visibility-updated",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL, NULL,
                  G_TYPE_NONE, 3,
                  G_TYPE_POINTER,
                  G_TYPE_POINTER,
                  G_TYPE_POINTER);

  g_object_class_install_property (object_class,
                                   PROP_COMPOSITOR_MODIFIERS,
                                   g_param_spec_flags ("compositor-modifiers", NULL, NULL,
                                                       CLUTTER_TYPE_MODIFIER_TYPE,
                                                       0,
                                                       G_PARAM_READABLE));

  g_object_class_install_property (object_class,
                                   PROP_FOCUS_WINDOW,
                                   g_param_spec_object ("focus-window", NULL, NULL,
                                                        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 */
  g_clear_handle_id (&ping_data->ping_timeout_id, g_source_remove);

  g_free (ping_data);
}

void
meta_display_remove_pending_pings_for_window (MetaDisplay *display,
                                              MetaWindow  *window)
{
  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 == window)
        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);
}

static MetaCompositor *
create_compositor (MetaDisplay *display)
{
  MetaBackend *backend = backend_from_display (display);

#ifdef HAVE_WAYLAND
#ifdef HAVE_NATIVE_BACKEND
  if (META_IS_BACKEND_NATIVE (backend))
    return META_COMPOSITOR (meta_compositor_native_new (display, backend));
#endif
#ifdef HAVE_XWAYLAND
  if (META_IS_BACKEND_X11_NESTED (backend))
    return META_COMPOSITOR (meta_compositor_server_new (display, backend));
#endif
#endif/* HAVE_WAYLAND */
#ifdef HAVE_X11
  return META_COMPOSITOR (meta_compositor_x11_new (display, backend));
#else
  g_assert_not_reached ();
#endif
}

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

void
meta_display_cancel_touch (MetaDisplay *display)
{
#ifdef HAVE_WAYLAND
  MetaWaylandCompositor *compositor;

  if (!meta_is_wayland_compositor ())
    return;

  compositor = wayland_compositor_from_display (display);
  meta_wayland_touch_cancel (compositor->seat->touch);
#endif
}

static void
gesture_tracker_state_changed (MetaGestureTracker   *tracker,
                               ClutterEventSequence *sequence,
                               MetaSequenceState     state,
                               MetaDisplay          *display)
{
  switch (state)
    {
    case META_SEQUENCE_NONE:
    case META_SEQUENCE_PENDING_END:
      return;
    case META_SEQUENCE_ACCEPTED:
      meta_display_cancel_touch (display);

      G_GNUC_FALLTHROUGH;
    case META_SEQUENCE_REJECTED:
      {
        MetaBackend *backend;

        backend = backend_from_display (display);
        meta_backend_finish_touch_sequence (backend, sequence, state);
        break;
      }
    }
}

static void
on_ui_scaling_factor_changed (MetaSettings *settings,
                              MetaDisplay  *display)
{
  meta_display_reload_cursor (display);
}

static void
on_monitor_privacy_screen_changed (MetaDisplay        *display,
                                   MetaLogicalMonitor *logical_monitor,
                                   gboolean            enabled)
{
  meta_display_show_osd (display,
                         logical_monitor->number,
                         enabled ? "screen-privacy-symbolic"
                                 : "screen-privacy-disabled-symbolic",
                         enabled ? _("Privacy Screen Enabled")
                                 : _("Privacy Screen Disabled"));
}

gboolean
meta_display_process_captured_input (MetaDisplay        *display,
                                     const ClutterEvent *event)
{
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);
  MetaContext *context = priv->context;
  MetaBackend *backend = meta_context_get_backend (context);
  MetaInputCapture *input_capture = meta_backend_get_input_capture (backend);

  if (!priv->enable_input_capture)
    return FALSE;

  /* Check for the cancel key combo, but let the event flow through, so
   * that meta_input_capture_process_event() can account for all press
   * and release events, even the one from the key combo itself.
   */
  meta_display_process_keybinding_event (display,
                                         "cancel-input-capture",
                                         event);

  return meta_input_capture_process_event (input_capture, event);
}

void
meta_display_cancel_input_capture (MetaDisplay *display)
{
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);
  MetaContext *context = priv->context;
  MetaBackend *backend = meta_context_get_backend (context);
  MetaInputCapture *input_capture = meta_backend_get_input_capture (backend);

  meta_input_capture_notify_cancelled (input_capture);
}

static void
enable_input_capture (MetaInputCapture *input_capture,
                      gpointer          user_data)
{
  MetaDisplay *display = META_DISPLAY (user_data);
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);

  g_return_if_fail (!priv->enable_input_capture);

  priv->enable_input_capture = TRUE;
}

static void
disable_input_capture (MetaInputCapture *input_capture,
                       gpointer          user_data)
{
  MetaDisplay *display = META_DISPLAY (user_data);
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);

  g_return_if_fail (priv->enable_input_capture);

  priv->enable_input_capture = FALSE;
}

#ifdef HAVE_X11_CLIENT
static gboolean
meta_display_init_x11_display (MetaDisplay  *display,
                               GError      **error)
{
  MetaX11Display *x11_display;

  x11_display = meta_x11_display_new (display, error);
  if (!x11_display)
    return FALSE;

  display->x11_display = x11_display;
  g_signal_emit (display, display_signals[X11_DISPLAY_SETUP], 0);

  meta_x11_display_create_guard_window (x11_display);

  if (!display->display_opening)
    g_signal_emit (display, display_signals[X11_DISPLAY_OPENED], 0);

  return TRUE;
}
#endif

#ifdef HAVE_XWAYLAND
gboolean
meta_display_init_x11_finish (MetaDisplay   *display,
                              GAsyncResult  *result,
                              GError       **error)
{
  MetaX11Display *x11_display;

  g_assert (g_task_get_source_tag (G_TASK (result)) == meta_display_init_x11);

  if (!g_task_propagate_boolean (G_TASK (result), error))
    {
      if (*error == NULL)
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown error");

      return FALSE;
    }

  if (display->x11_display)
    return TRUE;

  x11_display = meta_x11_display_new (display, error);
  if (!x11_display)
    return FALSE;

  display->x11_display = x11_display;
  g_signal_emit (display, display_signals[X11_DISPLAY_SETUP], 0);

  meta_x11_display_create_guard_window (x11_display);

  if (!display->display_opening)
    g_signal_emit (display, display_signals[X11_DISPLAY_OPENED], 0);

  return TRUE;
}

#ifdef HAVE_XWAYLAND
static void
on_xserver_started (MetaXWaylandManager *manager,
                    GAsyncResult        *result,
                    gpointer             user_data)
{
  g_autoptr (GTask) task = user_data;
  MetaDisplay *display = g_task_get_source_object (task);
  GError *error = NULL;
  gboolean retval = FALSE;

  if (!meta_xwayland_start_xserver_finish (manager, result, &error))
    {
      if (error)
        g_task_return_error (task, error);
      else
        g_task_return_boolean (task, FALSE);

      return;
    }

  g_signal_emit (display, display_signals[INIT_XSERVER], 0, task, &retval);

  if (!retval)
    {
      /* No handlers for this signal, proceed right away */
      g_task_return_boolean (task, TRUE);
    }
}
#endif /* HAVE_XWAYLAND */

void
meta_display_init_x11 (MetaDisplay         *display,
                       GCancellable        *cancellable,
                       GAsyncReadyCallback  callback,
                       gpointer             user_data)
{
  g_autoptr (GTask) task = NULL;
#ifdef HAVE_XWAYLAND
  MetaWaylandCompositor *compositor;
#endif

  task = g_task_new (display, cancellable, callback, user_data);
  g_task_set_source_tag (task, meta_display_init_x11);

#ifdef HAVE_XWAYLAND
  compositor = wayland_compositor_from_display (display);

  meta_xwayland_start_xserver (&compositor->xwayland_manager,
                               cancellable,
                               (GAsyncReadyCallback) on_xserver_started,
                               g_steal_pointer (&task));
#endif
}

static void
on_mandatory_x11_initialized (MetaDisplay  *display,
                              GAsyncResult *result,
                              gpointer      user_data)
{
  g_autoptr (GError) error = NULL;

  if (!meta_display_init_x11_finish (display, result, &error))
    g_critical ("Failed to init X11 display: %s", error->message);
}
#endif /* HAVE_XWAYLAND */

void
meta_display_shutdown_x11 (MetaDisplay *display)
{
  if (!display->x11_display)
    return;

  meta_stack_freeze (display->stack);
  g_signal_emit (display, display_signals[X11_DISPLAY_CLOSING], 0);
  g_object_run_dispose (G_OBJECT (display->x11_display));
  g_clear_object (&display->x11_display);
  meta_stack_thaw (display->stack);
}

MetaDisplay *
meta_display_new (MetaContext  *context,
                  GError      **error)
{
  MetaBackend *backend = meta_context_get_backend (context);
  ClutterActor *stage = meta_backend_get_stage (backend);
  MetaDisplay *display;
  MetaDisplayPrivate *priv;
  guint32 timestamp;
  MetaMonitorManager *monitor_manager;
  MetaSettings *settings;
  MetaInputCapture *input_capture;

  display = g_object_new (META_TYPE_DISPLAY, NULL);

  priv = meta_display_get_instance_private (display);
  priv->context = context;

  display->closing = 0;
  display->display_opening = TRUE;

  display->pending_pings = NULL;
  display->autoraise_timeout_id = 0;
  display->autoraise_window = NULL;
  display->focus_window = NULL;
  display->workspace_manager = NULL;
  display->x11_display = NULL;

  display->current_cursor = -1; /* invalid/unset */
  display->check_fullscreen_later = 0;
  display->work_area_later = 0;

  display->mouse_mode = TRUE; /* Only relevant for mouse or sloppy focus */

  display->current_time = META_CURRENT_TIME;

  meta_display_init_keys (display);

  meta_prefs_add_listener (prefs_changed_callback, display);

  /* Get events */
  meta_display_init_events (display);

  display->stamps = g_hash_table_new (g_int64_hash,
                                      g_int64_equal);
  display->wayland_windows = g_hash_table_new (NULL, NULL);

  monitor_manager = meta_backend_get_monitor_manager (backend);
  g_signal_connect (monitor_manager, "monitors-changed-internal",
                    G_CALLBACK (on_monitors_changed_internal), display);
  g_signal_connect_object (monitor_manager, "monitor-privacy-screen-changed",
                           G_CALLBACK (on_monitor_privacy_screen_changed),
                           display, G_CONNECT_SWAPPED);

  display->pad_action_mapper = meta_pad_action_mapper_new (monitor_manager);

  input_capture = meta_backend_get_input_capture (backend);
  meta_input_capture_set_event_router (input_capture,
                                       enable_input_capture,
                                       disable_input_capture,
                                       display);

  settings = meta_backend_get_settings (backend);
  g_signal_connect (settings, "ui-scaling-factor-changed",
                    G_CALLBACK (on_ui_scaling_factor_changed), display);

  display->compositor = create_compositor (display);

  meta_display_set_cursor (display, META_CURSOR_DEFAULT);

  display->stack = meta_stack_new (display);
  display->stack_tracker = meta_stack_tracker_new (display->stack);

  display->workspace_manager = meta_workspace_manager_new (display);

  display->startup_notification = meta_startup_notification_new (display);

  display->bell = meta_bell_new (display);

  display->selection = meta_selection_new (display);
  meta_clipboard_manager_init (display);

#ifdef HAVE_WAYLAND
  if (meta_is_wayland_compositor ())
    {
#ifdef HAVE_XWAYLAND
      MetaWaylandCompositor *wayland_compositor =
        wayland_compositor_from_display (display);
      MetaX11DisplayPolicy x11_display_policy;

      meta_xwayland_init_display (&wayland_compositor->xwayland_manager,
                                  display);

      x11_display_policy = meta_context_get_x11_display_policy (context);
      if (x11_display_policy == META_X11_DISPLAY_POLICY_MANDATORY)
        {
          meta_display_init_x11 (display, NULL,
                                 (GAsyncReadyCallback) on_mandatory_x11_initialized,
                                 NULL);
        }
#endif /* HAVE_XWAYLAND */
      timestamp = meta_display_get_current_time_roundtrip (display);
    }
  else
#endif /* HAVE_WAYLAND */
#ifdef HAVE_X11
    {
      if (!meta_display_init_x11_display (display, error))
        {
          g_object_unref (display);
          return NULL;
        }

      timestamp = display->x11_display->timestamp;
    }
#else
    {
      g_assert_not_reached ();
    }
#endif

  display->last_focus_time = timestamp;
  display->last_user_time = timestamp;

  if (!meta_compositor_manage (display->compositor, error))
    {
      g_object_unref (display);
      return NULL;
    }

#ifdef HAVE_X11_CLIENT
  if (display->x11_display)
    {
      g_signal_emit (display, display_signals[X11_DISPLAY_OPENED], 0);
      meta_x11_display_restore_active_workspace (display->x11_display);
      meta_x11_display_create_guard_window (display->x11_display);
    }
#endif

  /* Set up touch support */
  display->gesture_tracker = meta_gesture_tracker_new ();
  g_signal_connect (display->gesture_tracker, "state-changed",
                    G_CALLBACK (gesture_tracker_state_changed), display);

  meta_display_unset_input_focus (display, timestamp);

  g_signal_connect (stage, "notify::is-grabbed",
                    G_CALLBACK (on_is_grabbed_changed), display);

  display->sound_player = g_object_new (META_TYPE_SOUND_PLAYER, NULL);

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

  return display;
}

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 *prev;
  GSList *tmp;
  GHashTableIter iter;
  gpointer key, value;

  winlist = NULL;

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

          if (!META_IS_WINDOW (window) || window->unmanaging)
            continue;

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

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

  if (flags & META_LIST_SORTED)
    winlist = g_slist_sort (winlist, mru_cmp);

  return winlist;
}

void
meta_display_close (MetaDisplay *display,
                    guint32      timestamp)
{
  MetaBackend *backend = backend_from_display (display);
  ClutterActor *stage = meta_backend_get_stage (backend);
  MetaCompositor *compositor;
  MetaLaters *laters;

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

  display->closing += 1;

  g_signal_emit (display, display_signals[CLOSING], 0);

  meta_display_unmanage_windows (display, timestamp);
  meta_compositor_unmanage (display->compositor);

  meta_prefs_remove_listener (prefs_changed_callback, display);

  meta_display_remove_autoraise_callback (display);

  g_clear_object (&display->gesture_tracker);

  g_clear_handle_id (&display->focus_timeout_id, g_source_remove);

  compositor = meta_display_get_compositor (display);
  laters = meta_compositor_get_laters (compositor);
  if (display->work_area_later != 0)
    meta_laters_remove (laters, display->work_area_later);
  if (display->check_fullscreen_later != 0)
    meta_laters_remove (laters, display->check_fullscreen_later);

  /* Stop caring about events */
  meta_display_free_events (display);

  g_clear_pointer (&display->stack_tracker,
                   meta_stack_tracker_free);

  g_clear_pointer (&display->compositor, meta_compositor_destroy);
  meta_display_shutdown_x11 (display);
  g_clear_object (&display->stack);

  /* Must be after all calls to meta_window_unmanage() since they
   * unregister windows
   */
  g_hash_table_destroy (display->wayland_windows);
  g_hash_table_destroy (display->stamps);

  meta_display_shutdown_keys (display);

  g_signal_handlers_disconnect_by_func (stage, on_is_grabbed_changed, display);

  g_clear_object (&display->bell);
  g_clear_object (&display->startup_notification);
  g_clear_object (&display->workspace_manager);
  g_clear_object (&display->sound_player);

  meta_clipboard_manager_shutdown (display);
  g_clear_object (&display->selection);
  g_clear_object (&display->pad_action_mapper);
}

gboolean
meta_grab_op_is_mouse (MetaGrabOp op)
{
  return (op & META_GRAB_OP_WINDOW_FLAG_KEYBOARD) == 0;
}

gboolean
meta_grab_op_is_keyboard (MetaGrabOp op)
{
  return (op & META_GRAB_OP_WINDOW_FLAG_KEYBOARD) != 0;
}

gboolean
meta_grab_op_is_resizing (MetaGrabOp op)
{
  return (op & META_GRAB_OP_WINDOW_DIR_MASK) != 0 ||
    (op & META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN) ==
    META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN;
}

gboolean
meta_grab_op_is_moving (MetaGrabOp op)
{
  return !meta_grab_op_is_resizing (op);
}

/**
 * meta_display_windows_are_interactable:
 * @op: A #MetaGrabOp
 *
 * Whether windows can be interacted with.
 */
gboolean
meta_display_windows_are_interactable (MetaDisplay *display)
{
  MetaBackend *backend = backend_from_display (display);
  MetaStage *stage = META_STAGE (meta_backend_get_stage (backend));

  if (clutter_stage_get_grab_actor (CLUTTER_STAGE (stage)))
    return FALSE;

  return TRUE;
}

static void
on_is_grabbed_changed (ClutterStage *stage,
                       GParamSpec   *pspec,
                       MetaDisplay  *display)
{
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);

  if (!priv->focus_on_grab_dismissed.window)
    return;

  meta_window_focus (priv->focus_on_grab_dismissed.window, META_CURRENT_TIME);

  g_clear_signal_handler (&priv->focus_on_grab_dismissed.unmanaging_handler_id,
                          priv->focus_on_grab_dismissed.window);
  priv->focus_on_grab_dismissed.window = NULL;
}

static void
focus_on_grab_dismissed_unmanaging_cb (MetaWindow  *window,
                                       MetaDisplay *display)
{
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);

  g_return_if_fail (priv->focus_on_grab_dismissed.window == window);

  g_clear_signal_handler (&priv->focus_on_grab_dismissed.unmanaging_handler_id,
                          priv->focus_on_grab_dismissed.window);
  priv->focus_on_grab_dismissed.window = NULL;
}

void
meta_display_queue_focus (MetaDisplay *display,
                          MetaWindow  *window)
{
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);

  g_clear_signal_handler (&priv->focus_on_grab_dismissed.unmanaging_handler_id,
                          priv->focus_on_grab_dismissed.window);

  priv->focus_on_grab_dismissed.window = window;
  priv->focus_on_grab_dismissed.unmanaging_handler_id =
    g_signal_connect (window, "unmanaging",
                      G_CALLBACK (focus_on_grab_dismissed_unmanaging_cb),
                      display);
}

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

guint32
meta_display_get_current_time_roundtrip (MetaDisplay *display)
{
  if (meta_is_wayland_compositor ())
    /* Xwayland uses monotonic clock, so lets use it here as well */
    return (guint32) (g_get_monotonic_time () / 1000);
  else
#ifdef HAVE_X11_CLIENT
    return meta_x11_display_get_current_time_roundtrip (display->x11_display);
#else
    g_assert_not_reached ();
#endif
}

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->display->stack) != window)
    {
      if (meta_window_has_pointer (window))
	meta_window_raise (window);
      else
	meta_topic (META_DEBUG_FOCUS,
		    "Pointer not inside window, not raising %s",
		    window->desc);
    }

  return G_SOURCE_REMOVE;
}

void
meta_display_queue_autoraise_callback (MetaDisplay *display,
                                       MetaWindow  *window)
{
  meta_topic (META_DEBUG_FOCUS,
              "Queuing an autoraise timeout for %s with delay %d",
              window->desc,
              meta_prefs_get_auto_raise_delay ());

  g_clear_handle_id (&display->autoraise_timeout_id, g_source_remove);

  display->autoraise_timeout_id =
    g_timeout_add_full (G_PRIORITY_DEFAULT,
                        meta_prefs_get_auto_raise_delay (),
                        window_raise_with_delay_callback,
                        window, NULL);
  g_source_set_name_by_id (display->autoraise_timeout_id, "[mutter] window_raise_with_delay_callback");
  display->autoraise_window = window;
}

static void
meta_window_set_inactive_since (MetaWindow  *window,
                                int64_t      inactive_since_us)
{
  GFile *file = NULL;
  g_autoptr (GFileInfo) file_info = NULL;
  g_autofree char *timestamp = NULL;

  timestamp = g_strdup_printf ("%" G_GINT64_FORMAT, inactive_since_us);

  file = meta_window_get_unit_cgroup (window);
  if (!file)
    return;

  file_info = g_file_info_new ();
  g_file_info_set_attribute_string (file_info,
                                    "xattr::xdg.inactive-since", timestamp);

  if (!g_file_set_attributes_from_info (file, file_info,
                                        G_FILE_QUERY_INFO_NONE,
                                        NULL, NULL))
    return;
}

void
meta_display_update_focus_window (MetaDisplay *display,
                                  MetaWindow  *window)
{
  MetaWindow *previous = NULL;

  if (display->focus_window == window)
    return;

  if (display->focus_window)
    {
      meta_topic (META_DEBUG_FOCUS,
                  "%s is now the previous focus window due to being focused out or unmapped",
                  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;

      meta_window_set_focused_internal (previous, FALSE);
    }

  display->focus_window = window;

  if (display->focus_window)
    {
      meta_topic (META_DEBUG_FOCUS, "* Focus --> %s",
                  display->focus_window->desc);
      meta_window_set_focused_internal (display->focus_window, TRUE);
    }
  else
    meta_topic (META_DEBUG_FOCUS, "* Focus --> NULL");

  if (!previous || !display->focus_window ||
      !meta_window_unit_cgroup_equal (previous, display->focus_window))
    {
      if (previous)
        meta_window_set_inactive_since (previous, g_get_monotonic_time ());
      if (display->focus_window)
        meta_window_set_inactive_since (display->focus_window, -1);
    }

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

static gboolean
meta_display_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 == META_CURRENT_TIME)
    {
      *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;
}

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

  g_signal_emit (display, display_signals[FOCUS_WINDOW], 0, window, ms2us (timestamp));

  meta_display_update_focus_window (display, window);

  display->last_focus_time = timestamp;

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

void
meta_display_unset_input_focus (MetaDisplay *display,
                                guint32      timestamp)
{
  meta_display_set_input_focus (display, NULL, timestamp);
}

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

MetaWindow*
meta_display_lookup_stamp (MetaDisplay *display,
                           guint64       stamp)
{
  return g_hash_table_lookup (display->stamps, &stamp);
}

void
meta_display_register_stamp (MetaDisplay *display,
                             guint64     *stampp,
                             MetaWindow  *window)
{
  g_return_if_fail (g_hash_table_lookup (display->stamps, stampp) == NULL);

  g_hash_table_insert (display->stamps, stampp, window);
}

void
meta_display_unregister_stamp (MetaDisplay *display,
                               guint64      stamp)
{
  g_return_if_fail (g_hash_table_lookup (display->stamps, &stamp) != NULL);

  g_hash_table_remove (display->stamps, &stamp);
}

MetaWindow*
meta_display_lookup_stack_id (MetaDisplay *display,
                              guint64      stack_id)
{
#ifdef HAVE_X11_CLIENT
  if (META_STACK_ID_IS_X11 (stack_id))
    {
      if (!display->x11_display)
        return NULL;
      return meta_x11_display_lookup_x_window (display->x11_display,
                                               (Window)stack_id);
    }
#endif
  return meta_display_lookup_stamp (display, stack_id);
}

/* We return a pointer into a ring of static buffers. This is to make
 * using this function for debug-logging convenient and avoid temporary
 * strings that must be freed. */
const char *
meta_display_describe_stack_id (MetaDisplay *display,
                                guint64      stack_id)
{
  /* 0x<64-bit: 16 characters> (<10 characters of title>)\0' */
  static char buffer[5][32];
  MetaWindow *window;
  static int pos = 0;
  char *result;

  result = buffer[pos];
  pos = (pos + 1) % 5;

  window = meta_display_lookup_stack_id (display, stack_id);

  if (window && window->title)
    snprintf (result, sizeof(buffer[0]), "%#" G_GINT64_MODIFIER "x (%.10s)", stack_id, window->title);
  else
    snprintf (result, sizeof(buffer[0]), "%#" G_GINT64_MODIFIER "x", stack_id);

  return result;
}

void
meta_display_notify_window_created (MetaDisplay  *display,
                                    MetaWindow   *window)
{
  COGL_TRACE_BEGIN_SCOPED (MetaDisplayNotifyWindowCreated,
                           "Meta::Display::notify_window_created()");
  g_signal_emit (display, display_signals[WINDOW_CREATED], 0, window);
}

static void
root_cursor_prepare_at (MetaCursorSpriteXcursor *sprite_xcursor,
                        float                    best_scale,
                        int                      x,
                        int                      y,
                        MetaDisplay             *display)
{
  MetaCursorSprite *cursor_sprite = META_CURSOR_SPRITE (sprite_xcursor);
  MetaBackend *backend = backend_from_display (display);

  if (meta_backend_is_stage_views_scaled (backend))
    {
      if (best_scale != 0.0f)
        {
          float ceiled_scale;

          ceiled_scale = ceilf (best_scale);
          meta_cursor_sprite_xcursor_set_theme_scale (sprite_xcursor,
                                                      (int) ceiled_scale);
          meta_cursor_sprite_set_texture_scale (cursor_sprite,
                                                1.0 / ceiled_scale);
        }
    }
  else
    {
      MetaMonitorManager *monitor_manager =
        meta_backend_get_monitor_manager (backend);
      MetaLogicalMonitor *logical_monitor;

      logical_monitor =
        meta_monitor_manager_get_logical_monitor_at (monitor_manager, x, y);

      /* Reload the cursor texture if the scale has changed. */
      if (logical_monitor)
        {
          meta_cursor_sprite_xcursor_set_theme_scale (sprite_xcursor,
                                                      logical_monitor->scale);
          meta_cursor_sprite_set_texture_scale (cursor_sprite, 1.0);
        }
    }
}

static void
manage_root_cursor_sprite_scale (MetaDisplay             *display,
                                 MetaCursorSpriteXcursor *sprite_xcursor)
{
  meta_cursor_sprite_set_prepare_func (META_CURSOR_SPRITE (sprite_xcursor),
                                       (MetaCursorPrepareFunc) root_cursor_prepare_at,
                                       display);
}

void
meta_display_reload_cursor (MetaDisplay *display)
{
  MetaCursor cursor = display->current_cursor;
  MetaCursorSpriteXcursor *sprite_xcursor;
  MetaBackend *backend = backend_from_display (display);
  MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend);

  sprite_xcursor = meta_cursor_sprite_xcursor_new (cursor, cursor_tracker);

  if (meta_is_wayland_compositor ())
    manage_root_cursor_sprite_scale (display, sprite_xcursor);

  meta_cursor_tracker_set_root_cursor (cursor_tracker,
                                       META_CURSOR_SPRITE (sprite_xcursor));
  g_object_unref (sprite_xcursor);

  g_signal_emit (display, display_signals[CURSOR_UPDATED], 0, display);
}

void
meta_display_set_cursor (MetaDisplay *display,
                         MetaCursor   cursor)
{
  if (cursor == display->current_cursor)
    return;

  display->current_cursor = cursor;
  meta_display_reload_cursor (display);
}

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

 * Returns %TRUE if there is an ongoing grab operation.
 *
 * Return value: Whether there is an active display grab operation.
 */
gboolean
meta_display_is_grabbed (MetaDisplay *display)
{
  return meta_compositor_get_current_window_drag (display->compositor) != NULL;
}

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

      tmp = tmp->next;
    }

  g_slist_free (windows);
}

/**
 * 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;
  MetaWindow *window = ping_data->window;
  MetaDisplay *display = window->display;

  meta_window_set_alive (window, FALSE);
  meta_window_show_close_dialog (window);

  ping_data->ping_timeout_id = 0;

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

  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!
 *
 * 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     serial)
{
  GSList *l;
  MetaDisplay *display = window->display;
  MetaPingData *ping_data;
  unsigned int check_alive_timeout;

  check_alive_timeout = meta_prefs_get_check_alive_timeout ();
  if (check_alive_timeout == 0)
    return;

  if (serial == 0)
    {
      meta_warning ("Tried to ping window %s with a bad serial! Not allowed.",
                    window->desc);
      return;
    }

  if (!meta_window_can_ping (window))
    return;

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

      if (window == ping_data->window)
        {
          meta_topic (META_DEBUG_PING,
                      "Window %s already is being pinged with serial %u",
                      window->desc, ping_data->serial);
          return;
        }

      if (serial == ping_data->serial)
        {
          meta_warning ("Ping serial %u was reused for window %s, "
                        "previous use was for window %s.",
                        serial, window->desc, ping_data->window->desc);
          return;
        }
    }

  ping_data = g_new (MetaPingData, 1);
  ping_data->window = window;
  ping_data->serial = serial;
  ping_data->ping_timeout_id =
    g_timeout_add (check_alive_timeout,
                   meta_display_ping_timeout,
                   ping_data);
  g_source_set_name_by_id (ping_data->ping_timeout_id, "[mutter] meta_display_ping_timeout");

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

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

  META_WINDOW_GET_CLASS (window)->ping (window, serial);

  window->events_during_ping = 0;
}

/**
 * meta_display_pong_for_serial:
 * @display: the display we got the pong from
 * @serial: the serial in the pong response
 *
 * 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", serial);

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

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

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

          /* Remove the timeout */
          g_clear_handle_id (&ping_data->ping_timeout_id, g_source_remove);

          meta_window_set_alive (ping_data->window, TRUE);
          ping_data_free (ping_data);
          break;
        }
    }
}

static gboolean
in_tab_chain (MetaWindow  *window,
              MetaTabList  type)
{
  gboolean in_normal_tab_chain_type;
  gboolean in_normal_tab_chain;
  gboolean in_dock_tab_chain;
  gboolean in_group_tab_chain = FALSE;
#ifdef HAVE_X11_CLIENT
  MetaGroup *focus_group = NULL;
  MetaGroup *window_group = NULL;

  if (window->display->focus_window &&
      window->display->focus_window->client_type == META_WINDOW_CLIENT_TYPE_X11)
    focus_group = meta_window_x11_get_group (window->display->focus_window);

  if (window->client_type == META_WINDOW_CLIENT_TYPE_X11)
    window_group = meta_window_x11_get_group (window);

  in_group_tab_chain = meta_window_is_focusable (window) && (!focus_group || window_group == focus_group);
#endif

  in_normal_tab_chain_type = window->type != META_WINDOW_DOCK && window->type != META_WINDOW_DESKTOP;
  in_normal_tab_chain = meta_window_is_focusable (window) && in_normal_tab_chain_type && !window->skip_taskbar;
  in_dock_tab_chain = meta_window_is_focusable (window) && (!in_normal_tab_chain_type || window->skip_taskbar);

  return (type == META_TAB_LIST_NORMAL && in_normal_tab_chain)
         || (type == META_TAB_LIST_DOCKS && in_dock_tab_chain)
         || (type == META_TAB_LIST_GROUP && in_group_tab_chain)
         || (type == META_TAB_LIST_NORMAL_ALL && in_normal_tab_chain_type);
}

static MetaWindow*
find_tab_forward (MetaDisplay   *display,
                  MetaTabList    type,
                  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 (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,
                   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 (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_list_all_windows:
 * @display: a #MetaDisplay
 *
 * List all windows, including override-redirect ones. The windows are
 * in no particular order.
 *
 * Returns: (transfer container) (element-type Meta.Window): List of windows
 */
GList *
meta_display_list_all_windows (MetaDisplay *display)
{
  GList *all_windows = NULL;
  g_autoptr (GSList) windows = NULL;
  GSList *l;

  windows = meta_display_list_windows (display,
                                       META_LIST_INCLUDE_OVERRIDE_REDIRECT);

  /* Yay for mixing GList and GSList in the API */
  for (l = windows; l; l = l->next)
    all_windows = g_list_prepend (all_windows, l->data);
  return all_windows;
}

/**
 * meta_display_get_tab_list:
 * @display: a #MetaDisplay
 * @type: type of tab list
 * @workspace: (nullable): 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 contains windows that are on
 * @workspace or have the demands-attention hint set; otherwise it contains
 * all windows.
 *
 * Returns: (transfer container) (element-type Meta.Window): List of windows
 */
GList*
meta_display_get_tab_list (MetaDisplay   *display,
                           MetaTabList    type,
                           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. Collect unminimized windows
   * then minimized so minimized windows aren't in the way so much.
   */
  for (tmp = mru_list; tmp; tmp = tmp->next)
    {
      MetaWindow *window = tmp->data;

      if (!window->minimized && in_tab_chain (window, type))
        tab_list = g_list_prepend (tab_list, window);
    }

  for (tmp = mru_list; tmp; tmp = tmp->next)
    {
      MetaWindow *window = tmp->data;

      if (window->minimized && 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 &&
            !meta_window_located_on_workspace (l_window, 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
 * @workspace: origin workspace
 * @window: (nullable): 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,
                           MetaWorkspace *workspace,
                           MetaWindow    *window,
                           gboolean       backward)
{
  gboolean skip;
  GList *tab_list;
  MetaWindow *ret;
  tab_list = meta_display_get_tab_list (display, type, workspace);

  if (tab_list == NULL)
    return NULL;

  if (window != NULL)
    {
      g_assert (window->display == display);

      if (backward)
        ret = find_tab_backward (display, type, workspace, g_list_find (tab_list, window), TRUE);
      else
        ret = find_tab_forward (display, type, 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, workspace, tab_list, skip);
      else
        ret = find_tab_forward (display, type, workspace, tab_list, skip);
    }

  g_list_free (tab_list);
  return ret;
}

/**
 * meta_display_get_tab_current:
 * @display: a #MetaDisplay
 * @type: type of tab list
 * @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,
                              MetaWorkspace *workspace)
{
  MetaWindow *window;

  window = display->focus_window;

  if (window != NULL &&
      in_tab_chain (window, type) &&
      (workspace == NULL ||
       meta_window_located_on_workspace (window, workspace)))
    return window;
  else
    return NULL;
}

MetaGravity
meta_resize_gravity_from_grab_op (MetaGrabOp op)
{
  MetaGravity gravity;

  op &= ~(META_GRAB_OP_WINDOW_FLAG_UNCONSTRAINED);

  gravity = -1;
  switch (op)
    {
    case META_GRAB_OP_RESIZING_SE:
    case META_GRAB_OP_KEYBOARD_RESIZING_SE:
      gravity = META_GRAVITY_NORTH_WEST;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_S:
    case META_GRAB_OP_RESIZING_S:
      gravity = META_GRAVITY_NORTH;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_SW:
    case META_GRAB_OP_RESIZING_SW:
      gravity = META_GRAVITY_NORTH_EAST;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_N:
    case META_GRAB_OP_RESIZING_N:
      gravity = META_GRAVITY_SOUTH;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_NE:
    case META_GRAB_OP_RESIZING_NE:
      gravity = META_GRAVITY_SOUTH_WEST;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_NW:
    case META_GRAB_OP_RESIZING_NW:
      gravity = META_GRAVITY_SOUTH_EAST;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_E:
    case META_GRAB_OP_RESIZING_E:
      gravity = META_GRAVITY_WEST;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_W:
    case META_GRAB_OP_RESIZING_W:
      gravity = META_GRAVITY_EAST;
      break;
    case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
      gravity = META_GRAVITY_CENTER;
      break;
    default:
      break;
    }

  return gravity;
}

#ifdef HAVE_X11_CLIENT
void
meta_display_manage_all_xwindows (MetaDisplay *display)
{
  guint64 *_children;
  guint64 *children;
  int n_children, i;

  meta_stack_freeze (display->stack);
  meta_stack_tracker_get_stack (display->stack_tracker, &_children, &n_children);

  /* Copy the stack as it will be modified as part of the loop */
  children = g_memdup2 (_children, sizeof (uint64_t) * n_children);

  for (i = 0; i < n_children; ++i)
    {
      if (!META_STACK_ID_IS_X11 (children[i]))
        continue;
      meta_window_x11_new (display, children[i], TRUE,
                           META_COMP_EFFECT_NONE);
    }

  g_free (children);
  meta_stack_thaw (display->stack);
}
#endif

void
meta_display_unmanage_windows (MetaDisplay *display,
                               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;

  return meta_stack_windows_cmp (aw->display->stack, aw, bw);
}

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

static void
prefs_changed_callback (MetaPreference pref,
                        void          *data)
{
  MetaDisplay *display = data;

  switch (pref)
    {
    case META_PREF_DRAGGABLE_BORDER_WIDTH:
      meta_display_queue_retheme_all_windows (display);
      break;
    case META_PREF_CURSOR_THEME:
    case META_PREF_CURSOR_SIZE:
      meta_display_reload_cursor (display);
      break;
    default:
      break;
    }
}

void
meta_display_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...",
                    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...",
                    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...",
                            window->desc, window->net_wm_user_time);
              window->net_wm_user_time_set = FALSE;
              meta_window_set_user_time (window, timestamp);
            }

          tmp = tmp->next;
        }

      g_slist_free (windows);
    }
}

void
meta_display_remove_autoraise_callback (MetaDisplay *display)
{
  g_clear_handle_id (&display->autoraise_timeout_id, g_source_remove);
  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,
                                   const ClutterKeyEvent *event)
{
  g_signal_emit (display, display_signals[ACCELERATOR_ACTIVATED], 0,
                 action,
                 clutter_event_get_source_device ((const ClutterEvent *) event),
                 clutter_event_get_time ((const ClutterEvent *) event));
}

gboolean
meta_display_modifiers_accelerator_activate (MetaDisplay *display)
{
  gboolean freeze;

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

  return freeze;
}

/**
 * meta_display_get_context:
 * @display: a #MetaDisplay
 *
 * Returns: (transfer none): the #MetaContext
 */
MetaContext *
meta_display_get_context (MetaDisplay *display)
{
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);

  return priv->context;
}

/**
 * meta_display_get_compositor:
 * @display: a #MetaDisplay
 *
 * Returns: (transfer none): the #MetaCompositor
 */
MetaCompositor *
meta_display_get_compositor (MetaDisplay *display)
{
  return display->compositor;
}

/**
 * meta_display_get_x11_display: (skip)
 * @display: a #MetaDisplay
 *
 */
MetaX11Display *
meta_display_get_x11_display (MetaDisplay *display)
{
  return display->x11_display;
}

/**
 * meta_display_get_size:
 * @display: A #MetaDisplay
 * @width: (out): The width of the screen
 * @height: (out): The height of the screen
 *
 * Retrieve the size of the display.
 */
void
meta_display_get_size (MetaDisplay *display,
                       int         *width,
                       int         *height)
{
  MetaBackend *backend = backend_from_display (display);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  int display_width, display_height;

  meta_monitor_manager_get_screen_size (monitor_manager,
                                        &display_width,
                                        &display_height);

  if (width != NULL)
    *width = display_width;

  if (height != NULL)
    *height = display_height;
}

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

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

MetaGestureTracker *
meta_display_get_gesture_tracker (MetaDisplay *display)
{
  return display->gesture_tracker;
}

gboolean
meta_display_show_restart_message (MetaDisplay *display,
                                   const char  *message)
{
  gboolean result = FALSE;

  g_signal_emit (display,
                 display_signals[SHOW_RESTART_MESSAGE], 0,
                 message, &result);

  return result;
}

gboolean
meta_display_request_restart (MetaDisplay *display)
{
  gboolean result = FALSE;

  g_signal_emit (display,
                 display_signals[RESTART], 0,
                 &result);

  return result;
}

gboolean
meta_display_show_resize_popup (MetaDisplay  *display,
                                gboolean      show,
                                MtkRectangle *rect,
                                int           display_w,
                                int           display_h)
{
  gboolean result = FALSE;

  g_signal_emit (display,
                 display_signals[SHOW_RESIZE_POPUP], 0,
                 show, rect, display_w, display_h, &result);

  return result;
}

/**
 * meta_display_is_pointer_emulating_sequence:
 * @display: the display
 * @sequence: (nullable): a #ClutterEventSequence
 *
 * Tells whether the event sequence is the used for pointer emulation
 * and single-touch interaction.
 *
 * Returns: #TRUE if the sequence emulates pointer behavior
 **/
gboolean
meta_display_is_pointer_emulating_sequence (MetaDisplay          *display,
                                            ClutterEventSequence *sequence)
{
  if (!sequence)
    return FALSE;

  return display->pointer_emulating_sequence == sequence;
}

void
meta_display_request_pad_osd (MetaDisplay        *display,
                              ClutterInputDevice *pad,
                              gboolean            edition_mode)
{
  MetaBackend *backend = backend_from_display (display);
  MetaInputMapper *input_mapper;
  const gchar *layout_path = NULL;
  ClutterActor *osd;
  MetaLogicalMonitor *logical_monitor = NULL;
  GSettings *settings = NULL;
#ifdef HAVE_LIBWACOM
  WacomDevice *wacom_device;
#endif

  /* Avoid emitting the signal while there is an OSD being currently
   * displayed, the first OSD will have to be dismissed before showing
   * any other one.
   */
  if (display->current_pad_osd)
    return;

  input_mapper = meta_backend_get_input_mapper (backend_from_display (display));

  if (input_mapper)
    {
      settings = meta_input_mapper_get_tablet_settings (input_mapper, pad);
      logical_monitor =
        meta_input_mapper_get_device_logical_monitor (input_mapper, pad);
#ifdef HAVE_LIBWACOM
      wacom_device = meta_input_device_get_wacom_device (META_INPUT_DEVICE (pad));
      layout_path = libwacom_get_layout_filename (wacom_device);
#endif
    }

  if (!layout_path || !settings)
    return;

  if (!logical_monitor)
    logical_monitor = meta_backend_get_current_logical_monitor (backend);

  g_signal_emit (display, display_signals[SHOW_PAD_OSD], 0,
                 pad, settings, layout_path,
                 edition_mode, logical_monitor->number, &osd);

  if (osd)
    {
      display->current_pad_osd = osd;
      g_object_add_weak_pointer (G_OBJECT (display->current_pad_osd),
                                 (gpointer *) &display->current_pad_osd);
    }
}

char *
meta_display_get_pad_button_label (MetaDisplay        *display,
                                   ClutterInputDevice *pad,
                                   int                 button)
{
  char *label;

  /* First, lookup the action, as imposed by settings */
  label = meta_pad_action_mapper_get_button_label (display->pad_action_mapper,
                                                   pad, button);
  if (label)
    return label;

#ifdef HAVE_WAYLAND
  /* Second, if this wayland, lookup the actions set by the clients */
  if (meta_is_wayland_compositor ())
    {
      MetaWaylandCompositor *compositor;
      MetaWaylandTabletSeat *tablet_seat;
      MetaWaylandTabletPad *tablet_pad = NULL;

      compositor = wayland_compositor_from_display (display);
      tablet_seat = meta_wayland_tablet_manager_ensure_seat (compositor->tablet_manager,
                                                             compositor->seat);
      if (tablet_seat)
        tablet_pad = meta_wayland_tablet_seat_lookup_pad (tablet_seat, pad);

      if (tablet_pad)
        {
          label = meta_wayland_tablet_pad_get_button_label (tablet_pad,
                                                            button);
        }

      if (label)
        return label;
    }
#endif

  return NULL;
}

char *
meta_display_get_pad_feature_label (MetaDisplay        *display,
                                    ClutterInputDevice *pad,
                                    MetaPadFeatureType  feature,
                                    MetaPadDirection    direction,
                                    int                 feature_number)
{
  char *label;

  /* First, lookup the action, as imposed by settings */
  label = meta_pad_action_mapper_get_feature_label (display->pad_action_mapper,
                                                    pad, feature,
                                                    direction,
                                                    feature_number);
  if (label)
    return label;

#ifdef HAVE_WAYLAND
  /* Second, if this wayland, lookup the actions set by the clients */
  if (meta_is_wayland_compositor ())
    {
      MetaWaylandCompositor *compositor;
      MetaWaylandTabletSeat *tablet_seat;
      MetaWaylandTabletPad *tablet_pad = NULL;

      compositor = wayland_compositor_from_display (display);
      tablet_seat = meta_wayland_tablet_manager_ensure_seat (compositor->tablet_manager,
                                                             compositor->seat);
      if (tablet_seat)
        tablet_pad = meta_wayland_tablet_seat_lookup_pad (tablet_seat, pad);

      if (tablet_pad)
        {
          label = meta_wayland_tablet_pad_get_feature_label (tablet_pad,
                                                             feature,
                                                             feature_number);
        }

      if (label)
        return label;
    }
#endif

  return NULL;
}

static void
meta_display_show_osd (MetaDisplay *display,
                       gint         monitor_idx,
                       const gchar *icon_name,
                       const gchar *message)
{
  g_signal_emit (display, display_signals[SHOW_OSD], 0,
                 monitor_idx, icon_name, message);
}

static gint
lookup_tablet_monitor (MetaDisplay        *display,
                       ClutterInputDevice *device)
{
  MetaInputMapper *input_mapper;
  MetaLogicalMonitor *monitor;
  gint monitor_idx = -1;

  input_mapper = meta_backend_get_input_mapper (backend_from_display (display));
  if (!input_mapper)
    return -1;

  monitor = meta_input_mapper_get_device_logical_monitor (input_mapper, device);

  if (monitor)
    {
      monitor_idx = meta_display_get_monitor_index_for_rect (display,
                                                             &monitor->rect);
    }

  return monitor_idx;
}

void
meta_display_show_tablet_mapping_notification (MetaDisplay        *display,
                                               ClutterInputDevice *pad,
                                               const gchar        *pretty_name)
{
  if (!pretty_name)
    pretty_name = clutter_input_device_get_device_name (pad);
  meta_display_show_osd (display, lookup_tablet_monitor (display, pad),
                         "input-tablet-symbolic", pretty_name);
}

void
meta_display_notify_pad_group_switch (MetaDisplay        *display,
                                      ClutterInputDevice *pad,
                                      const gchar        *pretty_name,
                                      guint               n_group,
                                      guint               n_mode,
                                      guint               n_modes)
{
  GString *message;
  guint i;

  if (!pretty_name)
    pretty_name = clutter_input_device_get_device_name (pad);

  message = g_string_new (pretty_name);
  g_string_append (message, "\n\n");
  for (i = 0; i < n_modes; i++)
    {
      if (i > 0)
        g_string_append_c (message, ' ');
      g_string_append (message, (i == n_mode) ? "●" : "○");
    }

  meta_display_show_osd (display, lookup_tablet_monitor (display, pad),
                         "input-tablet-symbolic", message->str);

  g_signal_emit (display, display_signals[PAD_MODE_SWITCH], 0, pad,
                 n_group, n_mode);

  g_string_free (message, TRUE);
}

static void
meta_display_foreach_window (MetaDisplay           *display,
                             MetaListWindowsFlags   flags,
                             MetaDisplayWindowFunc  func,
                             gpointer               data)
{
  GSList *windows;

  /* If we end up doing this often, just keeping a list
   * of windows might be sensible.
   */

  windows = meta_display_list_windows (display, flags);

  g_slist_foreach (windows, (GFunc) func, data);

  g_slist_free (windows);
}

static void
meta_display_resize_func (MetaWindow *window,
                          gpointer    user_data)
{
  if (window->struts)
    {
      meta_window_update_struts (window);
    }
  meta_window_queue (window, META_QUEUE_MOVE_RESIZE);

  meta_window_recalc_features (window);
}

static void
on_monitors_changed_internal (MetaMonitorManager *monitor_manager,
                              MetaDisplay        *display)
{
  meta_workspace_manager_reload_work_areas (display->workspace_manager);

  /* Fix up monitor for all windows on this display */
  meta_display_foreach_window (display, META_LIST_INCLUDE_OVERRIDE_REDIRECT,
                               (MetaDisplayWindowFunc)
                               meta_window_update_for_monitors_changed, 0);

  /* Queue a resize on all the windows */
  meta_display_foreach_window (display, META_LIST_DEFAULT,
                               meta_display_resize_func, 0);

  meta_display_queue_check_fullscreen (display);
}

void
meta_display_restacked (MetaDisplay *display)
{
  g_signal_emit (display, display_signals[RESTACKED], 0);
}

static MetaStartupSequence *
find_startup_sequence_by_wmclass (MetaDisplay *display,
                                  MetaWindow  *window)
{
  GSList *startup_sequences, *l;

  startup_sequences =
    meta_startup_notification_get_sequences (display->startup_notification);

  for (l = startup_sequences; l; l = l->next)
    {
      MetaStartupSequence *sequence = l->data;
      const char *wmclass;

      wmclass = meta_startup_sequence_get_wmclass (sequence);

      if (wmclass != NULL &&
          ((window->res_class &&
            strcmp (wmclass, window->res_class) == 0) ||
           (window->res_name &&
            strcmp (wmclass, window->res_name) == 0)))
        return sequence;
    }

  return NULL;
}

/* Sets the initial_timestamp and initial_workspace properties
 * of a window according to information given us by the
 * startup-notification library.
 *
 * Returns TRUE if startup properties have been applied, and
 * FALSE if they have not (for example, if they had already
 * been applied.)
 */
gboolean
meta_display_apply_startup_properties (MetaDisplay *display,
                                       MetaWindow  *window)
{
  const char *startup_id;
  MetaStartupSequence *sequence = NULL;

  /* Does the window have a startup ID stored? */
  startup_id = meta_window_get_startup_id (window);

  meta_topic (META_DEBUG_STARTUP,
              "Applying startup props to %s id \"%s\"",
              window->desc,
              startup_id ? startup_id : "(none)");

  if (!startup_id)
    {
      /* No startup ID stored for the window. Let's ask the
       * startup-notification library whether there's anything
       * stored for the resource name or resource class hints.
       */
      sequence = find_startup_sequence_by_wmclass (display, window);

      if (sequence)
        {
          g_assert (window->startup_id == NULL);
          window->startup_id = g_strdup (meta_startup_sequence_get_id (sequence));
          startup_id = window->startup_id;

          meta_topic (META_DEBUG_STARTUP,
                      "Ending legacy sequence %s due to window %s",
                      meta_startup_sequence_get_id (sequence),
                      window->desc);

          meta_startup_sequence_complete (sequence);
        }
    }

  /* Still no startup ID? Bail. */
  if (!startup_id)
    return FALSE;

  /* We might get this far and not know the sequence ID (if the window
   * already had a startup ID stored), so let's look for one if we don't
   * already know it.
   */
  if (sequence == NULL)
    {
      sequence =
        meta_startup_notification_lookup_sequence (display->startup_notification,
                                                   startup_id);
    }

  if (sequence != NULL)
    {
      gboolean changed_something = FALSE;

      meta_topic (META_DEBUG_STARTUP,
                  "Found startup sequence for window %s ID \"%s\"",
                  window->desc, startup_id);

      if (!window->initial_workspace_set)
        {
          int space = meta_startup_sequence_get_workspace (sequence);
          if (space >= 0)
            {
              meta_topic (META_DEBUG_STARTUP,
                          "Setting initial window workspace to %d based on startup info",
                          space);

              window->initial_workspace_set = TRUE;
              window->initial_workspace = space;
              changed_something = TRUE;
            }
        }

      if (!window->initial_timestamp_set)
        {
          guint32 timestamp = meta_startup_sequence_get_timestamp (sequence);
          meta_topic (META_DEBUG_STARTUP,
                      "Setting initial window timestamp to %u based on startup info",
                      timestamp);

          window->initial_timestamp_set = TRUE;
          window->initial_timestamp = timestamp;
          changed_something = TRUE;
        }

      return changed_something;
    }
  else
    {
      meta_topic (META_DEBUG_STARTUP,
                  "Did not find startup sequence for window %s ID \"%s\"",
                  window->desc, startup_id);
    }

  return FALSE;
}

static gboolean
set_work_area_later_func (MetaDisplay *display)
{
  meta_topic (META_DEBUG_WORKAREA,
              "Running work area hint computation function");

  display->work_area_later = 0;

  g_signal_emit (display, display_signals[WORKAREAS_CHANGED], 0);

  return FALSE;
}

void
meta_display_queue_workarea_recalc (MetaDisplay *display)
{
  /* Recompute work area later before redrawing */
  if (display->work_area_later == 0)
    {
      MetaLaters *laters = meta_compositor_get_laters (display->compositor);

      meta_topic (META_DEBUG_WORKAREA,
                  "Adding work area hint computation function");
      display->work_area_later =
        meta_laters_add (laters, META_LATER_BEFORE_REDRAW,
                         (GSourceFunc) set_work_area_later_func,
                         display,
                         NULL);
    }
}

static gboolean
check_fullscreen_func (gpointer data)
{
  MetaDisplay *display = data;
  MetaBackend *backend = backend_from_display (display);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  GList *logical_monitors, *l;
  MetaWindow *window;
  GSList *fullscreen_monitors = NULL;
  GSList *obscured_monitors = NULL;
  gboolean in_fullscreen_changed = FALSE;

  display->check_fullscreen_later = 0;

  logical_monitors =
    meta_monitor_manager_get_logical_monitors (monitor_manager);

  /* We consider a monitor in fullscreen if it contains a fullscreen window;
   * however we make an exception for maximized windows above the fullscreen
   * one, as in that case window+chrome fully obscure the fullscreen window.
   */
  for (window = meta_stack_get_top (display->stack);
       window;
       window = meta_stack_get_below (display->stack, window, FALSE))
    {
      gboolean covers_monitors = FALSE;

      if (window->hidden)
        continue;

      if (window->fullscreen)
        {
          covers_monitors = TRUE;
        }
      else if (window->override_redirect)
        {
          /* We want to handle the case where an application is creating an
           * override-redirect window the size of the screen (monitor) and treat
           * it similarly to a fullscreen window, though it doesn't have fullscreen
           * window management behavior. (Being O-R, it's not managed at all.)
           */
          if (meta_window_is_monitor_sized (window))
            covers_monitors = TRUE;
        }
      else if (window->maximized_horizontally &&
               window->maximized_vertically)
        {
          MetaLogicalMonitor *logical_monitor;

          logical_monitor = meta_window_get_main_logical_monitor (window);
          if (!g_slist_find (obscured_monitors, logical_monitor))
            obscured_monitors = g_slist_prepend (obscured_monitors,
                                                 logical_monitor);
        }

      if (covers_monitors)
        {
          MtkRectangle window_rect;

          meta_window_get_frame_rect (window, &window_rect);

          for (l = logical_monitors; l; l = l->next)
            {
              MetaLogicalMonitor *logical_monitor = l->data;

              if (mtk_rectangle_overlap (&window_rect,
                                         &logical_monitor->rect) &&
                  !g_slist_find (fullscreen_monitors, logical_monitor) &&
                  !g_slist_find (obscured_monitors, logical_monitor))
                fullscreen_monitors = g_slist_prepend (fullscreen_monitors,
                                                       logical_monitor);
            }
        }
    }

  g_slist_free (obscured_monitors);

  for (l = logical_monitors; l; l = l->next)
    {
      MetaLogicalMonitor *logical_monitor = l->data;
      gboolean in_fullscreen;

      in_fullscreen = g_slist_find (fullscreen_monitors,
                                    logical_monitor) != NULL;
      if (in_fullscreen != logical_monitor->in_fullscreen)
        {
          logical_monitor->in_fullscreen = in_fullscreen;
          in_fullscreen_changed = TRUE;
        }
    }

  g_slist_free (fullscreen_monitors);

  if (in_fullscreen_changed)
    {
      /* DOCK window stacking depends on the monitor's fullscreen
         status so we need to trigger a re-layering. */
      MetaWindow *window = meta_stack_get_top (display->stack);
      if (window)
        meta_stack_update_layer (display->stack, window);

      g_signal_emit (display, display_signals[IN_FULLSCREEN_CHANGED], 0, NULL);
    }

  return FALSE;
}

void
meta_display_queue_check_fullscreen (MetaDisplay *display)
{
  MetaLaters *laters;

  if (display->check_fullscreen_later)
    return;

  laters = meta_compositor_get_laters (display->compositor);
  display->check_fullscreen_later = meta_laters_add (laters,
                                                     META_LATER_CHECK_FULLSCREEN,
                                                     check_fullscreen_func,
                                                     display, NULL);
}

int
meta_display_get_monitor_index_for_rect (MetaDisplay  *display,
                                         MtkRectangle *rect)
{
  MetaBackend *backend = backend_from_display (display);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaLogicalMonitor *logical_monitor;

  logical_monitor =
    meta_monitor_manager_get_logical_monitor_from_rect (monitor_manager, rect);
  if (!logical_monitor)
    return -1;

  return logical_monitor->number;
}

int
meta_display_get_monitor_neighbor_index (MetaDisplay         *display,
                                         int                  which_monitor,
                                         MetaDisplayDirection direction)
{
  MetaBackend *backend = backend_from_display (display);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaLogicalMonitor *logical_monitor;
  MetaLogicalMonitor *neighbor;

  logical_monitor =
    meta_monitor_manager_get_logical_monitor_from_number (monitor_manager,
                                                          which_monitor);
  neighbor = meta_monitor_manager_get_logical_monitor_neighbor (monitor_manager,
                                                                logical_monitor,
                                                                direction);
  return neighbor ? neighbor->number : -1;
}

/**
 * meta_display_get_current_monitor:
 * @display: a #MetaDisplay
 *
 * Gets the index of the monitor that currently has the mouse pointer.
 *
 * Return value: a monitor index
 */
int
meta_display_get_current_monitor (MetaDisplay *display)
{
  MetaBackend *backend = backend_from_display (display);
  MetaLogicalMonitor *logical_monitor;

  logical_monitor = meta_backend_get_current_logical_monitor (backend);

  /* Pretend its the first when there is no actual current monitor. */
  if (!logical_monitor)
    return 0;

  return logical_monitor->number;
}

/**
 * meta_display_get_n_monitors:
 * @display: a #MetaDisplay
 *
 * Gets the number of monitors that are joined together to form @display.
 *
 * Return value: the number of monitors
 */
int
meta_display_get_n_monitors (MetaDisplay *display)
{
  MetaBackend *backend = backend_from_display (display);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);

  g_return_val_if_fail (META_IS_DISPLAY (display), 0);

  return meta_monitor_manager_get_num_logical_monitors (monitor_manager);
}

/**
 * meta_display_get_primary_monitor:
 * @display: a #MetaDisplay
 *
 * Gets the index of the primary monitor on this @display.
 *
 * Return value: a monitor index
 */
int
meta_display_get_primary_monitor (MetaDisplay *display)
{
  MetaBackend *backend = backend_from_display (display);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaLogicalMonitor *logical_monitor;

  g_return_val_if_fail (META_IS_DISPLAY (display), 0);

  logical_monitor =
    meta_monitor_manager_get_primary_logical_monitor (monitor_manager);
  if (logical_monitor)
    return logical_monitor->number;
  else
    return 0;
}

/**
 * meta_display_get_monitor_geometry:
 * @display: a #MetaDisplay
 * @monitor: the monitor number
 * @geometry: (out): location to store the monitor geometry
 *
 * Stores the location and size of the indicated @monitor in @geometry.
 */
void
meta_display_get_monitor_geometry (MetaDisplay  *display,
                                   int           monitor,
                                   MtkRectangle *geometry)
{
  MetaBackend *backend = backend_from_display (display);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaLogicalMonitor *logical_monitor;
#ifndef G_DISABLE_CHECKS
  int n_logical_monitors =
    meta_monitor_manager_get_num_logical_monitors (monitor_manager);
#endif

  g_return_if_fail (META_IS_DISPLAY (display));
  g_return_if_fail (monitor >= 0 && monitor < n_logical_monitors);
  g_return_if_fail (geometry != NULL);

  logical_monitor =
    meta_monitor_manager_get_logical_monitor_from_number (monitor_manager,
                                                          monitor);
  *geometry = logical_monitor->rect;
}

/**
 * meta_display_get_monitor_scale:
 * @display: a #MetaDisplay
 * @monitor: the monitor number
 *
 * Gets the monitor scaling value for the given @monitor.
 *
 * Return value: the monitor scaling value
 */
float
meta_display_get_monitor_scale (MetaDisplay *display,
                                int          monitor)
{
  MetaBackend *backend = backend_from_display (display);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaLogicalMonitor *logical_monitor;
#ifndef G_DISABLE_CHECKS
  int n_logical_monitors =
    meta_monitor_manager_get_num_logical_monitors (monitor_manager);
#endif

  g_return_val_if_fail (META_IS_DISPLAY (display), 1.0f);
  g_return_val_if_fail (monitor >= 0 && monitor < n_logical_monitors, 1.0f);

  logical_monitor =
    meta_monitor_manager_get_logical_monitor_from_number (monitor_manager,
                                                          monitor);
  return logical_monitor->scale;
}

/**
 * meta_display_get_monitor_in_fullscreen:
 * @display: a #MetaDisplay
 * @monitor: the monitor number
 *
 * Determines whether there is a fullscreen window obscuring the specified
 * monitor. If there is a fullscreen window, the desktop environment will
 * typically hide any controls that might obscure the fullscreen window.
 *
 * You can get notification when this changes by connecting to
 * MetaDisplay::in-fullscreen-changed.
 *
 * Returns: %TRUE if there is a fullscreen window covering the specified monitor.
 */
gboolean
meta_display_get_monitor_in_fullscreen (MetaDisplay *display,
                                        int          monitor)
{
  MetaBackend *backend = backend_from_display (display);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaLogicalMonitor *logical_monitor;
#ifndef G_DISABLE_CHECKS
  int n_logical_monitors =
    meta_monitor_manager_get_num_logical_monitors (monitor_manager);
#endif

  g_return_val_if_fail (META_IS_DISPLAY (display), FALSE);
  g_return_val_if_fail (monitor >= 0 &&
                        monitor < n_logical_monitors, FALSE);

  logical_monitor =
    meta_monitor_manager_get_logical_monitor_from_number (monitor_manager,
                                                          monitor);

  /* We use -1 as a flag to mean "not known yet" for notification
  purposes */ return logical_monitor->in_fullscreen == TRUE;
}

void
meta_display_focus_default_window (MetaDisplay *display,
                                   guint32      timestamp)
{
  MetaWorkspaceManager *workspace_manager = display->workspace_manager;

  meta_workspace_focus_default_window (workspace_manager->active_workspace,
                                       NULL,
                                       timestamp);
}

/**
 * meta_display_get_workspace_manager:
 * @display: a #MetaDisplay
 *
 * Returns: (transfer none): The workspace manager of the display
 */
MetaWorkspaceManager *
meta_display_get_workspace_manager (MetaDisplay *display)
{
  return display->workspace_manager;
}

MetaStartupNotification *
meta_display_get_startup_notification (MetaDisplay *display)
{
  return display->startup_notification;
}

MetaWindow *
meta_display_get_window_from_id (MetaDisplay *display,
                                 uint64_t     window_id)
{
  g_autoptr (GSList) windows = NULL;
  GSList *l;

  windows = meta_display_list_windows (display, META_LIST_DEFAULT);
  for (l = windows; l; l = l->next)
    {
      MetaWindow *window = l->data;

      if (window->id == window_id)
        return window;
    }

  return NULL;
}

uint64_t
meta_display_generate_window_id (MetaDisplay *display)
{
  static uint64_t base_window_id;
  static uint64_t last_window_id;

  if (!base_window_id)
    base_window_id = g_random_int () + 1;

  /* We can overflow here, that's fine */
  return (base_window_id + last_window_id++);
}

/**
 * meta_display_get_sound_player:
 * @display: a #MetaDisplay
 *
 * Returns: (transfer none): The sound player of the display
 */
MetaSoundPlayer *
meta_display_get_sound_player (MetaDisplay *display)
{
  return display->sound_player;
}

/**
 * meta_display_get_selection:
 * @display: a #MetaDisplay
 *
 * Returns: (transfer none): The selection manager of the display
 */
MetaSelection *
meta_display_get_selection (MetaDisplay *display)
{
  return display->selection;
}

#ifdef WITH_VERBOSE_MODE
static const char* meta_window_queue_names[META_N_QUEUE_TYPES] =
  {
    "calc-showing",
    "move-resize",
  };
#endif

static int
window_stack_cmp (gconstpointer a,
                  gconstpointer b)
{
  MetaWindow *aw = (gpointer) a;
  MetaWindow *bw = (gpointer) b;

  return meta_stack_windows_cmp (aw->display->stack,
                                 aw, bw);
}

static void
warn_on_incorrectly_unmanaged_window (MetaWindow *window)
{
  g_warn_if_fail (!window->unmanaging);
}

static void
update_window_visibilities (MetaDisplay *display,
                            GList       *windows)
{
  g_autoptr (GList) unplaced = NULL;
  g_autoptr (GList) should_show = NULL;
  g_autoptr (GList) should_hide = NULL;
  GList *l;

  COGL_TRACE_BEGIN_SCOPED (MetaDisplayUpdateVisibility,
                           "Meta::Display::update_window_visibilities()");

  for (l = windows; l; l = l->next)
    {
      MetaWindow *window = l->data;

      if (!window->placed)
        unplaced = g_list_prepend (unplaced, window);
      else if (meta_window_should_be_showing (window))
        should_show = g_list_prepend (should_show, window);
      else
        should_hide = g_list_prepend (should_hide, window);
    }

  /* Sort bottom to top */
  unplaced = g_list_sort (unplaced, window_stack_cmp);
  should_hide = g_list_sort (should_hide, window_stack_cmp);

  /* Sort top to bottom */
  should_show = g_list_sort (should_show, window_stack_cmp);
  should_show = g_list_reverse (should_show);

  COGL_TRACE_BEGIN_SCOPED (MetaDisplayShowUnplacedWindows,
                           "Meta::Display::update_window_visibilities#show_unplaced()");
  g_list_foreach (unplaced, (GFunc) meta_window_update_visibility, NULL);
  COGL_TRACE_END (MetaDisplayShowUnplacedWindows);

  meta_stack_freeze (display->stack);

  COGL_TRACE_BEGIN_SCOPED (MetaDisplayShowWindows,
                           "Meta::Display::update_window_visibilities#show()");
  g_list_foreach (should_show, (GFunc) meta_window_update_visibility, NULL);
  COGL_TRACE_END (MetaDisplayShowWindows);

  COGL_TRACE_BEGIN_SCOPED (MetaDisplayHideWindows,
                           "Meta::Display::update_window_visibilities#hide()");
  g_list_foreach (should_hide, (GFunc) meta_window_update_visibility, NULL);
  COGL_TRACE_END (MetaDisplayHideWindows);

  meta_stack_thaw (display->stack);

  g_list_foreach (windows, (GFunc) meta_window_clear_queued, NULL);

  g_signal_emit (display, display_signals[WINDOW_VISIBILITY_UPDATED], 0,
                 unplaced, should_show, should_hide);

  g_list_foreach (windows, (GFunc) warn_on_incorrectly_unmanaged_window, NULL);
}

static void
move_resize (MetaDisplay *display,
             GList       *windows)
{
  g_list_foreach (windows, (GFunc) meta_window_update_layout, NULL);
  g_list_foreach (windows, (GFunc) warn_on_incorrectly_unmanaged_window, NULL);
}

typedef void (* WindowQueueFunc) (MetaDisplay *display,
                                  GList       *windows);

static const WindowQueueFunc window_queue_func[META_N_QUEUE_TYPES] =
{
  update_window_visibilities,
  move_resize,
};

static gboolean
window_queue_run_later_func (gpointer user_data)
{
  MetaQueueRunData *run_data = user_data;
  MetaDisplay *display = run_data->display;
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);
  g_autoptr (GList) windows = NULL;
  int queue_idx = run_data->queue_idx;

  windows = g_steal_pointer (&priv->queue_windows[queue_idx]);

  priv->queue_later_ids[queue_idx] = 0;

  window_queue_func[queue_idx] (display, windows);

  return G_SOURCE_REMOVE;
}

void
meta_display_queue_window (MetaDisplay   *display,
                           MetaWindow    *window,
                           MetaQueueType  queue_types)
{
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);
  MetaCompositor *compositor = display->compositor;
  MetaLaters *laters = meta_compositor_get_laters (compositor);
  int queue_idx;

  for (queue_idx = 0; queue_idx < META_N_QUEUE_TYPES; queue_idx++)
    {
      const MetaLaterType window_queue_later_when[META_N_QUEUE_TYPES] =
        {
          META_LATER_CALC_SHOWING,
          META_LATER_RESIZE,
        };

      if (!(queue_types & 1 << queue_idx))
        continue;

      meta_topic (META_DEBUG_WINDOW_STATE,
                  "Queueing %s for window '%s'",
                  meta_window_queue_names[queue_idx],
                  meta_window_get_description (window));

      priv->queue_windows[queue_idx] =
        g_list_prepend (priv->queue_windows[queue_idx], window);

      if (!priv->queue_later_ids[queue_idx])
        {
          MetaQueueRunData *run_data;

          run_data = g_new0 (MetaQueueRunData, 1);
          run_data->display = display;
          run_data->queue_idx = queue_idx;
          priv->queue_later_ids[queue_idx] =
            meta_laters_add (laters,
                             window_queue_later_when[queue_idx],
                             window_queue_run_later_func,
                             run_data, g_free);
        }
    }
}

void
meta_display_unqueue_window (MetaDisplay   *display,
                             MetaWindow    *window,
                             MetaQueueType  queue_types)
{
  MetaDisplayPrivate *priv = meta_display_get_instance_private (display);
  MetaCompositor *compositor = display->compositor;
  MetaLaters *laters = meta_compositor_get_laters (compositor);
  int queue_idx;

  for (queue_idx = 0; queue_idx < META_N_QUEUE_TYPES; queue_idx++)
    {
      if (!(queue_types & 1 << queue_idx))
        continue;

      meta_topic (META_DEBUG_WINDOW_STATE,
                  "Unqueuing %s for window '%s'",
                  meta_window_queue_names[queue_idx],
                  window->desc);

      priv->queue_windows[queue_idx] =
        g_list_remove (priv->queue_windows[queue_idx], window);

      if (!priv->queue_windows[queue_idx] && priv->queue_later_ids[queue_idx])
        {
          meta_laters_remove (laters,
                              priv->queue_later_ids[queue_idx]);
          priv->queue_later_ids[queue_idx] = 0;
        }
    }
}

void
meta_display_flush_queued_window (MetaDisplay   *display,
                                  MetaWindow    *window,
                                  MetaQueueType  queue_types)
{
  g_autoptr (GList) windows = NULL;
  int queue_idx;

  meta_display_unqueue_window (display, window, queue_types);

  windows = g_list_prepend (windows, window);

  for (queue_idx = 0; queue_idx < META_N_QUEUE_TYPES; queue_idx++)
    {
      if (!(queue_types & 1 << queue_idx))
        continue;

      meta_topic (META_DEBUG_WINDOW_STATE,
                  "Running %s for window '%s'",
                  meta_window_queue_names[queue_idx],
                  window->desc);

      window_queue_func[queue_idx] (display, windows);
    }
}

typedef struct
{
  MetaDisplay *display;
  MetaWindow *window;
  int pointer_x;
  int pointer_y;
} MetaFocusData;

static void
meta_focus_data_free (MetaFocusData *focus_data)
{
  g_clear_object (&focus_data->window);
  g_free (focus_data);
}

static void
focus_mouse_mode (MetaDisplay *display,
                  MetaWindow  *window,
                  uint32_t     timestamp_ms)
{
  if (window && window->override_redirect)
    return;

  if (window && window->type != META_WINDOW_DESKTOP)
    {
      meta_topic (META_DEBUG_FOCUS,
                  "Focusing %s at time %u.", window->desc, timestamp_ms);

      meta_window_focus (window, timestamp_ms);

      if (meta_prefs_get_auto_raise ())
        meta_display_queue_autoraise_callback (display, window);
      else
        meta_topic (META_DEBUG_FOCUS, "Auto raise is disabled");
    }
  else
    {
      /* In mouse focus mode, we defocus when the mouse *enters*
       * the DESKTOP window, instead of defocusing on LeaveNotify.
       * This is because having the mouse enter override-redirect
       * child windows unfortunately causes LeaveNotify events that
       * we can't distinguish from the mouse actually leaving the
       * toplevel window as we expect.  But, since we filter out
       * EnterNotify events on override-redirect windows, this
       * alternative mechanism works great.
       */
      if (meta_prefs_get_focus_mode () == G_DESKTOP_FOCUS_MODE_MOUSE &&
          display->focus_window != NULL)
        {
          meta_topic (META_DEBUG_FOCUS,
                      "Unsetting focus from %s due to mouse entering "
                      "the DESKTOP window",
                      display->focus_window->desc);
          meta_display_unset_input_focus (display, timestamp_ms);
        }
    }
}

static gboolean
focus_on_pointer_rest_callback (gpointer data)
{
  MetaFocusData *focus_data = data;
  MetaWindow *window = focus_data->window;
  MetaDisplay *display = focus_data->display;
  MetaBackend *backend = backend_from_display (display);
  MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend);
  graphene_point_t point;
  uint32_t timestamp_ms;

  if (window && window->unmanaging)
    goto out;

  if (meta_prefs_get_focus_mode () == G_DESKTOP_FOCUS_MODE_CLICK)
    goto out;

  meta_cursor_tracker_get_pointer (cursor_tracker, &point, NULL);

  if ((int) point.x != focus_data->pointer_x ||
      (int) point.y != focus_data->pointer_y)
    {
      focus_data->pointer_x = point.x;
      focus_data->pointer_y = point.y;
      return G_SOURCE_CONTINUE;
    }

  if (window && !meta_window_has_pointer (window))
    goto out;

  timestamp_ms = meta_display_get_current_time_roundtrip (display);
  focus_mouse_mode (display, window, timestamp_ms);

 out:
  display->focus_timeout_id = 0;
  return G_SOURCE_REMOVE;
}

/* The interval, in milliseconds, we use in focus-follows-mouse
 * mode to check whether the pointer has stopped moving after a
 * crossing event.
 */
#define FOCUS_TIMEOUT_DELAY 25

static void
queue_pointer_rest_callback (MetaDisplay *display,
                             MetaWindow  *window,
                             int          pointer_x,
                             int          pointer_y)
{
  MetaFocusData *focus_data;

  focus_data = g_new (MetaFocusData, 1);
  focus_data->display = display;
  focus_data->window = NULL;
  focus_data->pointer_x = pointer_x;
  focus_data->pointer_y = pointer_y;

  if (window)
    focus_data->window = g_object_ref (window);

  g_clear_handle_id (&display->focus_timeout_id, g_source_remove);

  display->focus_timeout_id =
    g_timeout_add_full (G_PRIORITY_DEFAULT,
                        FOCUS_TIMEOUT_DELAY,
                        focus_on_pointer_rest_callback,
                        focus_data,
                        (GDestroyNotify) meta_focus_data_free);
  g_source_set_name_by_id (display->focus_timeout_id,
                           "[mutter] focus_on_pointer_rest_callback");
}

void
meta_display_handle_window_enter (MetaDisplay *display,
                                  MetaWindow  *window,
                                  uint32_t     timestamp_ms,
                                  int          root_x,
                                  int          root_y)
{
  switch (meta_prefs_get_focus_mode ())
    {
    case G_DESKTOP_FOCUS_MODE_SLOPPY:
    case G_DESKTOP_FOCUS_MODE_MOUSE:
      display->mouse_mode = TRUE;
      if (!window || window->type != META_WINDOW_DOCK)
        {
          if (meta_prefs_get_focus_change_on_pointer_rest ())
            queue_pointer_rest_callback (display, window, root_x, root_y);
          else
            focus_mouse_mode (display, window, timestamp_ms);
        }
      break;
    case G_DESKTOP_FOCUS_MODE_CLICK:
      break;
    }

  if (window && window->type == META_WINDOW_DOCK)
    meta_window_raise (window);
}

void
meta_display_handle_window_leave (MetaDisplay *display,
                                  MetaWindow  *window)
{
  if (window && window->type == META_WINDOW_DOCK && !window->has_focus)
    meta_window_lower (window);
}
