Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  gdksurface.c

  Sprache: C
 

/* GDK - The GIMP Drawing Kit
 * Copyright (C) 1995-2007 Peter Mattis, Spencer Kimball,
 * Josh MacDonald, Ryan Lortie
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */


/*
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
 */


#include "config.h"

#include <cairo-gobject.h>

#include "gdksurface.h"

#include "gdkprivate.h"
#include "gdkcairoprivate.h"
#include "gdkcontentprovider.h"
#include "gdkdeviceprivate.h"
#include "gdkdisplayprivate.h"
#include "gdkdragsurfaceprivate.h"
#include "gdkeventsprivate.h"
#include "gdkframeclockidleprivate.h"
#include "gdkglcontextprivate.h"
#include <glib/gi18n-lib.h>
#include "gdkmarshalers.h"
#include "gdkpopupprivate.h"
#include "gdkprofilerprivate.h"
#include "gdkrectangleprivate.h"
#include "gdktoplevelprivate.h"
#include "gdkvulkancontext.h"
#include "gdksubsurfaceprivate.h"
#include "gdkseatprivate.h"

#include "gsk/gskrectprivate.h"

#include <math.h>

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

/**
 * GdkSurface:
 *
 * Represents a rectangular region on the screen.
 *
 * Its a low-level object, used to implement high-level objects
 * such as [GtkWindow](../gtk4/class.Window.html).
 *
 * The surfaces you see in practice are either [iface@Gdk.Toplevel] or
 * [iface@Gdk.Popup], and those interfaces provide much of the required
 * API to interact with these surfaces. Other, more specialized surface
 * types exist, but you will rarely interact with them directly.
 */


typedef struct _GdkSurfacePrivate GdkSurfacePrivate;

struct _GdkSurfacePrivate
{
  cairo_region_t *opaque_region;
  cairo_rectangle_int_t opaque_rect; /* This is different from the region */

  GdkDrawContext *attached_context;
  gpointer widget;

  GdkColorState *color_state;
};

enum {
  LAYOUT,
  RENDER,
  EVENT,
  ENTER_MONITOR,
  LEAVE_MONITOR,
  LAST_SIGNAL
};

enum {
  PROP_0,
  PROP_CURSOR,
  PROP_DISPLAY,
  PROP_FRAME_CLOCK,
  PROP_MAPPED,
  PROP_WIDTH,
  PROP_HEIGHT,
  PROP_SCALE_FACTOR,
  PROP_SCALE,
  LAST_PROP
};

/* Global info */

static void gdk_surface_finalize     (GObject      *object);

static void gdk_surface_set_property (GObject      *object,
                                      guint         prop_id,
                                      const GValue *value,
                                      GParamSpec   *pspec);
static void gdk_surface_get_property (GObject      *object,
                                      guint         prop_id,
                                      GValue       *value,
                                      GParamSpec   *pspec);

static void update_cursor               (GdkDisplay *display,
                                         GdkDevice  *device);

static void gdk_surface_queue_set_is_mapped (GdkSurface *surface,
                                             gboolean    is_mapped);


static guint signals[LAST_SIGNAL] = { 0 };
static GParamSpec *properties[LAST_PROP] = { NULL, };

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkSurface, gdk_surface, G_TYPE_OBJECT)

static gboolean
gdk_surface_real_beep (GdkSurface *surface)
{
  return FALSE;
}

static GdkDisplay *
get_display_for_surface (GdkSurface *primary,
                         GdkSurface *secondary)
{
  GdkDisplay *display = primary->display;

  if (display)
    return display;

  display = secondary->display;

  if (display)
    return display;

  g_warning ("no display for surface, using default");
  return gdk_display_get_default ();
}

static GdkMonitor *
get_monitor_for_rect (GdkDisplay          *display,
                      const GdkRectangle  *rect,
                      void               (*get_bounds) (GdkMonitor   *monitor,
                                                        GdkRectangle *bounds))
{
  int biggest_area = G_MININT;
  GdkMonitor *best_monitor = NULL;
  GdkMonitor *monitor;
  GdkRectangle workarea;
  GdkRectangle intersection;
  GListModel *monitors;
  guint i;

  monitors = gdk_display_get_monitors (display);
  for (i = 0; i < g_list_model_get_n_items (monitors); i++)
    {
      monitor = g_list_model_get_item (monitors, i);
      get_bounds (monitor, &workarea);

      if (gdk_rectangle_intersect (&workarea, rect, &intersection))
        {
          if (intersection.width * intersection.height > biggest_area)
            {
              biggest_area = intersection.width * intersection.height;
              best_monitor = monitor;
            }
        }
      g_object_unref (monitor);
    }

  return best_monitor;
}

static int
get_anchor_x_sign (GdkGravity anchor)
{
  switch (anchor)
    {
    case GDK_GRAVITY_STATIC:
    case GDK_GRAVITY_NORTH_WEST:
    case GDK_GRAVITY_WEST:
    case GDK_GRAVITY_SOUTH_WEST:
      return -1;

    default:
    case GDK_GRAVITY_NORTH:
    case GDK_GRAVITY_CENTER:
    case GDK_GRAVITY_SOUTH:
      return 0;

    case GDK_GRAVITY_NORTH_EAST:
    case GDK_GRAVITY_EAST:
    case GDK_GRAVITY_SOUTH_EAST:
      return 1;
    }
}

static int
get_anchor_y_sign (GdkGravity anchor)
{
  switch (anchor)
    {
    case GDK_GRAVITY_STATIC:
    case GDK_GRAVITY_NORTH_WEST:
    case GDK_GRAVITY_NORTH:
    case GDK_GRAVITY_NORTH_EAST:
      return -1;

    default:
    case GDK_GRAVITY_WEST:
    case GDK_GRAVITY_CENTER:
    case GDK_GRAVITY_EAST:
      return 0;

    case GDK_GRAVITY_SOUTH_WEST:
    case GDK_GRAVITY_SOUTH:
    case GDK_GRAVITY_SOUTH_EAST:
      return 1;
    }
}

static int
maybe_flip_position (int       bounds_pos,
                     int       bounds_size,
                     int       rect_pos,
                     int       rect_size,
                     int       surface_size,
                     int       rect_sign,
                     int       surface_sign,
                     int       offset,
                     gboolean  flip,
                     gboolean *flipped)
{
  int primary;
  int secondary;
  int badness = 0;
  int limit;

  /* Try to fit without flipping */

  *flipped = FALSE;
  primary = rect_pos + (1 + rect_sign) * rect_size / 2 + offset - (1 + surface_sign) * surface_size / 2;

  if (!flip)
    return primary;

  /* If the position gets larger than this limit,
   * the other (right/bottom) edge is not going to fit.
   */

  limit = bounds_pos + bounds_size - surface_size;

  if (primary < bounds_pos)
    badness = bounds_pos - primary;
  if (primary > limit)
    badness = MAX (badness, primary - limit);
  /* If it fit, we're done */
  if (badness == 0)
    return primary;

  /* See if flipping helps */

  secondary = rect_pos + (1 - rect_sign) * rect_size / 2 - offset - (1 - surface_sign) * surface_size / 2;

  if (secondary < bounds_pos && bounds_pos - secondary > badness)
    return primary;
  else if (secondary > limit && secondary - limit > badness)
    return primary;

  /* It does help */
  *flipped = TRUE;
  return secondary;
}

GdkMonitor *
gdk_surface_get_layout_monitor (GdkSurface      *surface,
                                GdkPopupLayout  *layout,
                                void           (*get_bounds) (GdkMonitor   *monitor,
                                                              GdkRectangle *bounds))
{
  GdkDisplay *display;
  GdkRectangle root_rect;

  root_rect = *gdk_popup_layout_get_anchor_rect (layout);
  gdk_surface_get_root_coords (surface->parent,
                               root_rect.x,
                               root_rect.y,
                               &root_rect.x,
                               &root_rect.y);

  root_rect.width = MAX (1, root_rect.width);
  root_rect.height = MAX (1, root_rect.height);

  display = get_display_for_surface (surface, surface->transient_for);
  return get_monitor_for_rect (display, &root_rect, get_bounds);
}

void
gdk_surface_layout_popup_helper (GdkSurface                       *surface,
                                 int                               width,
                                 int                               height,
                                 int                               shadow_left,
                                 int                               shadow_right,
                                 int                               shadow_top,
                                 int                               shadow_bottom,
                                 GdkMonitor                       *monitor,
                                 GdkRectangle                     *bounds,
                                 GdkPopupLayout                   *layout,
                                 GdkSurfaceLayoutPopupHelperFlags  flags,
                                 GdkRectangle                     *out_final_rect)
{
  GdkRectangle root_rect;
  GdkGravity rect_anchor;
  GdkGravity surface_anchor;
  int rect_anchor_dx;
  int rect_anchor_dy;
  GdkAnchorHints anchor_hints;
  GdkRectangle final_rect;
  gboolean flipped_x;
  gboolean flipped_y;

  g_return_if_fail (GDK_IS_POPUP (surface));

  root_rect = *gdk_popup_layout_get_anchor_rect (layout);
  gdk_surface_get_root_coords (surface->parent,
                               root_rect.x,
                               root_rect.y,
                               &root_rect.x,
                               &root_rect.y);

  rect_anchor = gdk_popup_layout_get_rect_anchor (layout);
  surface_anchor = gdk_popup_layout_get_surface_anchor (layout);
  gdk_popup_layout_get_offset (layout, &rect_anchor_dx, &rect_anchor_dy);
  anchor_hints = gdk_popup_layout_get_anchor_hints (layout);

  final_rect.width = width - shadow_left - shadow_right;
  final_rect.height = height - shadow_top - shadow_bottom;
  final_rect.x = maybe_flip_position (bounds->x,
                                      bounds->width,
                                      root_rect.x,
                                      root_rect.width,
                                      final_rect.width,
                                      get_anchor_x_sign (rect_anchor),
                                      get_anchor_x_sign (surface_anchor),
                                      rect_anchor_dx,
                                      anchor_hints & GDK_ANCHOR_FLIP_X,
                                      &flipped_x);
  final_rect.y = maybe_flip_position (bounds->y,
                                      bounds->height,
                                      root_rect.y,
                                      root_rect.height,
                                      final_rect.height,
                                      get_anchor_y_sign (rect_anchor),
                                      get_anchor_y_sign (surface_anchor),
                                      rect_anchor_dy,
                                      anchor_hints & GDK_ANCHOR_FLIP_Y,
                                      &flipped_y);

  if (anchor_hints & GDK_ANCHOR_SLIDE_X)
    {
      if (final_rect.x + final_rect.width > bounds->x + bounds->width)
        final_rect.x = bounds->x + bounds->width - final_rect.width;

      if (final_rect.x < bounds->x)
        final_rect.x = bounds->x;
    }

  if (anchor_hints & GDK_ANCHOR_SLIDE_Y)
    {
      if (final_rect.y + final_rect.height > bounds->y + bounds->height)
        final_rect.y = bounds->y + bounds->height - final_rect.height;

      if (final_rect.y < bounds->y)
        final_rect.y = bounds->y;
    }

  if (anchor_hints & GDK_ANCHOR_RESIZE_X)
    {
      if (final_rect.x < bounds->x)
        {
          final_rect.width -= bounds->x - final_rect.x;
          final_rect.x = bounds->x;
        }

      if (final_rect.x + final_rect.width > bounds->x + bounds->width)
        final_rect.width = bounds->x + bounds->width - final_rect.x;
    }

  if (anchor_hints & GDK_ANCHOR_RESIZE_Y)
    {
      if (final_rect.y < bounds->y)
        {
          final_rect.height -= bounds->y - final_rect.y;
          final_rect.y = bounds->y;
        }

      if (final_rect.y + final_rect.height > bounds->y + bounds->height)
        final_rect.height = bounds->y + bounds->height - final_rect.y;
    }

  final_rect.x -= shadow_left;
  final_rect.y -= shadow_top;
  final_rect.width += shadow_left + shadow_right;
  final_rect.height += shadow_top + shadow_bottom;

  if ((flags & GDK_SURFACE_LAYOUT_POPUP_HELPER_ROOT_OUT) == 0)
    {
      int x, y;
      gdk_surface_get_origin (surface->parent, &x, &y);
      final_rect.x -= x;
      final_rect.y -= y;
    }

  if (flipped_x)
    {
      rect_anchor = gdk_gravity_flip_horizontally (rect_anchor);
      surface_anchor = gdk_gravity_flip_horizontally (surface_anchor);
    }
  if (flipped_y)
    {
      rect_anchor = gdk_gravity_flip_vertically (rect_anchor);
      surface_anchor = gdk_gravity_flip_vertically (surface_anchor);
    }

  surface->popup.rect_anchor = rect_anchor;
  surface->popup.surface_anchor = surface_anchor;

  *out_final_rect = final_rect;
}

