/* GDK - The GIMP Drawing Kit
 * gdkdisplay.c
 *
 * Copyright 2001 Sun Microsystems Inc.
 *
 * Erwann Chenede <erwann.chenede@sun.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "gdkdisplay.h"
#include "gdkdisplayprivate.h"

#include <glib/gi18n-lib.h>
#include "gdkprivate.h"

#include "gdkapplaunchcontext.h"
#include "gdkclipboardprivate.h"
#include "gdkdeviceprivate.h"
#include "gdkdisplaymanagerprivate.h"
#include "gdkdmabufeglprivate.h"
#include "gdkdmabufformatsbuilderprivate.h"
#include "gdkdmabufformatsprivate.h"
#include "gdkdmabuftextureprivate.h"
#include "gdkeventsprivate.h"
#include "gdkframeclockidleprivate.h"
#include "gdkglcontextprivate.h"
#include "gdkmonitorprivate.h"
#include "gdkrectangle.h"
#include "gdkseatprivate.h"
#include "gdkvulkancontextprivate.h"

#ifdef HAVE_EGL
#include <epoxy/egl.h>
#endif

#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#endif

#include <math.h>
#include <stdlib.h>

/**
 * GdkDisplay:
 *
 * A representation of a workstation.
 *
 * Their purpose are two-fold:
 *
 * - To manage and provide information about input devices (pointers, keyboards, etc)
 * - To manage and provide information about output devices (monitors, projectors, etc)
 *
 * Most of the input device handling has been factored out into separate
 * [class@Gdk.Seat] objects. Every display has a one or more seats, which
 * can be accessed with [method@Gdk.Display.get_default_seat] and
 * [method@Gdk.Display.list_seats].
 *
 * Output devices are represented by [class@Gdk.Monitor] objects, which can
 * be accessed with [method@Gdk.Display.get_monitor_at_surface] and similar APIs.
 */

enum
{
  PROP_0,
  PROP_COMPOSITED,
  PROP_RGBA,
  PROP_SHADOW_WIDTH,
  PROP_INPUT_SHAPES,
  PROP_DMABUF_FORMATS,
  LAST_PROP
};

static GParamSpec *props[LAST_PROP] = { NULL, };

enum {
  OPENED,
  CLOSED,
  SEAT_ADDED,
  SEAT_REMOVED,
  SETTING_CHANGED,
  LAST_SIGNAL
};

typedef struct _GdkDisplayPrivate GdkDisplayPrivate;

struct _GdkDisplayPrivate {
  /* The base context that all other contexts inherit from.
   * This context is never exposed to public API and is
   * allowed to have a %NULL surface.
   */
  GdkGLContext *gl_context;
  GError *gl_error;

#ifdef HAVE_EGL
  EGLDisplay egl_display;
  EGLConfig egl_config;
  EGLConfig egl_config_high_depth;
#endif

  guint rgba : 1;
  guint composited : 1;
  guint shadow_width: 1;
  guint input_shapes : 1;

  GdkDebugFlags debug_flags;
};

static void gdk_display_dispose     (GObject         *object);
static void gdk_display_finalize    (GObject         *object);


static GdkAppLaunchContext *gdk_display_real_get_app_launch_context (GdkDisplay *display);

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE_WITH_PRIVATE (GdkDisplay, gdk_display, G_TYPE_OBJECT)

static void
gdk_display_get_property (GObject    *object,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
{
  GdkDisplay *display = GDK_DISPLAY (object);

  switch (prop_id)
    {
    case PROP_COMPOSITED:
      g_value_set_boolean (value, gdk_display_is_composited (display));
      break;

    case PROP_RGBA:
      g_value_set_boolean (value, gdk_display_is_rgba (display));
      break;

    case PROP_SHADOW_WIDTH:
      g_value_set_boolean (value, gdk_display_supports_shadow_width (display));
      break;

    case PROP_INPUT_SHAPES:
      g_value_set_boolean (value, gdk_display_supports_input_shapes (display));
      break;

    case PROP_DMABUF_FORMATS:
      g_value_set_boxed (value, gdk_display_get_dmabuf_formats (display));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
gdk_display_real_make_default (GdkDisplay *display)
{
}

static GdkGLContext *
gdk_display_default_init_gl (GdkDisplay  *display,
                             GError     **error)
{
  g_set_error_literal (error, GDK_GL_ERROR, GDK_GL_ERROR_NOT_AVAILABLE,
                       _("The current backend does not support OpenGL"));

  return NULL;
}

static guint
gdk_display_default_rate_egl_config (GdkDisplay *display,
                                     gpointer    egl_display,
                                     gpointer    config)
{
  guint distance = 0;
#ifdef HAVE_EGL
  int tmp;

  if (!eglGetConfigAttrib (egl_display, config, EGL_SAMPLE_BUFFERS, &tmp) || tmp != 0)
    distance += 0x20000;

  if (!eglGetConfigAttrib (egl_display, config, EGL_DEPTH_SIZE, &tmp) || tmp != 0 ||
      !eglGetConfigAttrib (egl_display, config, EGL_STENCIL_SIZE, &tmp) || tmp != 0)
    distance += 0x10000;
#endif

  return distance;
}

static GdkSeat *
gdk_display_real_get_default_seat (GdkDisplay *display)
{
  if (!display->seats)
    return NULL;

  return display->seats->data;
}

static void
gdk_display_real_opened (GdkDisplay *display)
{
  _gdk_display_manager_add_display (gdk_display_manager_get (), display);
}

static void
gdk_display_class_init (GdkDisplayClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->finalize = gdk_display_finalize;
  object_class->dispose = gdk_display_dispose;
  object_class->get_property = gdk_display_get_property;

  class->make_default = gdk_display_real_make_default;
  class->get_app_launch_context = gdk_display_real_get_app_launch_context;
  class->init_gl = gdk_display_default_init_gl;
  class->rate_egl_config = gdk_display_default_rate_egl_config;
  class->get_default_seat = gdk_display_real_get_default_seat;
  class->opened = gdk_display_real_opened;

  /**
   * GdkDisplay:composited: (getter is_composited)
   *
   * %TRUE if the display properly composites the alpha channel.
   */
  props[PROP_COMPOSITED] =
    g_param_spec_boolean ("composited", NULL, NULL,
                          TRUE,
                          G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  /**
   * GdkDisplay:rgba: (getter is_rgba)
   *
   * %TRUE if the display supports an alpha channel.
   */
  props[PROP_RGBA] =
    g_param_spec_boolean ("rgba", NULL, NULL,
                          TRUE,
                          G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  /**
   * GdkDisplay:shadow-width: (getter supports_shadow_width)
   *
   * %TRUE if the display supports extensible frames.
   *
   * Since: 4.14
   */
  props[PROP_SHADOW_WIDTH] =
    g_param_spec_boolean ("shadow-width", NULL, NULL,
                          TRUE,
                          G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  /**
   * GdkDisplay:input-shapes: (getter supports_input_shapes)
   *
   * %TRUE if the display supports input shapes.
   */
  props[PROP_INPUT_SHAPES] =
    g_param_spec_boolean ("input-shapes", NULL, NULL,
                          TRUE,
                          G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  /**
   * GdkDisplay:dmabuf-formats:
   *
   * The dma-buf formats that are supported on this display
   *
   * Since: 4.14
   */
  props[PROP_DMABUF_FORMATS] =
    g_param_spec_boxed ("dmabuf-formats", NULL, NULL,
                        GDK_TYPE_DMABUF_FORMATS,
                        G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  g_object_class_install_properties (object_class, LAST_PROP, props);

  /**
   * GdkDisplay::opened:
   * @display: the object on which the signal is emitted
   *
   * Emitted when the connection to the windowing system for @display is opened.
   */
  signals[OPENED] =
    g_signal_new (g_intern_static_string ("opened"),
		  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GdkDisplayClass, opened),
                  NULL, NULL,
                  NULL,
                  G_TYPE_NONE, 0);

  /**
   * GdkDisplay::closed:
   * @display: the object on which the signal is emitted
   * @is_error: %TRUE if the display was closed due to an error
   *
   * Emitted when the connection to the windowing system for @display is closed.
   */
  signals[CLOSED] =
    g_signal_new (g_intern_static_string ("closed"),
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GdkDisplayClass, closed),
		  NULL, NULL,
                  NULL,
		  G_TYPE_NONE,
		  1,
		  G_TYPE_BOOLEAN);

  /**
   * GdkDisplay::seat-added:
   * @display: the object on which the signal is emitted
   * @seat: the seat that was just added
   *
   * Emitted whenever a new seat is made known to the windowing system.
   */
  signals[SEAT_ADDED] =
    g_signal_new (g_intern_static_string ("seat-added"),
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_LAST,
		  0, NULL, NULL,
                  NULL,
		  G_TYPE_NONE, 1, GDK_TYPE_SEAT);

  /**
   * GdkDisplay::seat-removed:
   * @display: the object on which the signal is emitted
   * @seat: the seat that was just removed
   *
   * Emitted whenever a seat is removed by the windowing system.
   */
  signals[SEAT_REMOVED] =
    g_signal_new (g_intern_static_string ("seat-removed"),
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_LAST,
		  0, NULL, NULL,
                  NULL,
		  G_TYPE_NONE, 1, GDK_TYPE_SEAT);

  /**
   * GdkDisplay::setting-changed:
   * @display: the object on which the signal is emitted
   * @setting: the name of the setting that changed
   *
   * Emitted whenever a setting changes its value.
   */
  signals[SETTING_CHANGED] =
    g_signal_new (g_intern_static_string ("setting-changed"),
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_LAST,
		  0, NULL, NULL,
                  NULL,
		  G_TYPE_NONE, 1, G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
}

static void
free_pointer_info (GdkPointerSurfaceInfo *info)
{
  g_clear_object (&info->surface_under_pointer);
  g_free (info);
}

static void
gdk_display_init (GdkDisplay *display)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  display->double_click_time = 250;
  display->double_click_distance = 5;

  display->pointers_info = g_hash_table_new_full (NULL, NULL, NULL,
                                                  (GDestroyNotify) free_pointer_info);

  g_queue_init (&display->queued_events);

  priv->debug_flags = _gdk_debug_flags;

  priv->composited = TRUE;
  priv->rgba = TRUE;
  priv->shadow_width = TRUE;
  priv->input_shapes = TRUE;
}

static void
gdk_display_dispose (GObject *object)
{
  GdkDisplay *display = GDK_DISPLAY (object);
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  if (display->vk_downloader)
    {
      gdk_dmabuf_downloader_close (display->vk_downloader);
      g_clear_object (&display->vk_downloader);
    }

  if (display->egl_downloader)
    {
      gdk_dmabuf_downloader_close (display->egl_downloader);
      g_clear_object (&display->egl_downloader);
    }

  _gdk_display_manager_remove_display (gdk_display_manager_get (), display);

  g_queue_clear (&display->queued_events);

  g_clear_pointer (&display->egl_dmabuf_formats, gdk_dmabuf_formats_unref);
  g_clear_pointer (&display->egl_internal_formats, gdk_dmabuf_formats_unref);
  g_clear_pointer (&display->egl_external_formats, gdk_dmabuf_formats_unref);
#ifdef GDK_RENDERING_VULKAN
  if (display->vk_instance)
    gdk_display_destroy_vulkan_instance (display);
  g_assert (display->vk_dmabuf_formats == NULL);
  g_clear_error (&display->vulkan_error);
#endif

  g_clear_object (&priv->gl_context);
#ifdef HAVE_EGL
  g_clear_pointer (&priv->egl_display, eglTerminate);
#endif
  g_clear_error (&priv->gl_error);

  for (GList *l = display->seats; l; l = l->next)
    g_object_run_dispose (G_OBJECT (l->data));

  G_OBJECT_CLASS (gdk_display_parent_class)->dispose (object);
}

static void
gdk_display_finalize (GObject *object)
{
  GdkDisplay *display = GDK_DISPLAY (object);

  g_hash_table_destroy (display->pointers_info);

  g_list_free_full (display->seats, g_object_unref);

  g_clear_pointer (&display->dmabuf_formats, gdk_dmabuf_formats_unref);

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

/**
 * gdk_display_close:
 * @display: a `GdkDisplay`
 *
 * Closes the connection to the windowing system for the given display.
 *
 * This cleans up associated resources.
 */
void
gdk_display_close (GdkDisplay *display)
{
  g_return_if_fail (GDK_IS_DISPLAY (display));

  if (!display->closed)
    {
      display->closed = TRUE;

      g_signal_emit (display, signals[CLOSED], 0, FALSE);
      g_object_run_dispose (G_OBJECT (display));

      g_object_unref (display);
    }
}

/**
 * gdk_display_is_closed:
 * @display: a `GdkDisplay`
 *
 * Finds out if the display has been closed.
 *
 * Returns: %TRUE if the display is closed.
 */
gboolean
gdk_display_is_closed  (GdkDisplay  *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);

  return display->closed;
}

/*<private>
 * gdk_display_get_event:
 * @display: a `GdkDisplay`
 *
 * Gets the next `GdkEvent` to be processed for @display,
 * fetching events from the windowing system if necessary.
 *
 * Returns: (nullable) (transfer full): the next `GdkEvent`
 *   to be processed
 */
GdkEvent *
gdk_display_get_event (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  if (display->event_pause_count == 0)
    GDK_DISPLAY_GET_CLASS (display)->queue_events (display);

  return _gdk_event_unqueue (display);
}

/**
 * gdk_display_put_event:
 * @display: a `GdkDisplay`
 * @event: (transfer none): a `GdkEvent`
 *
 * Adds the given event to the event queue for @display.
 *
 * Deprecated: 4.10: This function is only useful in very
 * special situations and should not be used by applications.
 **/
void
gdk_display_put_event (GdkDisplay *display,
                       GdkEvent   *event)
{
  g_return_if_fail (GDK_IS_DISPLAY (display));
  g_return_if_fail (event != NULL);

  _gdk_event_queue_append (display, gdk_event_ref ((GdkEvent *)event));
}

void
_gdk_display_update_last_event (GdkDisplay     *display,
                                GdkEvent       *event)
{
  if (gdk_event_get_time (event) != GDK_CURRENT_TIME)
    display->last_event_time = gdk_event_get_time (event);
}

GdkPointerSurfaceInfo *
_gdk_display_get_pointer_info (GdkDisplay *display,
                               GdkDevice  *device)
{
  GdkPointerSurfaceInfo *info;
  GdkSeat *seat;

  if (device)
    {
      seat = gdk_device_get_seat (device);

      if (device == gdk_seat_get_keyboard (seat))
        device = gdk_seat_get_pointer (seat);
    }

  if (G_UNLIKELY (!device))
    return NULL;

  info = g_hash_table_lookup (display->pointers_info, device);

  if (G_UNLIKELY (!info))
    {
      info = g_new0 (GdkPointerSurfaceInfo, 1);
      g_hash_table_insert (display->pointers_info, device, info);
    }

  return info;
}

void
_gdk_display_pointer_info_foreach (GdkDisplay                   *display,
                                   GdkDisplayPointerInfoForeach  func,
                                   gpointer                      user_data)
{
  GHashTableIter iter;
  gpointer key, value;

  g_hash_table_iter_init (&iter, display->pointers_info);

  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      GdkPointerSurfaceInfo *info = value;
      GdkDevice *device = key;

      (func) (display, device, info, user_data);
    }
}

/**
 * gdk_display_device_is_grabbed:
 * @display: a `GdkDisplay`
 * @device: a `GdkDevice`
 *
 * Returns %TRUE if there is an ongoing grab on @device for @display.
 *
 * Returns: %TRUE if there is a grab in effect for @device.
 */
gboolean
gdk_display_device_is_grabbed (GdkDisplay *display,
                               GdkDevice  *device)
{
  GdkSeat *seat;

  g_return_val_if_fail (GDK_IS_DISPLAY (display), TRUE);
  g_return_val_if_fail (GDK_IS_DEVICE (device), TRUE);

  seat = gdk_device_get_seat (device);

  return gdk_seat_get_topmost_grab_surface (seat) != NULL;
}

/**
 * gdk_display_get_name:
 * @display: a `GdkDisplay`
 *
 * Gets the name of the display.
 *
 * Returns: a string representing the display name. This string is owned
 *   by GDK and should not be modified or freed.
 */
const char *
gdk_display_get_name (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  return GDK_DISPLAY_GET_CLASS (display)->get_name (display);
}

/**
 * gdk_display_beep:
 * @display: a `GdkDisplay`
 *
 * Emits a short beep on @display
 */
void
gdk_display_beep (GdkDisplay *display)
{
  g_return_if_fail (GDK_IS_DISPLAY (display));

  GDK_DISPLAY_GET_CLASS (display)->beep (display);
}

/**
 * gdk_display_sync:
 * @display: a `GdkDisplay`
 *
 * Flushes any requests queued for the windowing system and waits until all
 * requests have been handled.
 *
 * This is often used for making sure that the display is synchronized
 * with the current state of the program. Calling [method@Gdk.Display.sync]
 * before [method@GdkX11.Display.error_trap_pop] makes sure that any errors
 * generated from earlier requests are handled before the error trap is removed.
 *
 * This is most useful for X11. On windowing systems where requests are
 * handled synchronously, this function will do nothing.
 */
void
gdk_display_sync (GdkDisplay *display)
{
  g_return_if_fail (GDK_IS_DISPLAY (display));

  GDK_DISPLAY_GET_CLASS (display)->sync (display);
}

/**
 * gdk_display_flush:
 * @display: a `GdkDisplay`
 *
 * Flushes any requests queued for the windowing system.
 *
 * This happens automatically when the main loop blocks waiting for new events,
 * but if your application is drawing without returning control to the main loop,
 * you may need to call this function explicitly. A common case where this function
 * needs to be called is when an application is executing drawing commands
 * from a thread other than the thread where the main loop is running.
 *
 * This is most useful for X11. On windowing systems where requests are
 * handled synchronously, this function will do nothing.
 */
void
gdk_display_flush (GdkDisplay *display)
{
  g_return_if_fail (GDK_IS_DISPLAY (display));

  GDK_DISPLAY_GET_CLASS (display)->flush (display);
}

/**
 * gdk_display_get_clipboard:
 * @display: a `GdkDisplay`
 *
 * Gets the clipboard used for copy/paste operations.
 *
 * Returns: (transfer none): the display's clipboard
 */
GdkClipboard *
gdk_display_get_clipboard (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  if (display->clipboard == NULL)
    display->clipboard = gdk_clipboard_new (display);

  return display->clipboard;
}

/**
 * gdk_display_get_primary_clipboard:
 * @display: a `GdkDisplay`
 *
 * Gets the clipboard used for the primary selection.
 *
 * On backends where the primary clipboard is not supported natively,
 * GDK emulates this clipboard locally.
 *
 * Returns: (transfer none): the primary clipboard
 */
GdkClipboard *
gdk_display_get_primary_clipboard (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  if (display->primary_clipboard == NULL)
    display->primary_clipboard = gdk_clipboard_new (display);

  return display->primary_clipboard;
}

/**
 * gdk_display_supports_input_shapes: (get-property input-shapes)
 * @display: a `GdkDisplay`
 *
 * Returns %TRUE if the display supports input shapes.
 *
 * This means that [method@Gdk.Surface.set_input_region] can
 * be used to modify the input shape of surfaces on @display.
 *
 * On modern displays, this value is always %TRUE.
 *
 * Returns: %TRUE if surfaces with modified input shape are supported
 */
gboolean
gdk_display_supports_input_shapes (GdkDisplay *display)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);

  return priv->input_shapes;
}

void
gdk_display_set_input_shapes (GdkDisplay *display,
                              gboolean    input_shapes)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  g_return_if_fail (GDK_IS_DISPLAY (display));

  if (priv->input_shapes == input_shapes)
    return;

  priv->input_shapes = input_shapes;

  g_object_notify_by_pspec (G_OBJECT (display), props[PROP_INPUT_SHAPES]);
}

static GdkAppLaunchContext *
gdk_display_real_get_app_launch_context (GdkDisplay *display)
{
  GdkAppLaunchContext *ctx;

  ctx = g_object_new (GDK_TYPE_APP_LAUNCH_CONTEXT,
                      "display", display,
                      NULL);

  return ctx;
}

/**
 * gdk_display_get_app_launch_context:
 * @display: a `GdkDisplay`
 *
 * Returns a `GdkAppLaunchContext` suitable for launching
 * applications on the given display.
 *
 * Returns: (transfer full): a new `GdkAppLaunchContext` for @display
 */
GdkAppLaunchContext *
gdk_display_get_app_launch_context (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  return GDK_DISPLAY_GET_CLASS (display)->get_app_launch_context (display);
}

/**
 * gdk_display_open:
 * @display_name: (nullable): the name of the display to open
 *
 * Opens a display.
 *
 * If opening the display fails, `NULL` is returned.
 *
 * Returns: (nullable) (transfer none): a `GdkDisplay`
 */
GdkDisplay *
gdk_display_open (const char *display_name)
{
  gdk_ensure_initialized ();

  return gdk_display_manager_open_display (gdk_display_manager_get (), display_name);
}

gulong
_gdk_display_get_next_serial (GdkDisplay *display)
{
  return GDK_DISPLAY_GET_CLASS (display)->get_next_serial (display);
}

/**
 * gdk_display_notify_startup_complete:
 * @display: a `GdkDisplay`
 * @startup_id: a startup-notification identifier, for which
 *   notification process should be completed
 *
 * Indicates to the GUI environment that the application has
 * finished loading, using a given identifier.
 *
 * GTK will call this function automatically for [GtkWindow](../gtk4/class.Window.html)
 * with custom startup-notification identifier unless
 * [gtk_window_set_auto_startup_notification()](../gtk4/method.Window.set_auto_startup_notification.html)
 * is called to disable that feature.
 *
 * Deprecated: 4.10: Using [method@Gdk.Toplevel.set_startup_id] is sufficient
 */
void
gdk_display_notify_startup_complete (GdkDisplay  *display,
                                     const char *startup_id)
{
  g_return_if_fail (GDK_IS_DISPLAY (display));

  GDK_DISPLAY_GET_CLASS (display)->notify_startup_complete (display, startup_id);
}

/**
 * gdk_display_get_startup_notification_id:
 * @display: a `GdkDisplay`
 *
 * Gets the startup notification ID for a Wayland display, or %NULL
 * if no ID has been defined.
 *
 * Returns: (nullable): the startup notification ID for @display
 *
 * Deprecated: 4.10
 */
const char *
gdk_display_get_startup_notification_id (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  if (GDK_DISPLAY_GET_CLASS (display)->get_startup_notification_id == NULL)
    return NULL;

  return GDK_DISPLAY_GET_CLASS (display)->get_startup_notification_id (display);
}

void
_gdk_display_pause_events (GdkDisplay *display)
{
  display->event_pause_count++;
}

void
_gdk_display_unpause_events (GdkDisplay *display)
{
  g_return_if_fail (display->event_pause_count > 0);

  display->event_pause_count--;
}

/*< private >
 * gdk_display_get_keymap:
 * @display: the `GdkDisplay`
 *
 * Returns the `GdkKeymap` attached to @display.
 *
 * Returns: (transfer none): the `GdkKeymap` attached to @display.
 */
GdkKeymap *
gdk_display_get_keymap (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  return GDK_DISPLAY_GET_CLASS (display)->get_keymap (display);
}

/*< private >
 * gdk_display_prepare_vulkan:
 * @self: a `GdkDisplay`
 * @error: return location for a `GError`
 *
 * Checks that Vulkan is available for @self and ensures that it is
 * properly initialized.
 *
 * When this fails, an @error will be set describing the error and this
 * function returns %FALSE.
 *
 * Note that even if this function succeeds, creating a `GdkVulkanContext`
 * may still fail.
 *
 * This function is idempotent. Calling it multiple times will just
 * return the same value or error.
 *
 * You never need to call this function, GDK will call it automatically
 * as needed.
 *
 * Returns: %TRUE if the display supports Vulkan
 */
gboolean
gdk_display_prepare_vulkan (GdkDisplay  *self,
                            GError     **error)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (self), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

#ifdef GDK_RENDERING_VULKAN
  if (!self->vk_instance && !self->vulkan_error)
    gdk_display_create_vulkan_instance (self, &self->vulkan_error);

  if (self->vk_instance == NULL)
    {
      if (error)
        *error = g_error_copy (self->vulkan_error);
    }

  return self->vk_instance != NULL;
#else
  g_set_error (error, GDK_VULKAN_ERROR, GDK_VULKAN_ERROR_UNSUPPORTED,
               "GTK was built without Vulkan support");
  return FALSE;
#endif
}

/*<private>
 * gdk_display_create_vulkan_context:
 * @self: a `GdkDisplay`
 * @surface: (nullable): the `GdkSurface` to use or %NULL for a surfaceless
 *   context
 * @error: return location for an error
 *
 * Creates a new `GdkVulkanContext` for use with @display.
 *
 * If @surface is NULL, the context can not be used to draw to surfaces,
 * it can only be used for custom rendering or compute.
 *
 * If the creation of the `GdkVulkanContext` failed, @error will be set.
 *
 * Returns: (transfer full): the newly created `GdkVulkanContext`, or
 *   %NULL on error
 */
GdkVulkanContext *
gdk_display_create_vulkan_context (GdkDisplay  *self,
                                   GdkSurface  *surface,
                                   GError     **error)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (self), NULL);
  g_return_val_if_fail (surface == NULL || GDK_IS_SURFACE (surface), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  if (!gdk_has_feature (GDK_FEATURE_VULKAN))
    {
      g_set_error_literal (error, GDK_VULKAN_ERROR, GDK_VULKAN_ERROR_NOT_AVAILABLE,
                           _("Vulkan support disabled via GDK_DISABLE"));
      return NULL;
    }

  if (GDK_DISPLAY_GET_CLASS (self)->vk_extension_name == NULL)
    {
      g_set_error (error, GDK_VULKAN_ERROR, GDK_VULKAN_ERROR_UNSUPPORTED,
                   "The %s backend has no Vulkan support.", G_OBJECT_TYPE_NAME (self));
      return FALSE;
    }

  if (surface)
    {
      return g_object_new (GDK_DISPLAY_GET_CLASS (self)->vk_context_type,
                           "surface", surface,
                           NULL);
    }
  else
    {
      return g_object_new (GDK_DISPLAY_GET_CLASS (self)->vk_context_type,
                           "display", self,
                           NULL);
    }
}