/* Since GdkEvent is a GTypeInstance, GValue can only store it as a pointer,
 * and GClosure does not know how to handle its memory management. To avoid
 * the event going away in the middle of the signal emission, we provide a
 * marshaller that keeps the event alive for the duration of the closure.
 */

static void
gdk_surface_event_marshaller (GClosure     *closure,
                              GValue       *return_value,
                              guint         n_param_values,
                              const GValue *param_values,
                              gpointer      invocation_hint,
                              gpointer      marshal_data)
{
  GdkEvent *event = g_value_get_pointer (¶m_values[1]);

  gdk_event_ref (event);

  _gdk_marshal_BOOLEAN__POINTER (closure,
                                 return_value,
                                 n_param_values,
                                 param_values,
                                 invocation_hint,
                                 marshal_data);


  gdk_event_unref (event);
}

static void
gdk_surface_event_marshallerv (GClosure *closure,
                               GValue   *return_value,
                               gpointer  instance,
                               va_list   args,
                               gpointer  marshal_data,
                               int       n_params,
                               GType    *param_types)
{
  va_list args_copy;
  GdkEvent *event;

  G_VA_COPY (args_copy, args);
  event = va_arg (args_copy, gpointer);

  gdk_event_ref (event);

  _gdk_marshal_BOOLEAN__POINTERv (closure,
                                  return_value,
                                  instance,
                                  args,
                                  marshal_data,
                                  n_params,
                                  param_types);

  gdk_event_unref (event);

  va_end (args_copy);
}

static void
gdk_surface_init (GdkSurface *surface)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (surface);

  /* 0-initialization is good for all other fields. */

  surface->state = 0;
  surface->fullscreen_mode = GDK_FULLSCREEN_ON_CURRENT_MONITOR;
  surface->width = 1;
  surface->height = 1;

  surface->alpha = 255;

  surface->device_cursor = g_hash_table_new_full (NULL, NULL,
                                                 NULL, g_object_unref);

  surface->subsurfaces = g_ptr_array_new ();

  priv->color_state = gdk_color_state_ref (gdk_color_state_get_srgb ());
}

static double
gdk_surface_real_get_scale (GdkSurface *surface)
{
  return 1.0;
}

static void
gdk_surface_default_get_buffer_size (GdkSurface      *surface,
                                     GdkDrawContext  *context,
                                     guint           *out_width,
                                     guint           *out_height)
{
  double scale = gdk_surface_get_scale (surface);

  *out_width = ceil (scale * surface->width);
  *out_height = ceil (scale * surface->height);
}

static GdkSubsurface *
gdk_surface_real_create_subsurface (GdkSurface *surface)
{
  GDK_DISPLAY_DEBUG (gdk_surface_get_display (surface), OFFLOAD,
                     "Subsurfaces not supported for %s", G_OBJECT_TYPE_NAME (surface));
  return NULL;
}

static void
gdk_surface_default_set_opaque_region (GdkSurface     *surface,
                                       cairo_region_t *region)
{
}

static void
gdk_surface_constructed (GObject *object)
{
  G_GNUC_UNUSED GdkSurface *surface = GDK_SURFACE (object);

  g_assert (surface->frame_clock != NULL);

  G_OBJECT_CLASS (gdk_surface_parent_class)->constructed (object);
}

static void
gdk_surface_class_init (GdkSurfaceClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->constructed = gdk_surface_constructed;
  object_class->finalize = gdk_surface_finalize;
  object_class->set_property = gdk_surface_set_property;
  object_class->get_property = gdk_surface_get_property;

  klass->beep = gdk_surface_real_beep;
  klass->get_scale = gdk_surface_real_get_scale;
  klass->get_buffer_size = gdk_surface_default_get_buffer_size;
  klass->create_subsurface = gdk_surface_real_create_subsurface;
  klass->set_opaque_region = gdk_surface_default_set_opaque_region;

  /**
   * GdkSurface:cursor:
   *
   * The mouse pointer for the `GdkSurface`.
   */

  properties[PROP_CURSOR] =
      g_param_spec_object ("cursor", NULL, NULL,
                           GDK_TYPE_CURSOR,
                           G_PARAM_READWRITE | G_PARAM_STATIC_NAME);

  /**
   * GdkSurface:display:
   *
   * The `GdkDisplay` connection of the surface.
   */

  properties[PROP_DISPLAY] =
      g_param_spec_object ("display", NULL, NULL,
                           GDK_TYPE_DISPLAY,
                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME);

  /**
   * GdkSurface:frame-clock:
   *
   * The `GdkFrameClock` of the surface.
   */

  properties[PROP_FRAME_CLOCK] =
      g_param_spec_object ("frame-clock", NULL, NULL,
                           GDK_TYPE_FRAME_CLOCK,
                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME);

  /**
   * GdkSurface:mapped:
   *
   * Whether the surface is mapped.
   */

  properties[PROP_MAPPED] =
      g_param_spec_boolean ("mapped", NULL, NULL,
                            FALSE,
                            G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  /**
   * GdkSurface:width:
   *
   * The width of the surface in pixels.
   */

  properties[PROP_WIDTH] =
      g_param_spec_int ("width", NULL, NULL,
                        0, G_MAXINT, 0,
                        G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  /**
   * GdkSurface:height:
   *
   * The height of the surface, in pixels.
   */

  properties[PROP_HEIGHT] =
      g_param_spec_int ("height", NULL, NULL,
                        0, G_MAXINT, 0,
                        G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  /**
   * GdkSurface:scale-factor:
   *
   * The scale factor of the surface.
   *
   * The scale factor is the next larger integer,
   * compared to [property@Gdk.Surface:scale].
   */

  properties[PROP_SCALE_FACTOR] =
      g_param_spec_int ("scale-factor", NULL, NULL,
                        1, G_MAXINT, 1,
                        G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  /**
   * GdkSurface:scale:
   *
   * The scale of the surface.
   *
   * Since: 4.12
   */

  properties[PROP_SCALE] =
      g_param_spec_double ("scale", NULL, NULL,
                        1., G_MAXDOUBLE, 1.,
                        G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  g_object_class_install_properties (object_class, LAST_PROP, properties);

  /**
   * GdkSurface::layout:
   * @surface: the `GdkSurface`
   * @width: the current width
   * @height: the current height
   *
   * Emitted when the size of @surface is changed, or when relayout should
   * be performed.
   *
   * Surface size is reported in application pixels, not
   * device pixels (see gdk_surface_get_scale_factor()).
   */

  signals[LAYOUT] =
    g_signal_new (g_intern_static_string ("layout"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  0,
                  NULL,
                  NULL,
                  _gdk_marshal_VOID__INT_INT,
                  G_TYPE_NONE,
                  2,
                  G_TYPE_INT,
                  G_TYPE_INT);
  g_signal_set_va_marshaller (signals[LAYOUT],
                              G_OBJECT_CLASS_TYPE (object_class),
                              _gdk_marshal_VOID__INT_INTv);

  /**
   * GdkSurface::render:
   * @surface: the `GdkSurface`
   * @region: the region that needs to be redrawn
   *
   * Emitted when part of the surface needs to be redrawn.
   *
   * Returns: %TRUE to indicate that the signal has been handled
   */

  signals[RENDER] =
    g_signal_new (g_intern_static_string ("render"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  0,
                  g_signal_accumulator_true_handled,
                  NULL,
                  _gdk_marshal_BOOLEAN__BOXED,
                  G_TYPE_BOOLEAN,
                  1,
                  CAIRO_GOBJECT_TYPE_REGION);
  g_signal_set_va_marshaller (signals[RENDER],
                              G_OBJECT_CLASS_TYPE (object_class),
                              _gdk_marshal_BOOLEAN__BOXEDv);

  /**
   * GdkSurface::event:
   * @surface: the `GdkSurface`
   * @event: (type Gdk.Event): an input event
   *
   * Emitted when GDK receives an input event for @surface.
   *
   * Returns: %TRUE to indicate that the event has been handled
   */

  signals[EVENT] =
    g_signal_new (g_intern_static_string ("event"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  0,
                  g_signal_accumulator_true_handled,
                  NULL,
                  gdk_surface_event_marshaller,
                  G_TYPE_BOOLEAN,
                  1,
                  G_TYPE_POINTER);
  g_signal_set_va_marshaller (signals[EVENT],
                              G_OBJECT_CLASS_TYPE (object_class),
                              gdk_surface_event_marshallerv);

  /**
   * GdkSurface::enter-monitor:
   * @surface: the `GdkSurface`
   * @monitor: the monitor
   *
   * Emitted when @surface starts being present on the monitor.
   */

  signals[ENTER_MONITOR] =
    g_signal_new (g_intern_static_string ("enter-monitor"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  0,
                  NULL,
                  NULL,
                  NULL,
                  G_TYPE_NONE,
                  1,
                  GDK_TYPE_MONITOR);

  /**
   * GdkSurface::leave-monitor:
   * @surface: the `GdkSurface`
   * @monitor: the monitor
   *
   * Emitted when @surface stops being present on the monitor.
   */

  signals[LEAVE_MONITOR] =
    g_signal_new (g_intern_static_string ("leave-monitor"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  0,
                  NULL,
                  NULL,
                  NULL,
                  G_TYPE_NONE,
                  1,
                  GDK_TYPE_MONITOR);
}

static void
seat_removed_cb (GdkDisplay *display,
                 GdkSeat    *seat,
                 GdkSurface  *surface)
{
  GdkDevice *device = gdk_seat_get_pointer (seat);

  surface->devices_inside = g_list_remove (surface->devices_inside, device);
  g_hash_table_remove (surface->device_cursor, device);
}

static void
gdk_surface_finalize (GObject *object)
{
  GdkSurface *surface = GDK_SURFACE (object);
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (surface);

  g_clear_handle_id (&surface->request_motion_id, g_source_remove);

  g_signal_handlers_disconnect_by_func (surface->display,
                                        seat_removed_cb, surface);

  if (!GDK_SURFACE_DESTROYED (surface))
    {
      g_warning ("losing last reference to undestroyed surface");
      _gdk_surface_destroy (surface, FALSE);
    }

  g_clear_pointer (&surface->input_region, cairo_region_destroy);
  g_clear_object (&surface->cursor);
  g_clear_pointer (&surface->device_cursor, g_hash_table_destroy);
  g_clear_pointer (&surface->devices_inside, g_list_free);

  g_clear_object (&surface->display);

  g_clear_pointer (&priv->opaque_region, cairo_region_destroy);

  if (surface->parent)
    surface->parent->children = g_list_remove (surface->parent->children, surface);

  g_assert (surface->subsurfaces->len == 0);

  g_ptr_array_unref (surface->subsurfaces);

  g_clear_pointer (&priv->color_state, gdk_color_state_unref);

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

static void
gdk_surface_set_property (GObject      *object,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
{
  GdkSurface *surface = GDK_SURFACE (object);

  switch (prop_id)
    {
    case PROP_CURSOR:
      gdk_surface_set_cursor (surface, g_value_get_object (value));
      break;

    case PROP_DISPLAY:
      surface->display = g_value_dup_object (value);
      g_assert (surface->display != NULL);
      g_signal_connect (surface->display, "seat-removed",
                        G_CALLBACK (seat_removed_cb), surface);
      break;

    case PROP_FRAME_CLOCK:
      gdk_surface_set_frame_clock (surface, GDK_FRAME_CLOCK (g_value_get_object (value)));
      break;

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

static void
gdk_surface_get_property (GObject    *object,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
{
  GdkSurface *surface = GDK_SURFACE (object);

  switch (prop_id)
    {
    case PROP_CURSOR:
      g_value_set_object (value, gdk_surface_get_cursor (surface));
      break;

    case PROP_DISPLAY:
      g_value_set_object (value, surface->display);
      break;

    case PROP_FRAME_CLOCK:
      g_value_set_object (value, surface->frame_clock);
      break;

    case PROP_MAPPED:
      g_value_set_boolean (value, GDK_SURFACE_IS_MAPPED (surface));
      break;

    case PROP_WIDTH:
      g_value_set_int (value, surface->width);
      break;

    case PROP_HEIGHT:
      g_value_set_int (value, surface->height);
      break;

    case PROP_SCALE_FACTOR:
      g_value_set_int (value, gdk_surface_get_scale_factor (surface));
      break;

    case PROP_SCALE:
      g_value_set_double (value, gdk_surface_get_scale (surface));
      break;

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

void
_gdk_surface_update_size (GdkSurface *surface)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (surface);

  if (priv->attached_context)
    gdk_draw_context_surface_resized (priv->attached_context);

  g_object_notify_by_pspec (G_OBJECT (surface), properties[PROP_WIDTH]);
  g_object_notify_by_pspec (G_OBJECT (surface), properties[PROP_HEIGHT]);
}

/**
 * gdk_surface_new_toplevel: (constructor)
 * @display: the display to create the surface on
 *
 * Creates a new toplevel surface.
 *
 * Returns: (transfer full): the new `GdkSurface`
 */

GdkSurface *
gdk_surface_new_toplevel (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  return g_object_new (GDK_DISPLAY_GET_CLASS (display)->toplevel_type,
                       "display", display,
                       NULL);
}

/**
 * gdk_surface_new_popup: (constructor)
 * @parent: the parent surface to attach the surface to
 * @autohide: whether to hide the surface on outside clicks
 *
 * Create a new popup surface.
 *
 * The surface will be attached to @parent and can be positioned
 * relative to it using [method@Gdk.Popup.present].
 *
 * Returns: (transfer full): a new `GdkSurface`
 */

GdkSurface *
gdk_surface_new_popup (GdkSurface *parent,
                       gboolean    autohide)
{
  GdkSurface *surface;
  GdkDisplay *display;

  g_return_val_if_fail (GDK_IS_SURFACE (parent), NULL);

  display = gdk_surface_get_display (parent);

  surface = g_object_new (GDK_DISPLAY_GET_CLASS (display)->popup_type,
                         "display", display,
                         "parent", parent,
                         NULL);

  surface->autohide = autohide;

  return surface;
}

static void
update_pointer_info_foreach (GdkDisplay           *display,
                             GdkDevice            *device,
                             GdkPointerSurfaceInfo *pointer_info,
                             gpointer              user_data)
{
  GdkSurface *surface = user_data;

  if (pointer_info->surface_under_pointer == surface)
    {
      g_clear_object (&pointer_info->surface_under_pointer);
    }
}

static void
surface_remove_from_pointer_info (GdkSurface  *surface,
                                  GdkDisplay *display)
{
  _gdk_display_pointer_info_foreach (display,
                                     update_pointer_info_foreach,
                                     surface);
}

/**
 * _gdk_surface_destroy_hierarchy:
 * @surface: a `GdkSurface`
 * @recursing_native: If %TRUE, then this is being called because a native
 *   parent was destroyed. This generally means that the call to the windowing
 *   system to destroy the surface can be omitted, since it will be destroyed
 *   as a result of the parent being destroyed. Unless @foreign_destroy.
 * @foreign_destroy: If %TRUE, the surface or a parent was destroyed by some
 *   external agency. The surface has already been destroyed and no windowing
 *   system calls should be made. (This may never happen for some windowing
 *   systems.)
 *
 * Internal function to destroy a surface. Like gdk_surface_destroy(),
 * but does not drop the reference count created by gdk_surface_new().
 */

static void
_gdk_surface_destroy_hierarchy (GdkSurface *surface,
                                gboolean    foreign_destroy)
{
  G_GNUC_UNUSED GdkSurfacePrivate *priv = gdk_surface_get_instance_private (surface);

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  if (priv->attached_context)
    {
      gdk_draw_context_detach (priv->attached_context);
      g_assert (priv->attached_context == NULL);
    }

  GDK_SURFACE_GET_CLASS (surface)->destroy (surface, foreign_destroy);

  g_clear_handle_id (&surface->set_is_mapped_source_id, g_source_remove);
  surface->is_mapped = FALSE;
  surface->pending_is_mapped = FALSE;

  surface->destroyed = TRUE;

  surface_remove_from_pointer_info (surface, surface->display);

  // Gtk.Window wants to disconnect from the frame clock, so notify before destroying it.

  g_object_ref (surface);

  g_object_notify_by_pspec (G_OBJECT (surface), properties[PROP_MAPPED]);

  if (surface->gl_paint_context)
    {
      /* Make sure to destroy if current */
      g_object_run_dispose (G_OBJECT (surface->gl_paint_context));
      g_clear_object (&surface->gl_paint_context);
    }

  if (surface->frame_clock)
    {
      if (surface->parent == NULL)
        g_object_run_dispose (G_OBJECT (surface->frame_clock));
      gdk_surface_set_frame_clock (surface, NULL);
    }

  _gdk_surface_clear_update_area (surface);

  g_object_unref (surface);
}

/**
 * _gdk_surface_destroy:
 * @surface: a `GdkSurface`
 * @foreign_destroy: If %TRUE, the surface or a parent was destroyed by some
 *   external agency. The surface has already been destroyed and no windowing
 *   system calls should be made. (This may never happen for some windowing
 *   systems.)
 *
 * Internal function to destroy a surface. Like gdk_surface_destroy(),
 * but does not drop the reference count created by gdk_surface_new().
 */

void
_gdk_surface_destroy (GdkSurface *surface,
                      gboolean   foreign_destroy)
{
  _gdk_surface_destroy_hierarchy (surface, foreign_destroy);
}

/**
 * gdk_surface_destroy:
 * @surface: a `GdkSurface`
 *
 * Destroys the window system resources associated with @surface and
 * decrements @surface's reference count.
 *
 * The window system resources for all children of @surface are also
 * destroyed, but the childrens reference counts are not decremented.
 *
 * Note that a surface will not be destroyed automatically when its
 * reference count reaches zero. You must call this function yourself
 * before that happens.
 */

void
gdk_surface_destroy (GdkSurface *surface)
{
  _gdk_surface_destroy_hierarchy (surface, FALSE);
  g_object_unref (surface);
}

void
gdk_surface_set_widget (GdkSurface *self,
                        gpointer    widget)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (self);

  priv->widget = widget;
}

gpointer
gdk_surface_get_widget (GdkSurface *self)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (self);

  return priv->widget;
}

/**
 * gdk_surface_get_display:
 * @surface: a `GdkSurface`
 *
 * Gets the `GdkDisplay` associated with a `GdkSurface`.
 *
 * Returns: (transfer none): the `GdkDisplay` associated with @surface
 */

GdkDisplay *
gdk_surface_get_display (GdkSurface *surface)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);

  return surface->display;
}
/**
 * gdk_surface_is_destroyed:
 * @surface: a `GdkSurface`
 *
 * Check to see if a surface is destroyed.
 *
 * Returns: %TRUE if the surface is destroyed
 */

gboolean
gdk_surface_is_destroyed (GdkSurface *surface)
{
  return GDK_SURFACE_DESTROYED (surface);
}

/**
 * gdk_surface_get_mapped:
 * @surface: a `GdkSurface`
 *
 * Checks whether the surface has been mapped.
 *
 * A surface is mapped with [method@Gdk.Toplevel.present]
 * or [method@Gdk.Popup.present].
 *
 * Returns: %TRUE if the surface is mapped
 */

gboolean
gdk_surface_get_mapped (GdkSurface *surface)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), FALSE);

  return GDK_SURFACE_IS_MAPPED (surface);
}

gboolean
gdk_surface_get_gl_is_srgb (GdkSurface *self)
{
  return self->is_srgb;
}

GdkGLContext *
gdk_surface_get_paint_gl_context (GdkSurface  *surface,
                                  GError     **error)
{
  if (!gdk_display_prepare_gl (surface->display, error))
    return NULL;

  if (surface->gl_paint_context == NULL)
    {
      surface->gl_paint_context = gdk_surface_create_gl_context (surface, error);
      if (surface->gl_paint_context == NULL)
        return NULL;
    }

  if (!gdk_gl_context_realize (surface->gl_paint_context, error))
    {
      g_clear_object (&surface->gl_paint_context);
      return NULL;
    }

  return surface->gl_paint_context;
}

/**
 * gdk_surface_create_gl_context:
 * @surface: a `GdkSurface`
 * @error: return location for an error
 *
 * Creates a new `GdkGLContext` for the `GdkSurface`.
 *
 * The context is disconnected from any particular surface or surface.
 * 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`
 */

GdkGLContext *
gdk_surface_create_gl_context (GdkSurface   *surface,
                               GError      **error)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  if (!gdk_display_prepare_gl (surface->display, error))
    return NULL;

  return gdk_gl_context_new (surface->display, surface, FALSE);
}

/**
 * gdk_surface_create_cairo_context:
 * @surface: a `GdkSurface`
 *
 * Creates a new `GdkCairoContext` for rendering on @surface.
 *
 * Returns: (transfer full): the newly created `GdkCairoContext`
 *
 * Deprecated: 4.18: Drawing content with Cairo should be done via
 *   Cairo rendernodes, not by using the Cairo renderer.
 */

GdkCairoContext *
gdk_surface_create_cairo_context (GdkSurface *surface)
{
  GdkDisplay *display;

  g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);

  display = surface->display;

  return g_object_new (GDK_DISPLAY_GET_CLASS (display)->cairo_context_type,
                       "surface", surface,
                       NULL);
}

/**
 * gdk_surface_create_vulkan_context:
 * @surface: a `GdkSurface`
 * @error: return location for an error
 *
 * Sets an error and returns %NULL.
 *
 * Returns: (transfer full): %NULL
 *
 * Deprecated: 4.14: GTK does not expose any Vulkan internals. This
 *   function is a leftover that was accidentally exposed.
 */

GdkVulkanContext *
gdk_surface_create_vulkan_context (GdkSurface  *surface,
                                   GError    **error)
{
  g_set_error (error, GDK_VULKAN_ERROR, GDK_VULKAN_ERROR_UNSUPPORTED,
               "GTK does not expose Vulkan internals.");
  return FALSE;
}

static void
gdk_surface_schedule_update (GdkSurface *surface)
{
  GdkFrameClock *frame_clock;

  g_return_if_fail (surface);

  surface->pending_phases |= GDK_FRAME_CLOCK_PHASE_PAINT;

  if (surface->update_freeze_count)
    return;

  /* If there's no frame clock (a foreign surface), then the invalid
   * region will just stick around unless gdk_surface_process_updates()
   * is called.
   */

  frame_clock = gdk_surface_get_frame_clock (surface);
  if (frame_clock)
    gdk_frame_clock_request_phase (gdk_surface_get_frame_clock (surface),
                                   GDK_FRAME_CLOCK_PHASE_PAINT);
}

static void
gdk_surface_layout_on_clock (GdkFrameClock *clock,
                             void          *data)
{
  GdkSurface *surface = GDK_SURFACE (data);
  GdkSurfaceClass *class;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  if (!GDK_SURFACE_IS_MAPPED (surface))
    return;

  surface->pending_phases &= ~GDK_FRAME_CLOCK_PHASE_LAYOUT;

  class = GDK_SURFACE_GET_CLASS (surface);
  if (class->compute_size)
    {
      if (class->compute_size (surface))
        return;
    }

  g_signal_emit (surface, signals[LAYOUT], 0, surface->width, surface->height);
}

/**
 * gdk_surface_request_layout:
 * @surface: a `GdkSurface`
 *
 * Request a layout phase from the surface's frame clock.
 *
 * See [method@Gdk.FrameClock.request_phase].
 */

void
gdk_surface_request_layout (GdkSurface *surface)
{
  GdkSurfaceClass *class;
  GdkFrameClock *frame_clock;

  class = GDK_SURFACE_GET_CLASS (surface);
  if (class->request_layout)
    class->request_layout (surface);

  frame_clock = gdk_surface_get_frame_clock (surface);
  g_return_if_fail (frame_clock);

  gdk_frame_clock_request_phase (frame_clock,
                                 GDK_FRAME_CLOCK_PHASE_LAYOUT);
}

static void
gdk_surface_paint_on_clock (GdkFrameClock *clock,
                            void          *data)
{
  GdkSurface *surface = GDK_SURFACE (data);
  cairo_region_t *expose_region;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface) ||
      !surface->update_area ||
      surface->update_freeze_count)
    return;

  surface->pending_phases &= ~GDK_FRAME_CLOCK_PHASE_PAINT;
  expose_region = surface->update_area;
  surface->update_area = NULL;

  if (GDK_SURFACE_IS_MAPPED (surface))
    {
      gboolean handled;

      g_object_ref (surface);

      g_signal_emit (surface, signals[RENDER], 0, expose_region, &handled);

      g_object_unref (surface);
    }

  cairo_region_destroy (expose_region);
}

/*
 * gdk_surface_invalidate_rect:
 * @surface: a `GdkSurface`
 * @rect: (nullable): rectangle to invalidate or %NULL to
 *   invalidate the whole surface
 *
 * Invalidate a rectangular region of @surface.
 *
 * This is a convenience wrapper around
 * [method@Gdk.Surface.invalidate_region].
 */

void
gdk_surface_invalidate_rect (GdkSurface        *surface,
                             const GdkRectangle *rect)
{
  GdkRectangle surface_rect;
  cairo_region_t *region;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (!GDK_SURFACE_IS_MAPPED (surface))
    return;

  if (!rect)
    {
      surface_rect.x = 0;
      surface_rect.y = 0;
      surface_rect.width = surface->width;
      surface_rect.height = surface->height;
      rect = &surface_rect;
    }

  region = cairo_region_create_rectangle (rect);
  gdk_surface_invalidate_region (surface, region);
  cairo_region_destroy (region);
}

static void
impl_surface_add_update_area (GdkSurface     *impl_surface,
                              cairo_region_t *region)
{
  if (impl_surface->update_area)
    cairo_region_union (impl_surface->update_area, region);
  else
    {
      impl_surface->update_area = cairo_region_copy (region);
      gdk_surface_schedule_update (impl_surface);
    }
}

/**
 * gdk_surface_queue_render:
 * @surface: a `GdkSurface`
 *
 * Forces a [signal@Gdk.Surface::render] signal emission for @surface
 * to be scheduled.
 *
 * This function is useful for implementations that track invalid
 * regions on their own.
 */

void
gdk_surface_queue_render (GdkSurface *surface)
{
  cairo_region_t *region;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  region = cairo_region_create ();
  impl_surface_add_update_area (surface, region);
  cairo_region_destroy (region);
}

/*
 * gdk_surface_invalidate_region:
 * @surface: a `GdkSurface`
 * @region: a `cairo_region_t`
 *
 * Adds @region to the update area for @surface.
 *
 * The update area is the region that needs to be redrawn,
 * or dirty region.
 *
 * GDK will process all updates whenever the frame clock schedules
 * a redraw, so theres no need to do forces redraws manually, you
 * just need to invalidate regions that you know should be redrawn.
 */

void
gdk_surface_invalidate_region (GdkSurface          *surface,
                               const cairo_region_t *region)
{
  cairo_region_t *visible_region;
  cairo_rectangle_int_t r;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (!GDK_SURFACE_IS_MAPPED (surface))
    return;

  if (cairo_region_is_empty (region))
    return;

  r.x = 0;
  r.y = 0;
  r.width = surface->width;
  r.height = surface->height;

  visible_region = cairo_region_copy (region);

  cairo_region_intersect_rectangle (visible_region, &r);
  impl_surface_add_update_area (surface, visible_region);

  cairo_region_destroy (visible_region);
}

/*
 * _gdk_surface_clear_update_area:
 * @surface: a `GdkSurface`
 *
 * Internal function to clear the update area for a surface.
 * This is called when the surface is hidden or destroyed.
 */

void
_gdk_surface_clear_update_area (GdkSurface *surface)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (surface->update_area)
    {
      cairo_region_destroy (surface->update_area);
      surface->update_area = NULL;
    }
}

/*
 * gdk_surface_freeze_updates:
 * @surface: a `GdkSurface`
 *
 * Temporarily freezes a surface such that it wont receive expose
 * events.  The surface will begin receiving expose events again when
 * gdk_surface_thaw_updates() is called. If gdk_surface_freeze_updates()
 * has been called more than once, gdk_surface_thaw_updates() must be
 * called an equal number of times to begin processing exposes.
 */

void
gdk_surface_freeze_updates (GdkSurface *surface)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_DEBUG_CHECK (NO_VSYNC))
    return;

  surface->update_freeze_count++;
  if (surface->update_freeze_count == 1)
    gdk_frame_clock_stop (surface->frame_clock);
}

static gboolean
request_motion_cb (void *data)
{
  GdkSurface *surface = GDK_SURFACE (data);
  GdkFrameClock *clock = gdk_surface_get_frame_clock (surface);

  if (clock)
    gdk_frame_clock_request_phase (clock, GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS);
  surface->request_motion_id = 0;

  return G_SOURCE_REMOVE;
}


/*
 * gdk_surface_thaw_updates:
 * @surface: a `GdkSurface`
 *
 * Thaws a surface frozen with gdk_surface_freeze_updates(). Note that this
 * will not necessarily schedule updates if the surface freeze count reaches
 * zero.
 */

void
gdk_surface_thaw_updates (GdkSurface *surface)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_DEBUG_CHECK (NO_VSYNC))
    return;

  g_return_if_fail (surface->update_freeze_count > 0);

  if (--surface->update_freeze_count == 0)
    {
      GdkFrameClock *frame_clock = surface->frame_clock;

      gdk_frame_clock_start (frame_clock);

      if (surface->pending_phases)
        gdk_frame_clock_request_phase (frame_clock, surface->pending_phases);

      if (surface->request_motion && surface->request_motion_id == 0)
        {
          surface->request_motion_id =
            g_idle_add_full (GDK_PRIORITY_REDRAW + 20, request_motion_cb, surface, NULL);
          gdk_source_set_static_name_by_id (surface->request_motion_id, "[gtk] request_motion_cb");
        }
    }
}