gboolean
gdk_display_has_vulkan_feature (GdkDisplay        *self,
                                GdkVulkanFeatures  feature)
{
#ifdef GDK_RENDERING_VULKAN
  return !!(self->vulkan_features & feature);
#else
  return FALSE;
#endif
}

static void
gdk_display_init_gl (GdkDisplay *self)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (self);
  GdkGLContext *context;
  gint64 before G_GNUC_UNUSED;
  gint64 before2 G_GNUC_UNUSED;

  before = GDK_PROFILER_CURRENT_TIME;

  if (!gdk_has_feature (GDK_FEATURE_OPENGL))
    {
      g_set_error_literal (&priv->gl_error, GDK_GL_ERROR,
                           GDK_GL_ERROR_NOT_AVAILABLE,
                           _("OpenGL support disabled via GDK_DISABLE"));
      return;
    }

  context = GDK_DISPLAY_GET_CLASS (self)->init_gl (self, &priv->gl_error);
  if (context == NULL)
    return;

  before2 = GDK_PROFILER_CURRENT_TIME;

  if (!gdk_gl_context_realize (context, &priv->gl_error))
    {
      g_object_unref (context);
      return;
    }

  gdk_profiler_end_mark (before2, "Realize OpenGL context", NULL);

  /* Only assign after realize, so GdkGLContext::realize() can use
   * gdk_display_get_gl_context() == NULL to differentiate between
   * the display's context and any other context.
   */
  priv->gl_context = context;

  gdk_gl_backend_use (GDK_GL_CONTEXT_GET_CLASS (context)->backend_type);

  gdk_profiler_end_mark (before, "Init OpenGL", NULL);
}

/**
 * gdk_display_prepare_gl:
 * @self: a `GdkDisplay`
 * @error: return location for a `GError`
 *
 * Checks that OpenGL is available for @self and ensures that it is
 * properly initialized.
 * When this fails, an @error will be set describing the error and this
 * function returns %FALSE.
 *
 * Note that even if this function succeeds, creating a `GdkGLContext`
 * may still fail.
 *
 * This function is idempotent. Calling it multiple times will just
 * return the same value or error.
 *
 * You never need to call this function, GDK will call it automatically
 * as needed. But you can use it as a check when setting up code that
 * might make use of OpenGL.
 *
 * Returns: %TRUE if the display supports OpenGL
 *
 * Since: 4.4
 **/
gboolean
gdk_display_prepare_gl (GdkDisplay  *self,
                        GError     **error)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (self);

  g_return_val_if_fail (GDK_IS_DISPLAY (self), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  for (;;)
    {
      if (priv->gl_context)
        return TRUE;

      if (priv->gl_error != NULL)
        {
          if (error)
            *error = g_error_copy (priv->gl_error);


          return FALSE;
        }

      gdk_display_init_gl (self);

      /* try again */
    }
}

/**
 * gdk_display_create_gl_context:
 * @self: a `GdkDisplay`
 * @error: return location for an error
 *
 * Creates a new `GdkGLContext` for the `GdkDisplay`.
 *
 * The context is disconnected from any particular surface or surface
 * and cannot be used to draw to any surface. It can only be used to
 * draw to non-surface framebuffers like textures.
 *
 * If the creation of the `GdkGLContext` failed, @error will be set.
 * Before using the returned `GdkGLContext`, you will need to
 * call [method@Gdk.GLContext.make_current] or [method@Gdk.GLContext.realize].
 *
 * Returns: (transfer full): the newly created `GdkGLContext`
 *
 * Since: 4.6
 */
GdkGLContext *
gdk_display_create_gl_context (GdkDisplay  *self,
                               GError     **error)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (self), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  if (!gdk_display_prepare_gl (self, error))
    return NULL;

  return gdk_gl_context_new (self, NULL, FALSE);
}

/*< private >
 * gdk_display_get_gl_context:
 * @self: the `GdkDisplay`
 *
 * Gets the GL context returned from [vfunc@Gdk.Display.init_gl]
 * previously.
 *
 * If that function has not been called yet or did fail, %NULL is
 * returned.
 * Call [method@Gdk.Display.prepare_gl] to avoid this.
 *
 * Returns: The `GdkGLContext`
 */
GdkGLContext *
gdk_display_get_gl_context (GdkDisplay *self)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (self);

  return priv->gl_context;
}

#ifdef HAVE_EGL
static int
strvcmp (gconstpointer p1,
         gconstpointer p2)
{
  const char * const *s1 = p1;
  const char * const *s2 = p2;

  return strcmp (*s1, *s2);
}

static char *
describe_extensions (EGLDisplay egl_display)
{
  const char *extensions;
  char **exts;
  char *ext;

  extensions = eglQueryString (egl_display, EGL_EXTENSIONS);

  exts = g_strsplit (extensions, " ", -1);
  qsort (exts, g_strv_length (exts), sizeof (char *), strvcmp);

  ext = g_strjoinv ("\n\t", exts);
  if (ext[0] == '\n')
    ext[0] = ' ';

  g_strfreev (exts);

  return g_strstrip (ext);
}