/*
 * gdk_surface_constrain_size:
 * @geometry: a `GdkGeometry` structure
 * @flags: a mask indicating what portions of @geometry are set
 * @width: desired width of surface
 * @height: desired height of the surface
 * @new_width: (out): location to store resulting width
 * @new_height: (out): location to store resulting height
 *
 * Constrains a desired width and height according to a
 * set of geometry hints (such as minimum and maximum size).
 */

void
gdk_surface_constrain_size (GdkGeometry    *geometry,
                            GdkSurfaceHints  flags,
                            int             width,
                            int             height,
                            int            *new_width,
                            int            *new_height)
{
  /* This routine is partially borrowed from fvwm.
   *
   * Copyright 1993, Robert Nation
   *     You may use this code for any purpose, as long as the original
   *     copyright remains in the source code and all documentation
   *
   * which in turn borrows parts of the algorithm from uwm
   */

  int min_width = 0;
  int min_height = 0;
  int max_width = G_MAXINT;
  int max_height = G_MAXINT;

  if (flags & GDK_HINT_MIN_SIZE)
    {
      min_width = geometry->min_width;
      min_height = geometry->min_height;
    }

  if (flags & GDK_HINT_MAX_SIZE)
    {
      max_width = geometry->max_width ;
      max_height = geometry->max_height;
    }

  /* clamp width and height to min and max values
   */

  width = CLAMP (width, min_width, max_width);
  height = CLAMP (height, min_height, max_height);

  *new_width = width;
  *new_height = height;
}