static char *
describe_egl_config (EGLDisplay egl_display,
                     EGLConfig  egl_config)
{
  EGLint red, green, blue, alpha, type;
  EGLint depth, stencil;

  if (egl_config == NULL)
    return g_strdup ("-");

  if (!eglGetConfigAttrib (egl_display, egl_config, EGL_RED_SIZE, &red) ||
      !eglGetConfigAttrib (egl_display, egl_config, EGL_GREEN_SIZE, &green) ||
      !eglGetConfigAttrib (egl_display, egl_config, EGL_BLUE_SIZE, &blue) ||
      !eglGetConfigAttrib (egl_display, egl_config, EGL_ALPHA_SIZE, &alpha))
    return g_strdup ("Unknown");

  if (epoxy_has_egl_extension (egl_display, "EGL_EXT_pixel_format_float"))
    {
      if (!eglGetConfigAttrib (egl_display, egl_config, EGL_COLOR_COMPONENT_TYPE_EXT, &type))
        type = EGL_COLOR_COMPONENT_TYPE_FIXED_EXT;
    }
  else
    type = EGL_COLOR_COMPONENT_TYPE_FIXED_EXT;

  if (!eglGetConfigAttrib (egl_display, egl_config, EGL_DEPTH_SIZE, &depth))
    depth = 0;

  if (!eglGetConfigAttrib (egl_display, egl_config, EGL_STENCIL_SIZE, &stencil))
    stencil = 0;

  return g_strdup_printf ("R%dG%dB%dA%d%s, depth %d, stencil %d", red, green, blue, alpha,
                          type == EGL_COLOR_COMPONENT_TYPE_FIXED_EXT ? "" : " float",
                          depth,
                          stencil);
}

gpointer
gdk_display_get_egl_config (GdkDisplay     *self,
                            GdkMemoryDepth  depth)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (self);

  switch (depth)
    {
      case GDK_MEMORY_NONE:
      case GDK_MEMORY_U8:
        return priv->egl_config;

      case GDK_MEMORY_FLOAT16:
      case GDK_MEMORY_FLOAT32:
        return priv->egl_config_high_depth;

      case GDK_N_DEPTHS:
      default:
        g_return_val_if_reached (priv->egl_config);
    }
}

static EGLDisplay
gdk_display_create_egl_display (EGLenum  platform,
                                gpointer native_display)
{
  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
  EGLDisplay egl_display = NULL;

  if (epoxy_has_egl_extension (NULL, "EGL_KHR_platform_base"))
    {
      PFNEGLGETPLATFORMDISPLAYPROC getPlatformDisplay =
        (void *) eglGetProcAddress ("eglGetPlatformDisplay");

      if (getPlatformDisplay != NULL)
        egl_display = getPlatformDisplay (platform, native_display, NULL);
      if (egl_display != NULL)
        goto out;
    }

  if (epoxy_has_egl_extension (NULL, "EGL_EXT_platform_base"))
    {
      PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay =
        (void *) eglGetProcAddress ("eglGetPlatformDisplayEXT");

      if (getPlatformDisplay != NULL)
        egl_display = getPlatformDisplay (platform, native_display, NULL);
      if (egl_display != NULL)
        goto out;
    }

  egl_display = eglGetDisplay ((EGLNativeDisplayType) native_display);

out:
  gdk_profiler_end_mark (start_time, "Create EGL display", NULL);

  return egl_display;
}

#define MAX_EGL_ATTRS 30

typedef enum {
  GDK_EGL_CONFIG_PERFECT = (1 << 0),
  GDK_EGL_CONFIG_HDR     = (1 << 1),
} GdkEGLConfigCreateFlags;

static EGLConfig
gdk_display_create_egl_config (GdkDisplay               *self,
                               GdkEGLConfigCreateFlags   flags,
                               GError                  **error)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (self);
  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
  EGLint attrs[MAX_EGL_ATTRS];
  EGLConfig *configs;
  EGLint count, alloced;
  EGLConfig best_config;
  guint best_score;

  int i = 0;

  attrs[i++] = EGL_SURFACE_TYPE;
  attrs[i++] = EGL_WINDOW_BIT;

  attrs[i++] = EGL_COLOR_BUFFER_TYPE;
  attrs[i++] = EGL_RGB_BUFFER;

  attrs[i++] = EGL_RED_SIZE;
  attrs[i++] = (flags & GDK_EGL_CONFIG_HDR) ? 9 : 8;
  attrs[i++] = EGL_GREEN_SIZE;
  attrs[i++] = (flags & GDK_EGL_CONFIG_HDR) ? 9 : 8;
  attrs[i++] = EGL_BLUE_SIZE;
  attrs[i++] = (flags & GDK_EGL_CONFIG_HDR) ? 9 : 8;
  attrs[i++] = EGL_ALPHA_SIZE;
  attrs[i++] = 8;

  if (flags & GDK_EGL_CONFIG_HDR &&
      self->have_egl_pixel_format_float)
    {
      attrs[i++] = EGL_COLOR_COMPONENT_TYPE_EXT;
      attrs[i++] = EGL_DONT_CARE;
    }

  attrs[i++] = EGL_NONE;
  g_assert (i < MAX_EGL_ATTRS);

  if (!eglChooseConfig (priv->egl_display, attrs, NULL, -1, &alloced) || alloced == 0)
    {
      g_set_error_literal (error, GDK_GL_ERROR, GDK_GL_ERROR_NOT_AVAILABLE,
                           _("No EGL configuration available"));
      return NULL;
    }

  configs = g_new (EGLConfig, alloced);
  if (!eglChooseConfig (priv->egl_display, attrs, configs, alloced, &count))
    {
      g_set_error_literal (error, GDK_GL_ERROR, GDK_GL_ERROR_NOT_AVAILABLE,
                           _("Failed to get EGL configurations"));
      return NULL;
    }
  g_warn_if_fail (alloced == count);

  best_score = G_MAXUINT;
  best_config = NULL;

  for (i = 0; i < count; i++)
    {
      guint score = GDK_DISPLAY_GET_CLASS (self)->rate_egl_config (self, priv->egl_display, configs[i]);

      if (score < best_score)
        {
          best_score = score;
          best_config = configs[i];
        }

      if (score == 0)
        break;
    }

  g_free (configs);

  gdk_profiler_end_mark (start_time, "Create EGL config", NULL);

  if (best_score == G_MAXUINT)
    {
      g_set_error_literal (error, GDK_GL_ERROR,
                           GDK_GL_ERROR_NOT_AVAILABLE,
                           _("No EGL configuration with required features found"));
      return NULL;
    }
  else if ((flags & GDK_EGL_CONFIG_PERFECT) && best_score != 0)
    {
      g_set_error_literal (error, GDK_GL_ERROR,
                           GDK_GL_ERROR_NOT_AVAILABLE,
                           _("No perfect EGL configuration found"));
      return NULL;
    }

  return best_config;
}

#undef MAX_EGL_ATTRS

static gboolean
gdk_display_check_egl_extensions (EGLDisplay   egl_display,
                                  const char **extensions,
                                  GError     **error)
{
  GString *missing = NULL;
  gsize i, n_missing;

  n_missing = 0;

  for (i = 0; extensions[i] != NULL; i++)
    {
      if (!epoxy_has_egl_extension (egl_display, extensions[i]))
        {
          if (missing == NULL)
            {
              missing = g_string_new (extensions[i]);
            }
          else
            {
              g_string_append (missing, ", ");
              g_string_append (missing, extensions[i]);
            }
          n_missing++;
        }
    }

  if (n_missing)
    {
      g_set_error (error, GDK_GL_ERROR, GDK_GL_ERROR_UNSUPPORTED_PROFILE,
                   /* translators: Arguments are the number of missing extensions
                    * followed by a comma-separated list of their names */
                   g_dngettext (GETTEXT_PACKAGE,
                                "EGL implementation is missing extension %s",
                                "EGL implementation is missing %2$d extensions: %1$s",
                                n_missing),
                   missing->str, (int) n_missing);

      g_string_free (missing, TRUE);
      return FALSE;
    }

  return TRUE;
}

static const char *
find_egl_device (EGLDisplay egl_display)
{
  EGLAttrib value;
  EGLDeviceEXT egl_device;

  eglQueryDisplayAttribEXT (egl_display, EGL_DEVICE_EXT, &value);

  egl_device = (EGLDeviceEXT)value;

#ifndef EGL_DRM_RENDER_NODE_FILE_EXT
#define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377
#endif

  return eglQueryDeviceStringEXT (egl_device, EGL_DRM_RENDER_NODE_FILE_EXT);
}

gboolean
gdk_display_init_egl (GdkDisplay  *self,
                      int          platform,
                      gpointer     native_display,
                      gboolean     allow_any,
                      GError     **error)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (self);
  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
  int major, minor;

  if (!gdk_gl_backend_can_be_used (GDK_GL_EGL, error))
    return FALSE;

  if (!epoxy_has_egl ())
    {
      gboolean sandboxed = gdk_running_in_sandbox ();

      g_set_error_literal (error, GDK_GL_ERROR,
                           GDK_GL_ERROR_NOT_AVAILABLE,
                           sandboxed ? _("libEGL not available in this sandbox")
                                     : _("libEGL not available"));
      return FALSE;
    }

  priv->egl_display = gdk_display_create_egl_display (platform, native_display);

  if (priv->egl_display == NULL)
    {
      g_set_error_literal (error, GDK_GL_ERROR,
                           GDK_GL_ERROR_NOT_AVAILABLE,
                           _("Failed to create EGL display"));
      return FALSE;
    }

  if (!eglInitialize (priv->egl_display, &major, &minor))
    {
      priv->egl_display = NULL;
      g_set_error_literal (error, GDK_GL_ERROR,
                           GDK_GL_ERROR_NOT_AVAILABLE,
                           _("Could not initialize EGL display"));
      return FALSE;
    }

  if (major < GDK_EGL_MIN_VERSION_MAJOR ||
      (major == GDK_EGL_MIN_VERSION_MAJOR && minor < GDK_EGL_MIN_VERSION_MINOR))
    {
      g_clear_pointer (&priv->egl_display, eglTerminate);
      g_set_error (error, GDK_GL_ERROR,
                   GDK_GL_ERROR_NOT_AVAILABLE,
                   _("EGL version %d.%d is too old. GTK requires %d.%d"),
                   major, minor, GDK_EGL_MIN_VERSION_MAJOR, GDK_EGL_MIN_VERSION_MINOR);
      return FALSE;
    }

  if (!gdk_display_check_egl_extensions (priv->egl_display,
                                         (const char *[]) {
                                           "EGL_KHR_create_context",
                                           "EGL_KHR_surfaceless_context",
                                           NULL
                                         },
                                         error))
    {
      g_clear_pointer (&priv->egl_display, eglTerminate);
      return FALSE;
    }

  priv->egl_config = gdk_display_create_egl_config (self,
                                                    allow_any ? 0 : GDK_EGL_CONFIG_PERFECT,
                                                    error);
  if (priv->egl_config == NULL)
    {
      g_clear_pointer (&priv->egl_display, eglTerminate);
      return FALSE;
    }

  self->have_egl_buffer_age =
    epoxy_has_egl_extension (priv->egl_display, "EGL_EXT_buffer_age");
  self->have_egl_no_config_context =
    epoxy_has_egl_extension (priv->egl_display, "EGL_KHR_no_config_context");
  self->have_egl_pixel_format_float =
    epoxy_has_egl_extension (priv->egl_display, "EGL_EXT_pixel_format_float");
  self->have_egl_dma_buf_import =
    epoxy_has_egl_extension (priv->egl_display, "EGL_EXT_image_dma_buf_import_modifiers");
  self->have_egl_dma_buf_export =
    epoxy_has_egl_extension (priv->egl_display, "EGL_MESA_image_dma_buf_export");
  self->have_egl_gl_colorspace =
    epoxy_has_egl_extension (priv->egl_display, "EGL_KHR_gl_colorspace");

  if (self->have_egl_no_config_context)
    priv->egl_config_high_depth = gdk_display_create_egl_config (self,
                                                          GDK_EGL_CONFIG_HDR,
                                                          error);
  if (priv->egl_config_high_depth == NULL)
    priv->egl_config_high_depth = priv->egl_config;

  if (GDK_DISPLAY_DEBUG_CHECK (self, OPENGL))
    {
      char *ext = describe_extensions (priv->egl_display);
      char *std_cfg = describe_egl_config (priv->egl_display, priv->egl_config);
      char *hd_cfg = describe_egl_config (priv->egl_display, priv->egl_config_high_depth);
      const char *path;
      struct stat buf = { .st_rdev = 0, };

      path = find_egl_device (priv->egl_display);
      if (path)
        stat (path, &buf);

      gdk_debug_message ("EGL API version %d.%d found\n"
                         " - Vendor: %s\n"
                         " - Version: %s\n"
                         " - Device: %s, %d %d\n"
                         " - Client APIs: %s\n"
                         " - Extensions:\n"
                         "\t%s\n"
                         " - Selected fbconfig: %s\n"
                         "          high depth: %s",
                         major, minor,
                         eglQueryString (priv->egl_display, EGL_VENDOR),
                         eglQueryString (priv->egl_display, EGL_VERSION),
                         path ? path : "unknown",
#ifdef HAVE_SYS_SYSMACROS_H
                         major (buf.st_rdev), minor (buf.st_rdev),
#else
                         0, 0,
#endif
                         eglQueryString (priv->egl_display, EGL_CLIENT_APIS),
                         ext, std_cfg,
                         priv->egl_config_high_depth == priv->egl_config ? "none" : hd_cfg);
      g_free (hd_cfg);
      g_free (std_cfg);
      g_free (ext);
    }

  gdk_profiler_end_mark (start_time, "Init EGL", NULL);

  return TRUE;
}
#endif

/*<private>
 * gdk_display_get_egl_display:
 * @self: a display
 *
 * Retrieves the EGL display connection object for the given GDK display.
 *
 * This function returns `NULL` if GL is not supported or GDK is using
 * a different OpenGL framework than EGL.
 *
 * Returns: (nullable): the EGL display object
 */
gpointer
gdk_display_get_egl_display (GdkDisplay *self)
{
#ifdef HAVE_EGL
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (self);

  g_return_val_if_fail (GDK_IS_DISPLAY (self), NULL);

  if (!priv->egl_display &&
      !gdk_display_prepare_gl (self, NULL))
    return NULL;

  return priv->egl_display;
#else
  return NULL;
#endif
}

static gboolean
gdk_display_init_dmabuf_invoke_callback (gpointer data)
{
  GdkDisplay *self = data;
  GdkDmabufFormatsBuilder *builder;

  if (self->dmabuf_formats != NULL)
    return G_SOURCE_REMOVE;

  GDK_DISPLAY_DEBUG (self, DMABUF,
                     "Beginning initialization of dmabuf support");

  builder = gdk_dmabuf_formats_builder_new ();

#ifdef HAVE_DMABUF
  if (gdk_has_feature (GDK_FEATURE_DMABUF))
    {
#ifdef GDK_RENDERING_VULKAN
      gdk_vulkan_init_dmabuf (self);
      if (self->vk_dmabuf_formats)
        gdk_dmabuf_formats_builder_add_formats (builder, self->vk_dmabuf_formats);
#endif

#ifdef HAVE_EGL
      gdk_dmabuf_egl_init (self);
      if (self->egl_dmabuf_formats)
        gdk_dmabuf_formats_builder_add_formats (builder, self->egl_dmabuf_formats);
#endif

      gdk_dmabuf_formats_builder_add_formats (builder, gdk_dmabuf_get_mmap_formats ());
    }
#endif

  g_atomic_pointer_set (&self->dmabuf_formats, gdk_dmabuf_formats_builder_free_to_formats (builder));

  GDK_DISPLAY_DEBUG (self, DMABUF,
                     "Initialization finished. Advertising %zu dmabuf formats",
                     gdk_dmabuf_formats_get_n_formats (self->dmabuf_formats));

  return G_SOURCE_REMOVE;
}