/**
 * gdk_surface_get_device_position:
 * @surface: a `GdkSurface`
 * @device: pointer `GdkDevice` to query to
 * @x: (out) (optional): return location for the X coordinate of @device
 * @y: (out) (optional): return location for the Y coordinate of @device
 * @mask: (out) (optional): return location for the modifier mask
 *
 * Obtains the current device position and modifier state.
 *
 * The position is given in coordinates relative to the upper
 * left corner of @surface.
 *
 * Return: %TRUE if the device is over the surface
 */

gboolean
gdk_surface_get_device_position (GdkSurface       *surface,
                                 GdkDevice       *device,
                                 double          *x,
                                 double          *y,
                                 GdkModifierType *mask)
{
  double tmp_x, tmp_y;
  GdkModifierType tmp_mask;
  gboolean ret;

  g_return_val_if_fail (GDK_IS_SURFACE (surface), FALSE);
  g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
  g_return_val_if_fail (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD, FALSE);

  tmp_x = 0;
  tmp_y = 0;
  tmp_mask = 0;

  ret = GDK_SURFACE_GET_CLASS (surface)->get_device_state (surface,
                                                           device,
                                                           &tmp_x, &tmp_y,
                                                           &tmp_mask);

  if (x)
    *x = tmp_x;
  if (y)
    *y = tmp_y;
  if (mask)
    *mask = tmp_mask;

  return ret;
}

/**
 * gdk_surface_hide:
 * @surface: a `GdkSurface`
 *
 * Hide the surface.
 *
 * For toplevel surfaces, withdraws them, so they will no longer be
 * known to the window manager; for all surfaces, unmaps them, so
 * they wont be displayed. Normally done automatically as
 * part of [gtk_widget_hide()](../gtk4/method.Widget.hide.html).
 */

void
gdk_surface_hide (GdkSurface *surface)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (surface->destroyed)
    return;

  gdk_surface_queue_set_is_mapped (surface, FALSE);

  GDK_SURFACE_GET_CLASS (surface)->hide (surface);

  surface->popup.rect_anchor = 0;
  surface->popup.surface_anchor = 0;
  surface->x = 0;
  surface->y = 0;
}

static void
gdk_surface_set_cursor_internal (GdkSurface *surface,
                                 GdkDevice *device,
                                 GdkCursor *cursor)
{
  GdkPointerSurfaceInfo *pointer_info;

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  g_assert (surface->display == gdk_device_get_display (device));

  pointer_info = _gdk_display_get_pointer_info (surface->display, device);

  if (surface == pointer_info->surface_under_pointer)
    update_cursor (surface->display, device);
}

/**
 * gdk_surface_get_cursor:
 * @surface: a `GdkSurface`
 *
 * Retrieves a `GdkCursor` pointer for the cursor currently set on the
 * `GdkSurface`.
 *
 * If the return value is %NULL then there is no custom cursor set on
 * the surface, and it is using the cursor for its parent surface.
 *
 * Use [method@Gdk.Surface.set_cursor] to unset the cursor of the surface.
 *
 * Returns: (nullable) (transfer none): a `GdkCursor`
 */

GdkCursor *
gdk_surface_get_cursor (GdkSurface *surface)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);

  return surface->cursor;
}

/**
 * gdk_surface_set_cursor:
 * @surface: a `GdkSurface`
 * @cursor: (nullable): a `GdkCursor`
 *
 * Sets the default mouse pointer for a `GdkSurface`.
 *
 * Passing %NULL for the @cursor argument means that @surface will use
 * the cursor of its parent surface. Most surfaces should use this default.
 * Note that @cursor must be for the same display as @surface.
 *
 * Use [ctor@Gdk.Cursor.new_from_name] or [ctor@Gdk.Cursor.new_from_texture]
 * to create the cursor. To make the cursor invisible, use %GDK_BLANK_CURSOR.
 */

void
gdk_surface_set_cursor (GdkSurface *surface,
                        GdkCursor  *cursor)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  g_clear_object (&surface->cursor);

  if (!GDK_SURFACE_DESTROYED (surface))
    {
      GdkDevice *device;
      GList *seats, *s;

      if (cursor)
        surface->cursor = g_object_ref (cursor);

      seats = gdk_display_list_seats (surface->display);

      for (s = seats; s; s = s->next)
        {
          GList *devices, *d;

          device = gdk_seat_get_pointer (s->data);
          gdk_surface_set_cursor_internal (surface, device, surface->cursor);

          devices = gdk_seat_get_devices (s->data, GDK_SEAT_CAPABILITY_TABLET_STYLUS);
          for (d = devices; d; d = d->next)
            {
              device = d->data;
              gdk_surface_set_cursor_internal (surface, device, surface->cursor);
            }
          g_list_free (devices);
        }

      g_list_free (seats);
      g_object_notify_by_pspec (G_OBJECT (surface), properties[PROP_CURSOR]);
    }
}

/**
 * gdk_surface_get_device_cursor:
 * @surface: a `GdkSurface`
 * @device: a pointer `GdkDevice`
 *
 * Retrieves a `GdkCursor` pointer for the @device currently set on the
 * specified `GdkSurface`.
 *
 * If the return value is %NULL then there is no custom cursor set on the
 * specified surface, and it is using the cursor for its parent surface.
 *
 * Use [method@Gdk.Surface.set_cursor] to unset the cursor of the surface.
 *
 * Returns: (nullable) (transfer none): a `GdkCursor`
 */

GdkCursor *
gdk_surface_get_device_cursor (GdkSurface *surface,
                               GdkDevice  *device)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);
  g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
  g_return_val_if_fail (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD, NULL);

  return g_hash_table_lookup (surface->device_cursor, device);
}

/**
 * gdk_surface_set_device_cursor:
 * @surface: a `GdkSurface`
 * @device: a pointer `GdkDevice`
 * @cursor: a `GdkCursor`
 *
 * Sets a specific `GdkCursor` for a given device when it gets inside @surface.
 *
 * Passing %NULL for the @cursor argument means that @surface will use the
 * cursor of its parent surface. Most surfaces should use this default.
 *
 * Use [ctor@Gdk.Cursor.new_from_name] or [ctor@Gdk.Cursor.new_from_texture]
 * to create the cursor. To make the cursor invisible, use %GDK_BLANK_CURSOR.
 */

void
gdk_surface_set_device_cursor (GdkSurface *surface,
                               GdkDevice  *device,
                               GdkCursor  *cursor)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));
  g_return_if_fail (GDK_IS_DEVICE (device));
  g_return_if_fail (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD);

  if (!cursor)
    g_hash_table_remove (surface->device_cursor, device);
  else
    g_hash_table_replace (surface->device_cursor, device, g_object_ref (cursor));

  gdk_surface_set_cursor_internal (surface, device, cursor);
}

/*
 * gdk_surface_get_geometry:
 * @surface: a `GdkSurface`
 * @x: (out) (optional): return location for X coordinate of surface (relative to its parent)
 * @y: (out) (optional): return location for Y coordinate of surface (relative to its parent)
 * @width: (out) (optional): return location for width of surface
 * @height: (out) (optional): return location for height of surface
 *
 * Get the geometry of the surface.
 *
 * The X and Y coordinates returned are relative to the parent surface
 * of @surface, which for toplevels usually means relative to the
 * surface decorations (titlebar, etc.) rather than relative to the
 * root window (screen-size background window).
 *
 * On the X11 platform, the geometry is obtained from the X server, so
 * reflects the latest position of @surface; this may be out-of-sync with
 * the position of @surface delivered in the most-recently-processed
 * `GdkEventConfigure`. [method@Gdk.Surface.get_position] in contrast gets
 * the position from the most recent configure event.
 *
 * Any of the return location arguments to this function may be %NULL,
 * if you arent interested in getting the value of that field.
 *
 * Note: If @surface is not a toplevel, it is much better to call
 * [method@Gdk.Surface.get_position], [method@Gdk.Surface.get_width] and
 * [method@Gdk.Surface.get_height] instead, because it avoids the roundtrip
 * to the X server and because these functions support the full 32-bit
 * coordinate space, whereas gdk_surface_get_geometry() is restricted to
 * the 16-bit coordinates of X11.
 */

void
gdk_surface_get_geometry (GdkSurface *surface,
                          int        *x,
                          int        *y,
                          int        *width,
                          int        *height)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  GDK_SURFACE_GET_CLASS (surface)->get_geometry (surface, x, y, width, height);
}

/**
 * gdk_surface_get_width:
 * @surface: a `GdkSurface`
 *
 * Returns the width of the given @surface.
 *
 * Surface size is reported in application pixels, not
 * device pixels (see [method@Gdk.Surface.get_scale_factor]).
 *
 * Returns: The width of @surface
 */

int
gdk_surface_get_width (GdkSurface *surface)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), 0);

  return surface->width;
}

/**
 * gdk_surface_get_height:
 * @surface: a `GdkSurface`
 *
 * Returns the height of the given @surface.
 *
 * Surface size is reported in application pixels, not
 * device pixels (see [method@Gdk.Surface.get_scale_factor]).
 *
 * Returns: The height of @surface
 */

int
gdk_surface_get_height (GdkSurface *surface)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), 0);

  return surface->height;
}

/*
 * gdk_surface_get_origin:
 * @surface: a `GdkSurface`
 * @x: (out): return location for X coordinate
 * @y: (out): return location for Y coordinate
 *
 * Obtains the position of a surface in root window coordinates.
 *
 * (Compare with gdk_surface_get_position() and
 * gdk_surface_get_geometry() which return the position
 * of a surface relative to its parent surface.)
 */

void
gdk_surface_get_origin (GdkSurface *surface,
                        int        *x,
                        int        *y)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  gdk_surface_get_root_coords (surface, 00, x, y);
}