void
gdk_display_init_dmabuf (GdkDisplay *self)
{
  if (g_atomic_pointer_get (&self->dmabuf_formats))
    return;

  GDK_DISPLAY_DEBUG (self, DMABUF,
                     "Invoking initialization of dmabuf support");

  g_main_context_invoke (NULL, gdk_display_init_dmabuf_invoke_callback, self);

  GDK_DISPLAY_DEBUG (self, DMABUF,
                     "Initialization invoking finished");
}

/**
 * gdk_display_get_dmabuf_formats:
 * @display: a `GdkDisplay`
 *
 * Returns the dma-buf formats that are supported on this display.
 *
 * GTK may use OpenGL or Vulkan to support some formats.
 * Calling this function will then initialize them if they aren't yet.
 *
 * The formats returned by this function can be used for negotiating
 * buffer formats with producers such as v4l, pipewire or GStreamer.
 *
 * To learn more about dma-bufs, see [class@Gdk.DmabufTextureBuilder].
 *
 * This function is threadsafe. It can be called from any thread.
 *
 * Returns: (transfer none): a `GdkDmabufFormats` object
 *
 * Since: 4.14
 */
GdkDmabufFormats *
gdk_display_get_dmabuf_formats (GdkDisplay *display)
{
  gdk_display_init_dmabuf (display);

  return display->dmabuf_formats;
}

GdkDebugFlags
gdk_display_get_debug_flags (GdkDisplay *display)
{
  if (display == NULL)
    return _gdk_debug_flags;

  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  return priv->debug_flags;
}

void
gdk_display_set_debug_flags (GdkDisplay    *display,
                             GdkDebugFlags  flags)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  priv->debug_flags = flags;
}

/**
 * gdk_display_is_composited: (get-property composited)
 * @display: a `GdkDisplay`
 *
 * Returns whether surfaces can reasonably be expected to have
 * their alpha channel drawn correctly on the screen.
 *
 * Check [method@Gdk.Display.is_rgba] for whether the display
 * supports an alpha channel.
 *
 * On X11 this function returns whether a compositing manager is
 * compositing on @display.
 *
 * On modern displays, this value is always %TRUE.
 *
 * Returns: Whether surfaces with RGBA visuals can reasonably
 *   be expected to have their alpha channels drawn correctly
 *   on the screen.
 */
gboolean
gdk_display_is_composited (GdkDisplay *display)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);

  return priv->composited;
}

void
gdk_display_set_composited (GdkDisplay *display,
                            gboolean    composited)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  g_return_if_fail (GDK_IS_DISPLAY (display));

  if (priv->composited == composited)
    return;

  priv->composited = composited;

  g_object_notify_by_pspec (G_OBJECT (display), props[PROP_COMPOSITED]);
}

/**
 * gdk_display_is_rgba: (get-property rgba)
 * @display: a `GdkDisplay`
 *
 * Returns whether surfaces on this @display are created with an
 * alpha channel.
 *
 * Even if a %TRUE is returned, it is possible that the
 * surface’s alpha channel won’t be honored when displaying the
 * surface on the screen: in particular, for X an appropriate
 * windowing manager and compositing manager must be running to
 * provide appropriate display. Use [method@Gdk.Display.is_composited]
 * to check if that is the case.
 *
 * On modern displays, this value is always %TRUE.
 *
 * Returns: %TRUE if surfaces are created with an alpha channel or
 *   %FALSE if the display does not support this functionality.
 */
gboolean
gdk_display_is_rgba (GdkDisplay *display)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);

  return priv->rgba;
}

void
gdk_display_set_rgba (GdkDisplay *display,
                      gboolean    rgba)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  g_return_if_fail (GDK_IS_DISPLAY (display));

  if (priv->rgba == rgba)
    return;

  priv->rgba = rgba;

  g_object_notify_by_pspec (G_OBJECT (display), props[PROP_RGBA]);
}

/**
 * gdk_display_supports_shadow_width: (get-property shadow-width)
 * @display: a `GdkDisplay`
 *
 * Returns whether it's possible for a surface to draw outside of the window area.
 *
 * If %TRUE is returned the application decides if it wants to draw shadows.
 * If %FALSE is returned, the compositor decides if it wants to draw shadows.
 *
 * Returns: %TRUE if surfaces can draw shadows or
 *   %FALSE if the display does not support this functionality.
 *
 * Since: 4.14
 */
gboolean
gdk_display_supports_shadow_width (GdkDisplay *display)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);

  return priv->shadow_width;
}

void
gdk_display_set_shadow_width (GdkDisplay *display,
                              gboolean    shadow_width)
{
  GdkDisplayPrivate *priv = gdk_display_get_instance_private (display);

  g_return_if_fail (GDK_IS_DISPLAY (display));

  if (priv->shadow_width == shadow_width)
    return;

  priv->shadow_width = shadow_width;

  g_object_notify_by_pspec (G_OBJECT (display), props[PROP_SHADOW_WIDTH]);
}

static void
device_removed_cb (GdkSeat    *seat,
                   GdkDevice  *device,
                   GdkDisplay *display)
{
  g_hash_table_remove (display->pointers_info, device);
}

void
gdk_display_add_seat (GdkDisplay *display,
                      GdkSeat    *seat)
{
  g_return_if_fail (GDK_IS_DISPLAY (display));
  g_return_if_fail (GDK_IS_SEAT (seat));

  display->seats = g_list_append (display->seats, g_object_ref (seat));
  g_signal_emit (display, signals[SEAT_ADDED], 0, seat);

  g_signal_connect (seat, "device-removed", G_CALLBACK (device_removed_cb), display);
}

void
gdk_display_remove_seat (GdkDisplay *display,
                         GdkSeat    *seat)
{
  GList *link;

  g_return_if_fail (GDK_IS_DISPLAY (display));
  g_return_if_fail (GDK_IS_SEAT (seat));

  g_signal_handlers_disconnect_by_func (seat, G_CALLBACK (device_removed_cb), display);

  link = g_list_find (display->seats, seat);

  if (link)
    {
      display->seats = g_list_remove_link (display->seats, link);
      g_signal_emit (display, signals[SEAT_REMOVED], 0, seat);
      g_object_unref (link->data);
      g_list_free (link);
    }
}

/**
 * gdk_display_get_default_seat:
 * @display: a `GdkDisplay`
 *
 * Returns the default `GdkSeat` for this display.
 *
 * Note that a display may not have a seat. In this case,
 * this function will return %NULL.
 *
 * Returns: (transfer none) (nullable): the default seat.
 **/
GdkSeat *
gdk_display_get_default_seat (GdkDisplay *display)
{
  GdkDisplayClass *display_class;

  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  display_class = GDK_DISPLAY_GET_CLASS (display);

  return display_class->get_default_seat (display);
}

/**
 * gdk_display_list_seats:
 * @display: a `GdkDisplay`
 *
 * Returns the list of seats known to @display.
 *
 * Returns: (transfer container) (element-type GdkSeat): the
 *   list of seats known to the `GdkDisplay`
 */
GList *
gdk_display_list_seats (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  return g_list_copy (display->seats);
}

/**
 * gdk_display_get_monitors:
 * @self: a `GdkDisplay`
 *
 * Gets the list of monitors associated with this display.
 *
 * Subsequent calls to this function will always return the
 * same list for the same display.
 *
 * You can listen to the GListModel::items-changed signal on
 * this list to monitor changes to the monitor of this display.
 *
 * Returns: (transfer none): a `GListModel` of `GdkMonitor`
 */