/*
 * gdk_surface_get_root_coords:
 * @surface: a `GdkSurface`
 * @x: X coordinate in surface
 * @y: Y coordinate in surface
 * @root_x: (out): return location for X coordinate
 * @root_y: (out): return location for Y coordinate
 *
 * Obtains the position of a surface position in root
 * window coordinates. This is similar to
 * gdk_surface_get_origin() but allows you to pass
 * in any position in the surface, not just the origin.
 */

void
gdk_surface_get_root_coords (GdkSurface *surface,
                             int        x,
                             int        y,
                             int       *root_x,
                             int       *root_y)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    {
      *root_x = 0;
      *root_y = 0;
      return;
    }

  GDK_SURFACE_GET_CLASS (surface)->get_root_coords (surface, x, y, root_x, root_y);
}

/**
 * gdk_surface_set_input_region:
 * @surface: a `GdkSurface`
 * @region: (nullable): region of surface to be reactive,
 *   or %NULL to make the entire surface reactive
 *
 * Apply the region to the surface for the purpose of event
 * handling.
 *
 * Mouse events which happen while the pointer position corresponds
 * to an unset bit in the mask will be passed on the surface below
 * @surface.
 *
 * An input region is typically used with RGBA surfaces. The alpha
 * channel of the surface defines which pixels are invisible and
 * allows for nicely antialiased borders, and the input region
 * controls where the surface is clickable.
 *
 * Use [method@Gdk.Display.supports_input_shapes] to find out if
 * a particular backend supports input regions.
 */

void
gdk_surface_set_input_region (GdkSurface     *surface,
                              cairo_region_t *region)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  if (cairo_region_equal (surface->input_region, region))
    return;

  if (surface->input_region)
    cairo_region_destroy (surface->input_region);

  if (region)
    surface->input_region = cairo_region_copy (region);
  else
    surface->input_region = NULL;

  GDK_SURFACE_GET_CLASS (surface)->set_input_region (surface, surface->input_region);
}

static void
update_cursor (GdkDisplay *display,
               GdkDevice  *device)
{
  GdkSurface *cursor_surface;
  GdkSurface *pointer_surface;
  GdkPointerSurfaceInfo *pointer_info;
  GdkCursor *cursor;
  GdkSeat *seat;

  g_assert (display);
  g_assert (device);

  pointer_info = _gdk_display_get_pointer_info (display, device);
  pointer_surface = pointer_info->surface_under_pointer;

  seat = gdk_device_get_seat (device);
  cursor_surface = gdk_seat_get_topmost_grab_surface (seat);

  if (!cursor_surface)
    cursor_surface = pointer_surface;

  cursor = g_hash_table_lookup (cursor_surface->device_cursor, device);

  if (!cursor)
    cursor = cursor_surface->cursor;

  GDK_DEVICE_GET_CLASS (device)->set_surface_cursor (device, pointer_surface, cursor);
}

/**
 * gdk_surface_beep:
 * @surface: a toplevel `GdkSurface`
 *
 * Emits a short beep associated to @surface.
 *
 * If the display of @surface does not support per-surface beeps,
 * emits a short beep on the display just as [method@Gdk.Display.beep].
 */

void
gdk_surface_beep (GdkSurface *surface)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  if (GDK_SURFACE_GET_CLASS (surface)->beep (surface))
    return;

  gdk_display_beep (surface->display);
}

void
_gdk_display_set_surface_under_pointer (GdkDisplay *display,
                                        GdkDevice  *device,
                                        GdkSurface  *surface)
{
  GdkPointerSurfaceInfo *device_info;

  device_info = _gdk_display_get_pointer_info (display, device);

  if (device_info->surface_under_pointer)
    g_object_unref (device_info->surface_under_pointer);
  device_info->surface_under_pointer = surface;

  if (surface)
    {
      g_object_ref (surface);
      update_cursor (display, device);
    }
}

void
_gdk_windowing_got_event (GdkDisplay *display,
                          GList      *event_link,
                          GdkEvent   *event,
                          gulong      serial)
{
  GdkSurface *event_surface = NULL;
  gboolean unlink_event = FALSE;
  GdkPointerSurfaceInfo *pointer_info = NULL;
  GdkDevice *device;
  GdkEventType type;
  guint32 timestamp;

  _gdk_display_update_last_event (display, event);

  device = gdk_event_get_device (event);
  timestamp = gdk_event_get_time (event);

  if (device)
    {
      if (timestamp != GDK_CURRENT_TIME)
        gdk_device_set_timestamp (device, timestamp);

      if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD &&
          gdk_device_get_source (device) != GDK_SOURCE_TABLET_PAD)
        {
          pointer_info = _gdk_display_get_pointer_info (display, device);
          pointer_info->last_physical_device = device;
        }
    }

  event_surface = gdk_event_get_surface (event);
  if (!event_surface)
    goto out;

  type = gdk_event_get_event_type (event);
  if (type == GDK_ENTER_NOTIFY)
    _gdk_display_set_surface_under_pointer (display, device, event_surface);
  else if (type == GDK_LEAVE_NOTIFY)
    _gdk_display_set_surface_under_pointer (display, device, NULL);

 out:
  if (unlink_event)
    {
      _gdk_event_queue_remove_link (display, event_link);
      g_list_free_1 (event_link);
      gdk_event_unref (event);
    }

  /* This does two things - first it sees if there are motions at the
   * end of the queue that can be compressed. Second, if there is just
   * a single motion that won't be dispatched because it is a compression
   * candidate it queues up flushing the event queue.
   */

  _gdk_event_queue_handle_motion_compression (display);
  gdk_event_queue_handle_scroll_compression (display);

  if (event_surface)
    {
      GdkFrameClock *clock = gdk_surface_get_frame_clock (event_surface);

      if (clock) /* might be NULL if surface was destroyed */
        gdk_frame_clock_request_phase (clock, GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS);
    }
}

/**
 * gdk_surface_create_similar_surface:
 * @surface: surface to make new surface similar to
 * @content: the content for the new surface
 * @width: width of the new surface
 * @height: height of the new surface
 *
 * Create a new Cairo surface that is as compatible as possible with the
 * given @surface.
 *
 * For example the new surface will have the same fallback resolution
 * and font options as @surface. Generally, the new surface will also
 * use the same backend as @surface, unless that is not possible for
 * some reason. The type of the returned surface may be examined with
 * cairo_surface_get_type().
 *
 * Initially the surface contents are all 0 (transparent if contents
 * have transparency, black otherwise.)
 *
 * This function always returns a valid pointer, but it will return a
 * pointer to a nil surface if @other is already in an error state
 * or any other error occurs.
 *
 * Returns: a pointer to the newly allocated surface. The caller
 *   owns the surface and should call cairo_surface_destroy() when done
 *   with it.
 *
 * Deprecated: 4.12: Create a suitable cairo image surface yourself
 */

cairo_surface_t *
gdk_surface_create_similar_surface (GdkSurface      *surface,
                                    cairo_content_t  content,
                                    int              width,
                                    int              height)
{
  cairo_surface_t *similar_surface;
  int scale;

  g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);

  scale = gdk_surface_get_scale_factor (surface);

  similar_surface = cairo_image_surface_create (gdk_cairo_format_for_content (content),
                                                width * scale, height * scale);
  cairo_surface_set_device_scale (similar_surface, scale, scale);

  return similar_surface;
}

/* This function is called when the XWindow is really gone.
 */

void
gdk_surface_destroy_notify (GdkSurface *surface)
{
  GDK_SURFACE_GET_CLASS (surface)->destroy_notify (surface);
}

/**
 * gdk_drag_begin:
 * @surface: the source surface for this drag
 * @device: the device that controls this drag
 * @content: (transfer none): the offered content
 * @actions: the actions supported by this drag
 * @dx: the x offset to @device's position where the drag nominally started
 * @dy: the y offset to @device's position where the drag nominally started
 *
 * Starts a drag and creates a new drag context for it.
 *
 * This function is called by the drag source. After this call, you
 * probably want to set up the drag icon using the surface returned
 * by [method@Gdk.Drag.get_drag_surface].
 *
 * This function returns a reference to the [class@Gdk.Drag] object,
 * but GTK keeps its own reference as well, as long as the DND operation
 * is going on.
 *
 * Note: if @actions include %GDK_ACTION_MOVE, you need to listen for
 * the [signal@Gdk.Drag::dnd-finished] signal and delete the data at
 * the source if [method@Gdk.Drag.get_selected_action] returns
 * %GDK_ACTION_MOVE.
 *
 * Returns: (transfer full) (nullable): a newly created `GdkDrag`
 */

GdkDrag *
gdk_drag_begin (GdkSurface          *surface,
                GdkDevice          *device,
                GdkContentProvider *content,
                GdkDragAction       actions,
                double              dx,
                double              dy)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);
  g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
  g_return_val_if_fail (surface->display == gdk_device_get_display (device), NULL);
  g_return_val_if_fail (GDK_IS_CONTENT_PROVIDER (content), NULL);

  return GDK_SURFACE_GET_CLASS (surface)->drag_begin (surface, device, content, actions, dx, dy);
}

static void
gdk_surface_ensure_motion (GdkSurface *surface)
{
  GdkDisplay *display;
  GdkSeat *seat;
  GdkDevice *device;
  GdkEvent *event;
  double x, y;
  GdkModifierType state;
  GdkSurface *grab_surface;

  if (!surface->request_motion)
    return;

  surface->request_motion = FALSE;

  display = gdk_surface_get_display (surface);
  seat = gdk_display_get_default_seat (display);
  if (!seat)
    return;

  device = gdk_seat_get_pointer (seat);

  if (!gdk_surface_get_device_position (surface, device, &x, &y, &state))
    return;

  grab_surface = gdk_seat_get_topmost_grab_surface (seat);

  if (grab_surface && surface != grab_surface)
    return;

  event = gdk_motion_event_new (surface,
                                device,
                                NULL,
                                GDK_CURRENT_TIME,
                                state,
                                x, y,
                                NULL);

  gdk_surface_handle_event (event);
  gdk_event_unref (event);
}

static void
gdk_surface_flush_events (GdkFrameClock *clock,
                          void          *data)
{
  GdkSurface *surface = GDK_SURFACE (data);

  _gdk_event_queue_flush (surface->display);
  gdk_surface_ensure_motion (surface);
  _gdk_display_pause_events (surface->display);

  gdk_frame_clock_request_phase (clock, GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS);
  surface->frame_clock_events_paused = TRUE;
}

static void
gdk_surface_resume_events (GdkFrameClock *clock,
                           void          *data)
{
  GdkSurface *surface = GDK_SURFACE (data);

  if (surface->frame_clock_events_paused)
    {
      _gdk_display_unpause_events (surface->display);
      surface->frame_clock_events_paused = FALSE;
    }
}

void
gdk_surface_set_frame_clock (GdkSurface     *surface,
                             GdkFrameClock *clock)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));
  g_return_if_fail (clock == NULL || GDK_IS_FRAME_CLOCK (clock));

  if (clock == surface->frame_clock)
    return;

  if (clock)
    {
      g_object_ref (clock);

      g_signal_connect (G_OBJECT (clock),
                        "flush-events",
                        G_CALLBACK (gdk_surface_flush_events),
                        surface);
      g_signal_connect (G_OBJECT (clock),
                        "resume-events",
                        G_CALLBACK (gdk_surface_resume_events),
                        surface);
      g_signal_connect (G_OBJECT (clock),
                        "layout",
                        G_CALLBACK (gdk_surface_layout_on_clock),
                        surface);
      g_signal_connect (G_OBJECT (clock),
                        "paint",
                        G_CALLBACK (gdk_surface_paint_on_clock),
                        surface);

      if (surface->update_freeze_count == 0)
        gdk_frame_clock_start (clock);
    }

  if (surface->frame_clock)
    {
      if (surface->frame_clock_events_paused)
        gdk_surface_resume_events (surface->frame_clock, G_OBJECT (surface));

      g_signal_handlers_disconnect_by_func (G_OBJECT (surface->frame_clock),
                                            G_CALLBACK (gdk_surface_flush_events),
                                            surface);
      g_signal_handlers_disconnect_by_func (G_OBJECT (surface->frame_clock),
                                            G_CALLBACK (gdk_surface_resume_events),
                                            surface);
      g_signal_handlers_disconnect_by_func (G_OBJECT (surface->frame_clock),
                                            G_CALLBACK (gdk_surface_layout_on_clock),
                                            surface);
      g_signal_handlers_disconnect_by_func (G_OBJECT (surface->frame_clock),
                                            G_CALLBACK (gdk_surface_paint_on_clock),
                                            surface);

      if (surface->update_freeze_count == 0)
        gdk_frame_clock_stop (surface->frame_clock);

      g_object_unref (surface->frame_clock);
    }

  surface->frame_clock = clock;
}

/**
 * gdk_surface_get_frame_clock:
 * @surface: surface to get frame clock for
 *
 * Gets the frame clock for the surface.
 *
 * The frame clock for a surface never changes unless the surface is
 * reparented to a new toplevel surface.
 *
 * Returns: (transfer none): the frame clock
 */

GdkFrameClock *
gdk_surface_get_frame_clock (GdkSurface *surface)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);

  return surface->frame_clock;
}

/**
 * gdk_surface_get_scale_factor:
 * @surface: surface to get scale factor for
 *
 * Returns the internal scale factor that maps from surface coordinates
 * to the actual device pixels.
 *
 * On traditional systems this is 1, but on very high density outputs
 * this can be a higher value (often 2). A higher value means that drawing
 * is automatically scaled up to a higher resolution, so any code doing
 * drawing will automatically look nicer. However, if you are supplying
 * pixel-based data the scale value can be used to determine whether to
 * use a pixel resource with higher resolution data.
 *
 * The scale factor may change during the lifetime of the surface.
 *
 * Returns: the scale factor
 */

int
gdk_surface_get_scale_factor (GdkSurface *surface)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), 1);

  return (int) ceil (gdk_surface_get_scale (surface));
}

/**
 * gdk_surface_get_scale:
 * @surface: surface to get scale for
 *
 * Returns the internal scale that maps from surface coordinates
 * to the actual device pixels.
 *
 * When the scale is bigger than 1, the windowing system prefers to get
 * buffers with a resolution that is bigger than the surface size (e.g.
 * to show the surface on a high-resolution display, or in a magnifier).
 *
 * Compare with [method@Gdk.Surface.get_scale_factor], which returns the
 * next larger integer.
 *
 * The scale may change during the lifetime of the surface.
 *
 * Returns: the scale
 *
 * Since: 4.12
 */

double
gdk_surface_get_scale (GdkSurface *surface)
{
  g_return_val_if_fail (GDK_IS_SURFACE (surface), 1.);

  if (GDK_SURFACE_DESTROYED (surface))
    return 1.;

  return GDK_SURFACE_GET_CLASS (surface)->get_scale (surface);
}

static void
gdk_surface_update_opaque_region (GdkSurface *self)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (self);
  cairo_region_t *region;

  if (priv->opaque_region == NULL)
    {
      if (priv->opaque_rect.width <= 0)
        region = NULL;
      else
        region = cairo_region_create_rectangle (&priv->opaque_rect);
    }
  else
    {
      if (priv->opaque_rect.width <= 0)
        region = cairo_region_reference (priv->opaque_region);
      else
        {
          region = cairo_region_copy (priv->opaque_region);
          cairo_region_union_rectangle (region, &priv->opaque_rect);
        }
    }

  GDK_SURFACE_GET_CLASS (self)->set_opaque_region (self, region);

  g_clear_pointer (®ion, cairo_region_destroy);
}

/**
 * gdk_surface_set_opaque_region:
 * @surface: a top-level `GdkSurface`
 * @region: (nullable): a region, or %NULL to make the entire
 *   surface opaque
 *
 * Marks a region of the `GdkSurface` as opaque.
 *
 * For optimisation purposes, compositing window managers may
 * like to not draw obscured regions of surfaces, or turn off blending
 * during for these regions. With RGB windows with no transparency,
 * this is just the shape of the window, but with ARGB32 windows, the
 * compositor does not know what regions of the window are transparent
 * or not.
 *
 * This function only works for toplevel surfaces.
 *
 * GTK will update this property automatically if the @surface background
 * is opaque, as we know where the opaque regions are. If your surface
 * background is not opaque, please update this property in your
 * [GtkWidgetClass.css_changed](../gtk4/vfunc.Widget.css_changed.html) handler.
 *
 * Deprecated: 4.16: GDK can figure out the opaque parts of a window itself
 *   by inspecting the contents that are drawn.
 */

void
gdk_surface_set_opaque_region (GdkSurface      *surface,
                               cairo_region_t *region)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (surface);

  g_return_if_fail (GDK_IS_SURFACE (surface));
  g_return_if_fail (!GDK_SURFACE_DESTROYED (surface));

  if (cairo_region_equal (priv->opaque_region, region))
    return;

  g_clear_pointer (&priv->opaque_region, cairo_region_destroy);

  if (region != NULL)
    priv->opaque_region = cairo_region_reference (region);

  gdk_surface_update_opaque_region (surface);
}

/* Sets the opaque rect from the rendernode via end_frame() */
void
gdk_surface_set_opaque_rect (GdkSurface            *self,
                             const graphene_rect_t *rect)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (self);
  cairo_rectangle_int_t opaque;

  if (rect == NULL || !gsk_rect_to_cairo_shrink (rect, &opaque))
    opaque = (cairo_rectangle_int_t) { 0000 };

  if (gdk_rectangle_equal (&priv->opaque_rect, &opaque))
    return;

  priv->opaque_rect = opaque;

  gdk_surface_update_opaque_region (self);
}

/*
 * gdk_surface_is_opaque:
 * @self: a surface
 *
 * Checks if the whole surface is known to be opaque.
 * This allows using an RGBx buffer instead of RGBA.
 *
 * This function works for the currently rendered frame inside
 * begin_frame() implementations.
 *
 * Returns: %TRUE if the whole surface is provably opaque
 **/

gboolean
gdk_surface_is_opaque (GdkSurface *self)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (self);
  cairo_rectangle_int_t whole = { 00, self->width, self->height };

  if (gdk_rectangle_contains (&priv->opaque_rect, &whole))
    return TRUE;

  if (priv->opaque_region &&
      cairo_region_contains_rectangle (priv->opaque_region, &whole) == CAIRO_REGION_OVERLAP_IN)
    return TRUE;

  return FALSE;
}

static void
gdk_surface_set_state (GdkSurface      *surface,
                       GdkToplevelState new_state)
{
  if (new_state == surface->state)
    return/* No actual work to do, nothing changed. */

  /* Actually update the field in GdkSurface, this is sort of an odd
   * place to do it, but seems like the safest since it ensures we expose no
   * inconsistent state to the user.
   */


  surface->state = new_state;

  if (GDK_IS_TOPLEVEL (surface))
    /* gobject-linter-ignore-next-line: use_g_object_notify_by_pspec */
    g_object_notify (G_OBJECT (surface), "state");
}

void
gdk_synthesize_surface_state (GdkSurface       *surface,
                              GdkToplevelState  unset_flags,
                              GdkToplevelState  set_flags)
{
  gdk_surface_set_state (surface, (surface->state | set_flags) & ~unset_flags);
}

void
gdk_surface_queue_state_change (GdkSurface       *surface,
                                GdkToplevelState  unset_flags,
                                GdkToplevelState  set_flags)
{
  GdkFrameClock *frame_clock;

  surface->pending_unset_flags |= unset_flags;
  surface->pending_set_flags &= ~unset_flags;

  surface->pending_set_flags |= set_flags;
  surface->pending_unset_flags &= ~set_flags;

  frame_clock = gdk_surface_get_frame_clock (surface);
  if (!frame_clock || gdk_frame_clock_is_stopped (frame_clock))
    gdk_surface_apply_state_change (surface);
}

void
gdk_surface_apply_state_change (GdkSurface *surface)
{
  if (!surface->pending_unset_flags && !surface->pending_set_flags)
    return;

  gdk_synthesize_surface_state (surface,
                                surface->pending_unset_flags,
                                surface->pending_set_flags);
  surface->pending_unset_flags = 0;
  surface->pending_set_flags = 0;
}

static gboolean
set_is_mapped_idle (gpointer user_data)
{
  GdkSurface *surface = GDK_SURFACE (user_data);

  surface->set_is_mapped_source_id = 0;

  g_return_val_if_fail (surface->pending_is_mapped != surface->is_mapped,
                        G_SOURCE_REMOVE);

  surface->is_mapped = surface->pending_is_mapped;
  if (surface->is_mapped)
    gdk_surface_invalidate_rect (surface, NULL);

  g_object_notify_by_pspec (G_OBJECT (surface), properties[PROP_MAPPED]);

  return G_SOURCE_REMOVE;
}

void
gdk_surface_set_is_mapped (GdkSurface *surface,
                           gboolean    is_mapped)
{
  gboolean was_mapped;

  if (surface->pending_is_mapped != surface->is_mapped)
    g_clear_handle_id (&surface->set_is_mapped_source_id, g_source_remove);

  surface->pending_is_mapped = is_mapped;

  was_mapped = surface->is_mapped;
  surface->is_mapped = is_mapped;
  if (surface->is_mapped)
    gdk_surface_invalidate_rect (surface, NULL);

  if (was_mapped != is_mapped)
    g_object_notify_by_pspec (G_OBJECT (surface), properties[PROP_MAPPED]);
}