GListModel *
gdk_display_get_monitors (GdkDisplay *self)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (self), NULL);

  return GDK_DISPLAY_GET_CLASS (self)->get_monitors (self);
}

/**
 * gdk_display_get_monitor_at_surface:
 * @display: a `GdkDisplay`
 * @surface: a `GdkSurface`
 *
 * Gets the monitor in which the largest area of @surface
 * resides.
 *
 * Returns: (transfer none) (nullable): the monitor with the largest
 *   overlap with @surface
 */
GdkMonitor *
gdk_display_get_monitor_at_surface (GdkDisplay *display,
                                   GdkSurface  *surface)
{
  GdkRectangle win;
  GListModel *monitors;
  guint i;
  int area = 0;
  GdkMonitor *best = NULL;
  GdkDisplayClass *class;

  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  class = GDK_DISPLAY_GET_CLASS (display);
  if (class->get_monitor_at_surface)
    {
      best = class->get_monitor_at_surface (display, surface);

      if (best)
        return best;
    }

  /* the fallback implementation requires global coordinates */
  gdk_surface_get_geometry (surface, &win.x, &win.y, &win.width, &win.height);
  gdk_surface_get_origin (surface, &win.x, &win.y);

  monitors = gdk_display_get_monitors (display);
  for (i = 0; i < g_list_model_get_n_items (monitors); i++)
    {
      GdkMonitor *monitor;
      GdkRectangle mon, intersect;
      int overlap;

      monitor = g_list_model_get_item (monitors, i);
      gdk_monitor_get_geometry (monitor, &mon);
      gdk_rectangle_intersect (&win, &mon, &intersect);
      overlap = intersect.width *intersect.height;
      if (overlap > area)
        {
          area = overlap;
          best = monitor;
        }
      g_object_unref (monitor);
    }

  return best;
}

void
gdk_display_emit_opened (GdkDisplay *display)
{
  g_signal_emit (display, signals[OPENED], 0);
}

/**
 * gdk_display_get_setting:
 * @display: a `GdkDisplay`
 * @name: the name of the setting
 * @value: location to store the value of the setting
 *
 * Retrieves a desktop-wide setting such as double-click time
 * for the @display.
 *
 * Returns: %TRUE if the setting existed and a value was stored
 *   in @value, %FALSE otherwise
 */
gboolean
gdk_display_get_setting (GdkDisplay *display,
                         const char *name,
                         GValue     *value)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
  g_return_val_if_fail (name != NULL, FALSE);
  g_return_val_if_fail (value != NULL, FALSE);

  return GDK_DISPLAY_GET_CLASS (display)->get_setting (display, name, value);
}

void
gdk_display_setting_changed (GdkDisplay       *display,
                             const char       *name)
{
  g_signal_emit (display, signals[SETTING_CHANGED], 0, name);
}

void
gdk_display_set_double_click_time (GdkDisplay *display,
                                   guint       msec)
{
  display->double_click_time = msec;
}

void
gdk_display_set_double_click_distance (GdkDisplay *display,
                                       guint       distance)
{
  display->double_click_distance = distance;
}

void
gdk_display_set_cursor_theme (GdkDisplay *display,
                              const char *name,
                              int         size)
{
  if (GDK_DISPLAY_GET_CLASS (display)->set_cursor_theme)
    GDK_DISPLAY_GET_CLASS (display)->set_cursor_theme (display, name, size);
}

/**
 * gdk_display_map_keyval:
 * @display: a `GdkDisplay`
 * @keyval: a keyval, such as %GDK_KEY_a, %GDK_KEY_Up, %GDK_KEY_Return, etc.
 * @keys: (out) (array length=n_keys) (transfer full): return location
 *   for an array of `GdkKeymapKey`
 * @n_keys: return location for number of elements in returned array
 *
 * Obtains a list of keycode/group/level combinations that will
 * generate @keyval.
 *
 * Groups and levels are two kinds of keyboard mode; in general, the level
 * determines whether the top or bottom symbol on a key is used, and the
 * group determines whether the left or right symbol is used.
 *
 * On US keyboards, the shift key changes the keyboard level, and there
 * are no groups. A group switch key might convert a keyboard between
 * Hebrew to English modes, for example.
 *
 * `GdkEventKey` contains a %group field that indicates the active
 * keyboard group. The level is computed from the modifier mask.
 *
 * The returned array should be freed with g_free().
 *
 * Returns: %TRUE if keys were found and returned
 */
gboolean
gdk_display_map_keyval (GdkDisplay    *display,
                        guint          keyval,
                        GdkKeymapKey **keys,
                        int           *n_keys)
{
  return gdk_keymap_get_entries_for_keyval (gdk_display_get_keymap (display),
                                            keyval,
                                            keys,
                                            n_keys);
}

/**
 * gdk_display_map_keycode:
 * @display: a `GdkDisplay`
 * @keycode: a keycode
 * @keys: (out) (array length=n_entries) (transfer full) (optional): return
 *   location for array of `GdkKeymapKey`
 * @keyvals: (out) (array length=n_entries) (transfer full) (optional): return
 *   location for array of keyvals
 * @n_entries: length of @keys and @keyvals
 *
 * Returns the keyvals bound to @keycode.
 *
 * The Nth `GdkKeymapKey` in @keys is bound to the Nth keyval in @keyvals.
 *
 * When a keycode is pressed by the user, the keyval from
 * this list of entries is selected by considering the effective
 * keyboard group and level.
 *
 * Free the returned arrays with g_free().
 *
 * Returns: %TRUE if there were any entries
 */
gboolean
gdk_display_map_keycode (GdkDisplay    *display,
                         guint          keycode,
                         GdkKeymapKey **keys,
                         guint        **keyvals,
                         int           *n_entries)
{
  return gdk_keymap_get_entries_for_keycode (gdk_display_get_keymap (display),
                                             keycode,
                                             keys,
                                             keyvals,
                                             n_entries);
}

/**
 * gdk_display_translate_key:
 * @display: a `GdkDisplay`
 * @keycode: a keycode
 * @state: a modifier state
 * @group: active keyboard group
 * @keyval: (out) (optional): return location for keyval
 * @effective_group: (out) (optional): return location for effective group
 * @level: (out) (optional): return location for level
 * @consumed: (out) (optional): return location for modifiers that were used
 *   to determine the group or level
 *
 * Translates the contents of a `GdkEventKey` into a keyval, effective group,
 * and level.
 *
 * Modifiers that affected the translation and are thus unavailable for
 * application use are returned in @consumed_modifiers.
 *
 * The @effective_group is the group that was actually used for the
 * translation; some keys such as Enter are not affected by the active
 * keyboard group. The @level is derived from @state.
 *
 * @consumed_modifiers gives modifiers that should be masked out
 * from @state when comparing this key press to a keyboard shortcut.
 * For instance, on a US keyboard, the `plus` symbol is shifted, so
 * when comparing a key press to a `<Control>plus` accelerator `<Shift>`
 * should be masked out.
 *
 * This function should rarely be needed, since `GdkEventKey` already
 * contains the translated keyval. It is exported for the benefit of
 * virtualized test environments.
 *
 * Returns: %TRUE if there was a keyval bound to keycode/state/group.
 */
gboolean
gdk_display_translate_key (GdkDisplay      *display,
                           guint            keycode,
                           GdkModifierType  state,
                           int              group,
                           guint           *keyval,
                           int             *effective_group,
                           int             *level,
                           GdkModifierType *consumed)
{
  return gdk_keymap_translate_keyboard_state (gdk_display_get_keymap (display),
                                              keycode, state, group,
                                              keyval,
                                              effective_group,
                                              level,
                                              consumed);
}

int
gdk_display_guess_scale_factor (GdkDisplay *display)
{
  GdkMonitor *monitor;

  monitor = g_list_model_get_item (gdk_display_get_monitors (display), 0);
  if (monitor)
    {
      int result = gdk_monitor_get_scale_factor (monitor);
      g_object_unref (monitor);
      return result;
    }

  return 1;
}