static void
gdk_surface_queue_set_is_mapped (GdkSurface *surface,
                                 gboolean    is_mapped)
{
  if (surface->pending_is_mapped == is_mapped)
    return;

  surface->pending_is_mapped = is_mapped;

  if (surface->is_mapped == surface->pending_is_mapped)
    {
      g_clear_handle_id (&surface->set_is_mapped_source_id, g_source_remove);
    }
  else
    {
      g_return_if_fail (!surface->set_is_mapped_source_id);

      surface->set_is_mapped_source_id =
        g_idle_add_full (G_PRIORITY_HIGH - 10, set_is_mapped_idle, surface, NULL);
      gdk_source_set_static_name_by_id (surface->set_is_mapped_source_id, "[gtk] set_is_mapped_idle");
    }
}

static gboolean
check_autohide (GdkEvent *event)
{
  GdkSurface *grab_surface, *event_surface;
  GdkEventType evtype = gdk_event_get_event_type (event);
  GdkSeat *seat;

 switch ((guint) evtype)
    {
    case GDK_BUTTON_PRESS:
#if 0
    // FIXME: we need to ignore the release that is paired
    // with the press starting the grab - due to implicit
    // grabs, it will be delivered to the same place as the
    // press, and will cause the auto dismissal to be triggered.
    case GDK_BUTTON_RELEASE:
    case GDK_TOUCH_END:
    case GDK_TOUCH_CANCEL:
#endif
    case GDK_TOUCH_BEGIN:
    case GDK_TOUCHPAD_SWIPE:
    case GDK_TOUCHPAD_PINCH:
      seat = gdk_event_get_seat (event);
      grab_surface = gdk_seat_get_topmost_grab_surface (seat);
      if (grab_surface)
        {
          event_surface = gdk_event_get_surface (event);
          if (event_surface->autohide &&
              evtype != GDK_TOUCH_BEGIN &&
              !event_surface->has_pointer)
            event_surface = NULL;

          if (grab_surface->autohide &&
              (!event_surface ||
               (grab_surface != event_surface &&
                grab_surface != event_surface->parent)))
            {
              GdkSurface *surface = grab_surface;

              do
                {
                  gdk_surface_hide (surface);
                  surface = surface->parent;
                }
              while (surface->autohide && surface != event_surface);

              return TRUE;
            }
        }
      break;
    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
      event_surface = gdk_event_get_surface (event);
      if (event_surface->autohide &&
          gdk_crossing_event_get_mode (event) == GDK_CROSSING_NORMAL)
        event_surface->has_pointer = evtype == GDK_ENTER_NOTIFY;
      break;
    default:;
    }

  return FALSE;
}

static inline void
add_event_mark (GdkEvent *event,
                gint64    time,
                gint64    end_time)
{
#ifdef HAVE_SYSPROF
  char *message = NULL;
  const char *kind;
  GEnumClass *class;
  GEnumValue *value;
  GdkEventType event_type;

  event_type = gdk_event_get_event_type (event);
  class = g_type_class_ref (GDK_TYPE_EVENT_TYPE);
  value = g_enum_get_value (class, event_type);
  g_type_class_unref (class);
  kind = value ? value->value_nick : "Event";

  switch ((int) event_type)
    {
    case GDK_MOTION_NOTIFY:
      {
        double x, y;
        gdk_event_get_position (event, &x, &y);
        message = g_strdup_printf ("%s {x=%lf, y=%lf, state=0x%x}",
                                   kind,
                                   x, y,
                                   gdk_event_get_modifier_state (event));
        break;
      }

    case GDK_BUTTON_PRESS:
    case GDK_BUTTON_RELEASE:
      {
        double x, y;
        gdk_event_get_position (event, &x, &y);
        message = g_strdup_printf ("%s {button=%u, x=%lf, y=%lf, state=0x%x}",
                                   kind,
                                   gdk_button_event_get_button (event),
                                   x, y,
                                   gdk_event_get_modifier_state (event));
        break;
      }

    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
      {
        message = g_strdup_printf ("%s {keyval=%u, state=0x%x, keycode=%u layout=%u level=%u is_modifier=%u}",
                                   kind,
                                   gdk_key_event_get_keyval (event),
                                   gdk_event_get_modifier_state (event),
                                   gdk_key_event_get_keycode (event),
                                   gdk_key_event_get_layout (event),
                                   gdk_key_event_get_level (event),
                                   gdk_key_event_is_modifier (event));
        break;
      }

    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
    case GDK_TOUCHPAD_SWIPE:
    case GDK_TOUCHPAD_PINCH:
    case GDK_SCROLL:
    case GDK_DRAG_ENTER:
    case GDK_DRAG_LEAVE:
    case GDK_DRAG_MOTION:
    case GDK_DROP_START:
    case GDK_TOUCH_BEGIN:
    case GDK_TOUCH_UPDATE:
    case GDK_TOUCH_END:
    case GDK_TOUCH_CANCEL:
    case GDK_PAD_BUTTON_PRESS:
    case GDK_PAD_BUTTON_RELEASE:
    case GDK_PAD_RING:
    case GDK_PAD_STRIP:
    case GDK_PAD_DIAL:
    case GDK_PAD_GROUP_MODE:
    case GDK_GRAB_BROKEN:
    case GDK_DELETE:
    case GDK_FOCUS_CHANGE:
    case GDK_PROXIMITY_IN:
    case GDK_PROXIMITY_OUT:
    case GDK_EVENT_LAST:
    default:
      break;
    }

  gdk_profiler_add_mark (time, end_time - time, "Event", message ? message : kind);

  g_free (message);
#endif
}

gboolean
gdk_surface_handle_event (GdkEvent *event)
{
  GdkSurface *surface = gdk_event_get_surface (event);
  GdkEventType evtype = gdk_event_get_event_type (event);
  gint64 begin_time = GDK_PROFILER_CURRENT_TIME;
  gboolean handled = FALSE;

  if (!GDK_SURFACE_IS_MAPPED (surface) &&
      !(evtype == GDK_LEAVE_NOTIFY || evtype == GDK_TOUCH_CANCEL))
    return FALSE;

  if (check_autohide (event))
    return TRUE;

  if (evtype == GDK_MOTION_NOTIFY)
    surface->request_motion = FALSE;

  g_signal_emit (surface, signals[EVENT], 0, event, &handled);

  if (GDK_PROFILER_IS_RUNNING)
    add_event_mark (event, begin_time, GDK_PROFILER_CURRENT_TIME);

  return handled;
}

/*
 * gdk_surface_request_motion:
 * @surface: a `GdkSurface`
 *
 * Request that the next frame cycle should deliver a motion
 * event for @surface.
 *
 * The motion event will be delivered if the pointer is over the
 * surface, regardless whether the pointer has moved or not. This
 * is used by GTK after moving widgets around.
 */

void
gdk_surface_request_motion (GdkSurface *surface)
{
  surface->request_motion = TRUE;
}

/**
 * gdk_surface_translate_coordinates:
 * @from: the origin surface
 * @to: the target surface
 * @x: (inout): coordinates to translate
 * @y: (inout): coordinates to translate
 *
 * Translates coordinates between two surfaces.
 *
 * Note that this only works if @to and @from are popups or
 * transient-for to the same toplevel (directly or indirectly).
 *
 * Returns: %TRUE if the coordinates were successfully translated
 */

gboolean
gdk_surface_translate_coordinates (GdkSurface *from,
                                   GdkSurface *to,
                                   double     *x,
                                   double     *y)
{
  double in_x, in_y, out_x, out_y;
  int x1, y1, x2, y2;
  GdkSurface *f, *t;

  g_return_val_if_fail (GDK_IS_SURFACE (from), FALSE);
  g_return_val_if_fail (GDK_IS_SURFACE (to), FALSE);
  g_return_val_if_fail (x != NULL, FALSE);
  g_return_val_if_fail (y != NULL, FALSE);

  in_x = *x;
  in_y = *y;

  x1 = 0;
  y1 = 0;
  f = from;
  while (f->parent)
    {
      x1 += f->x;
      y1 += f->y;
      f = f->parent;
    }

  x2 = 0;
  y2 = 0;
  t = to;
  while (t->parent)
    {
      x2 += t->x;
      y2 += t->y;
      t = t->parent;
    }

  if (f != t)
    return FALSE;

  out_x = in_x + (x1 - x2);
  out_y = in_y + (y1 - y2);

  *x = out_x;
  *y = out_y;

  return TRUE;
}

GdkSeat *
gdk_surface_get_seat_from_event (GdkSurface *surface,
                                 GdkEvent   *event)
{
  if (event)
    {
      GdkSeat *seat = NULL;

      seat = gdk_event_get_seat (event);

      if (seat)
        return seat;
    }
  return gdk_display_get_default_seat (surface->display);
}

void
gdk_surface_enter_monitor (GdkSurface *surface,
                           GdkMonitor *monitor)
{
  g_signal_emit (surface, signals[ENTER_MONITOR], 0, monitor);
}

void
gdk_surface_leave_monitor (GdkSurface *surface,
                           GdkMonitor *monitor)
{
  g_signal_emit (surface, signals[LEAVE_MONITOR], 0, monitor);
}

GdkSubsurface *
gdk_surface_create_subsurface (GdkSurface *surface)
{
  GdkSubsurface *subsurface;

  subsurface = GDK_SURFACE_GET_CLASS (surface)->create_subsurface (surface);

  if (subsurface)
    {
      subsurface->parent = g_object_ref (surface);
      g_ptr_array_add (surface->subsurfaces, subsurface);
    }

  return subsurface;
}

gsize
gdk_surface_get_n_subsurfaces (GdkSurface *surface)
{
  return surface->subsurfaces->len;
}

GdkSubsurface *
gdk_surface_get_subsurface (GdkSurface *surface,
                            gsize       idx)
{
  return g_ptr_array_index (surface->subsurfaces, idx);
}

GdkColorState *
gdk_surface_get_color_state (GdkSurface *surface)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (surface);

  return priv->color_state;
}

void
gdk_surface_set_color_state (GdkSurface    *surface,
                             GdkColorState *color_state)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (surface);

  if (gdk_color_state_equal (priv->color_state, color_state))
    return;

  gdk_color_state_unref (priv->color_state);
  priv->color_state = gdk_color_state_ref (color_state);

  gdk_surface_invalidate_rect (surface, NULL);
}

/*<private>
 * gdk_surface_set_attached_context:
 * @self: the surface
 * @context: (nullable): the context to attach
 *
 * This function is an implementation detail for GdkDrawContext.
 *
 * Use draw context functions like gdk_draw_context_attach() or
 * gdk_draw_context_detach().
 **/

void
gdk_surface_set_attached_context (GdkSurface     *self,
                                  GdkDrawContext *context)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (self);

  priv->attached_context = context;
}

GdkDrawContext *
gdk_surface_get_attached_context (GdkSurface *self)
{
  GdkSurfacePrivate *priv = gdk_surface_get_instance_private (self);

  return priv->attached_context;
}

Messung V0.5 in Prozent
C=98 H=96 G=96

¤ Dauer der Verarbeitung: 0.83 Sekunden  (vorverarbeitet am  2026-07-03) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik