]> Pileus Git - ~andy/gtk/blobdiff - gdk/gdkwindow.c
Add gdk_frame_timings_get_predicted_presentation_time()
[~andy/gtk] / gdk / gdkwindow.c
index 99e469ffed119cbd90d4ee5fc589772a6af6b7d9..211f8fd9ee437f8b1495020a858a94f0d75a6722 100644 (file)
@@ -37,6 +37,7 @@
 #include "gdkdeviceprivate.h"
 #include "gdkvisualprivate.h"
 #include "gdkmarshalers.h"
+#include "gdkframeclockidle.h"
 #include "gdkwindowimpl.h"
 
 #include <math.h>
@@ -169,7 +170,8 @@ enum {
 
 enum {
   PROP_0,
-  PROP_CURSOR
+  PROP_CURSOR,
+  PROP_FRAME_CLOCK
 };
 
 typedef enum {
@@ -183,6 +185,7 @@ struct _GdkWindowPaint
   cairo_region_t *region;
   cairo_surface_t *surface;
   cairo_region_t *flushed;
+  guint8 alpha;
   guint uses_implicit : 1;
 };
 
@@ -237,6 +240,10 @@ static void gdk_window_invalidate_rect_full (GdkWindow          *window,
 static void _gdk_window_propagate_has_alpha_background (GdkWindow *window);
 static cairo_surface_t *gdk_window_ref_impl_surface (GdkWindow *window);
 
+static void gdk_window_process_all_updates_internal (gboolean default_clock_only);
+
+static void gdk_ensure_default_frame_clock  (void);
+
 static guint signals[LAST_SIGNAL] = { 0 };
 
 static gpointer parent_class = NULL;
@@ -387,6 +394,23 @@ gdk_window_class_init (GdkWindowClass *klass)
                                                         GDK_TYPE_CURSOR,
                                                         G_PARAM_READWRITE));
 
+  /**
+   * GdkWindow:paint-clock:
+   *
+   * The frame clock for a #GdkWindow, see #GdkFrameClock
+   *
+   * The frame clock remains the same for the lifetime of the window.
+   *
+   * Since: 3.0
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_FRAME_CLOCK,
+                                   g_param_spec_object ("paint-clock",
+                                                        P_("Frame clock"),
+                                                        P_("Frame clock"),
+                                                        GDK_TYPE_FRAME_CLOCK,
+                                                        G_PARAM_READWRITE));
+
   /**
    * GdkWindow::pick-embedded-child:
    * @window: the window on which the signal is emitted
@@ -599,6 +623,10 @@ gdk_window_set_property (GObject      *object,
       gdk_window_set_cursor (window, g_value_get_object (value));
       break;
 
+    case PROP_FRAME_CLOCK:
+      gdk_window_set_frame_clock (window, g_value_get_object (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -619,6 +647,10 @@ gdk_window_get_property (GObject    *object,
       g_value_set_object (value, gdk_window_get_cursor (window));
       break;
 
+    case PROP_FRAME_CLOCK:
+      g_value_set_object (value, gdk_window_get_frame_clock (window));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -673,7 +705,7 @@ static gboolean
 gdk_window_has_alpha (GdkWindow *window)
 {
   return !gdk_window_has_impl (window) &&
-    window->has_alpha_background;
+    (window->has_alpha_background || window->alpha != 255);
 }
 
 static void
@@ -1406,6 +1438,7 @@ gdk_window_new (GdkWindow     *parent,
   window->y = y;
   window->width = (attributes->width > 1) ? (attributes->width) : (1);
   window->height = (attributes->height > 1) ? (attributes->height) : (1);
+  window->alpha = 255;
 
   if (attributes->wclass == GDK_INPUT_ONLY)
     {
@@ -2721,7 +2754,8 @@ gdk_window_get_content (GdkWindow *window)
  * directly to the window or a child window.
  */
 static gboolean
-gdk_window_begin_implicit_paint (GdkWindow *window, GdkRectangle *rect)
+gdk_window_begin_implicit_paint (GdkWindow *window, GdkRectangle *rect,
+                                gboolean with_alpha, guint8 alpha)
 {
   GdkWindowPaint *paint;
 
@@ -2730,8 +2764,7 @@ gdk_window_begin_implicit_paint (GdkWindow *window, GdkRectangle *rect)
   if (GDK_IS_PAINTABLE (window->impl))
     return FALSE; /* Implementation does double buffering */
 
-  if (window->paint_stack != NULL ||
-      window->implicit_paint != NULL)
+  if (window->paint_stack != NULL)
     return FALSE; /* Don't stack implicit paints */
 
   /* Never do implicit paints for foreign windows, they don't need
@@ -2746,8 +2779,9 @@ gdk_window_begin_implicit_paint (GdkWindow *window, GdkRectangle *rect)
   paint->region = cairo_region_create (); /* Empty */
   paint->uses_implicit = FALSE;
   paint->flushed = NULL;
+  paint->alpha = alpha;
   paint->surface = gdk_window_create_similar_surface (window,
-                                                      gdk_window_get_content (window),
+                                                     with_alpha ? CAIRO_CONTENT_COLOR_ALPHA : gdk_window_get_content (window),
                                                      MAX (rect->width, 1),
                                                       MAX (rect->height, 1));
   cairo_surface_set_device_offset (paint->surface, -rect->x, -rect->y);
@@ -2802,12 +2836,23 @@ gdk_window_flush_implicit_paint (GdkWindow *window)
   GdkWindow *impl_window;
   GdkWindowPaint *paint;
   cairo_region_t *region;
+  GSList *l;
 
   impl_window = gdk_window_get_impl_window (window);
   if (impl_window->implicit_paint == NULL)
     return;
 
-  paint = impl_window->implicit_paint->data;
+  /* There is no proper way to flush any non-toplevel implicit
+     paint, because direct rendering is not compatible with
+     opacity rendering, as it requires an opacity group.
+
+     We warn and flush just the outermost paint in this case.
+  */
+  l = g_slist_last (impl_window->implicit_paint);
+  if (l != impl_window->implicit_paint)
+    g_warning ("Non double buffered drawing not supported inside transparent windows");
+
+  paint = l->data;
 
   region = cairo_region_copy (window->clip_region_with_children);
   cairo_region_translate (region, window->abs_x, window->abs_y);
@@ -2843,20 +2888,64 @@ gdk_window_end_implicit_paint (GdkWindow *window)
   window->implicit_paint = g_slist_delete_link (window->implicit_paint,
                                                window->implicit_paint);
 
-  if (!GDK_WINDOW_DESTROYED (window) && !cairo_region_is_empty (paint->region))
+  if (!GDK_WINDOW_DESTROYED (window) && !cairo_region_is_empty (paint->region) && paint->alpha > 0)
     {
+      GdkWindowPaint *parent_paint = NULL;
       cairo_t *cr;
 
       /* Some regions are valid, push these to window now */
-      cr = gdk_cairo_create_for_impl (window);
+      if (window->implicit_paint)
+       {
+         parent_paint = window->implicit_paint->data;
+
+         /* If the toplevel implicit paint was flushed, restore it now
+            before we blend over it. */
+         if (parent_paint->flushed != NULL &&
+             !cairo_region_is_empty (parent_paint->flushed))
+           {
+             cairo_surface_t *source_surface = gdk_window_ref_impl_surface (window);
+             cairo_region_t *flushed = cairo_region_copy (parent_paint->flushed);
+
+             cr = cairo_create (parent_paint->surface);
+             cairo_set_source_surface (cr, source_surface, 0, 0);
+             cairo_surface_destroy (source_surface);
+
+             cairo_region_intersect (flushed, paint->region);
+             gdk_cairo_region (cr, flushed);
+             cairo_clip (cr);
+
+             cairo_region_subtract (parent_paint->flushed, flushed);
+             cairo_region_destroy (flushed);
+
+             cairo_paint (cr);
+             cairo_destroy (cr);
+           }
+
+         cr = cairo_create (parent_paint->surface);
+       }
+      else
+       cr = gdk_cairo_create_for_impl (window);
+
       gdk_cairo_region (cr, paint->region);
       cairo_clip (cr);
       cairo_set_source_surface (cr, paint->surface, 0, 0);
-      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
-      cairo_paint (cr);
+      if (paint->alpha == 255)
+       {
+         cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+         cairo_paint (cr);
+       }
+      else
+       {
+         cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+         cairo_paint_with_alpha (cr, paint->alpha / 255.0);
+       }
+
       cairo_destroy (cr);
+
+      if (parent_paint)
+       cairo_region_union (parent_paint->region, paint->region);
     }
-  
+
   cairo_region_destroy (paint->region);
   if (paint->flushed)
     cairo_region_destroy (paint->flushed);
@@ -3720,7 +3809,7 @@ gdk_cairo_create (GdkWindow *window)
 /* Code for dirty-region queueing
  */
 static GSList *update_windows = NULL;
-static guint update_idle = 0;
+static GdkFrameClock *_gdk_default_frame_clock = NULL;
 static gboolean debug_updates = FALSE;
 
 static inline gboolean
@@ -3819,12 +3908,25 @@ gdk_window_remove_update_window (GdkWindow *window)
   update_windows = g_slist_remove (update_windows, window);
 }
 
-static gboolean
-gdk_window_update_idle (gpointer data)
+static void
+gdk_window_paint_default_clock_updates (gpointer data)
 {
-  gdk_window_process_all_updates ();
+  gdk_window_process_all_updates_internal (TRUE);
+}
 
-  return FALSE;
+static void
+gdk_ensure_default_frame_clock (void)
+{
+  if (_gdk_default_frame_clock == NULL)
+    {
+      _gdk_default_frame_clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE,
+                                               NULL);
+
+      g_signal_connect (G_OBJECT (_gdk_default_frame_clock),
+                        "paint",
+                        G_CALLBACK (gdk_window_paint_default_clock_updates),
+                        NULL);
+    }
 }
 
 static gboolean
@@ -3845,11 +3947,8 @@ gdk_window_schedule_update (GdkWindow *window)
        gdk_window_is_toplevel_frozen (window)))
     return;
 
-  if (!update_idle)
-    update_idle =
-      gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
-                                gdk_window_update_idle,
-                                NULL, NULL);
+  gdk_frame_clock_request_phase (gdk_window_get_frame_clock (window),
+                                 GDK_FRAME_CLOCK_PHASE_PAINT);
 }
 
 void
@@ -3858,7 +3957,9 @@ _gdk_window_process_updates_recurse (GdkWindow *window,
 {
   GdkWindow *child;
   cairo_region_t *clipped_expose_region;
+  GdkRectangle clip_box;
   GList *l, *children;
+  gboolean end_implicit;
 
   if (cairo_region_is_empty (expose_region))
     return;
@@ -3867,6 +3968,17 @@ _gdk_window_process_updates_recurse (GdkWindow *window,
       gdk_window_has_impl (window))
     _gdk_window_add_damage ((GdkWindow *) window->impl_window, expose_region);
 
+  end_implicit = FALSE;
+  if (window->alpha != 255 && !gdk_window_has_impl (window))
+    {
+      if (window->alpha == 0)
+       return;
+
+      cairo_region_get_extents (expose_region, &clip_box);
+      clip_box.x += window->abs_x;
+      clip_box.y += window->abs_y;
+      end_implicit = gdk_window_begin_implicit_paint (window->impl_window, &clip_box, TRUE, window->alpha);
+    }
 
   /* Paint the window before the children, clipped to the window region
      with visible child windows removed */
@@ -3939,6 +4051,8 @@ _gdk_window_process_updates_recurse (GdkWindow *window,
 
   g_list_free_full (children, g_object_unref);
 
+  if (end_implicit)
+    gdk_window_end_implicit_paint (window->impl_window);
 }
 
 /* Process and remove any invalid area on the native window by creating
@@ -4060,7 +4174,7 @@ gdk_window_process_updates_internal (GdkWindow *window)
           */
 
          cairo_region_get_extents (update_area, &clip_box);
-         end_implicit = gdk_window_begin_implicit_paint (window, &clip_box);
+         end_implicit = gdk_window_begin_implicit_paint (window, &clip_box, FALSE, 255);
          expose_region = cairo_region_copy (update_area);
          impl_class = GDK_WINDOW_IMPL_GET_CLASS (window->impl);
          if (!end_implicit)
@@ -4147,6 +4261,19 @@ after_process_all_updates (void)
   g_slist_free (displays);
 }
 
+/**
+ * gdk_window_process_all_updates:
+ *
+ * Calls gdk_window_process_updates() for all windows (see #GdkWindow)
+ * in the application.
+ *
+ **/
+void
+gdk_window_process_all_updates (void)
+{
+  gdk_window_process_all_updates_internal (FALSE);
+}
+
 /* Currently it is not possible to override
  * gdk_window_process_all_updates in the same manner as
  * gdk_window_process_updates and gdk_window_invalidate_maybe_recurse
@@ -4157,15 +4284,8 @@ after_process_all_updates (void)
  * displays and call the mehod.
  */
 
-/**
- * gdk_window_process_all_updates:
- *
- * Calls gdk_window_process_updates() for all windows (see #GdkWindow)
- * in the application.
- *
- **/
-void
-gdk_window_process_all_updates (void)
+static void
+gdk_window_process_all_updates_internal (gboolean default_clock_only)
 {
   GSList *old_update_windows = update_windows;
   GSList *tmp_list = update_windows;
@@ -4177,18 +4297,13 @@ gdk_window_process_all_updates (void)
       /* We can't do this now since that would recurse, so
         delay it until after the recursion is done. */
       got_recursive_update = TRUE;
-      update_idle = 0;
       return;
     }
 
   in_process_all_updates = TRUE;
   got_recursive_update = FALSE;
 
-  if (update_idle)
-    g_source_remove (update_idle);
-
   update_windows = NULL;
-  update_idle = 0;
 
   before_process_all_updates ();
 
@@ -4201,7 +4316,8 @@ gdk_window_process_all_updates (void)
       if (!GDK_WINDOW_DESTROYED (window))
        {
          if (window->update_freeze_count ||
-             gdk_window_is_toplevel_frozen (window))
+             gdk_window_is_toplevel_frozen (window) ||
+             (default_clock_only && window->frame_clock != NULL))
            gdk_window_add_update_window (window);
          else
            gdk_window_process_updates_internal (window);
@@ -4223,31 +4339,20 @@ gdk_window_process_all_updates (void)
      redraw now so that it eventually happens,
      otherwise we could miss an update if nothing
      else schedules an update. */
-  if (got_recursive_update && !update_idle)
-    update_idle =
-      gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
-                                gdk_window_update_idle,
-                                NULL, NULL);
+  if (got_recursive_update)
+    gdk_window_schedule_update (NULL);
 }
 
-/**
- * gdk_window_process_updates:
- * @window: a #GdkWindow
- * @update_children: whether to also process updates for child windows
- *
- * Sends one or more expose events to @window. The areas in each
- * expose event will cover the entire update area for the window (see
- * gdk_window_invalidate_region() for details). Normally GDK calls
- * gdk_window_process_all_updates() on your behalf, so there's no
- * need to call this function unless you want to force expose events
- * to be delivered immediately and synchronously (vs. the usual
- * case, where GDK delivers them in an idle handler). Occasionally
- * this is useful to produce nicer scrolling behavior, for example.
- *
- **/
-void
-gdk_window_process_updates (GdkWindow *window,
-                           gboolean   update_children)
+
+enum {
+  PROCESS_UPDATES_NO_RECURSE,
+  PROCESS_UPDATES_WITH_ALL_CHILDREN,
+  PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN
+};
+
+static void
+gdk_window_process_updates_with_mode (GdkWindow     *window,
+                                      int            recurse_mode)
 {
   GdkWindow *impl_window;
 
@@ -4273,7 +4378,7 @@ gdk_window_process_updates (GdkWindow *window,
       gdk_window_remove_update_window ((GdkWindow *)impl_window);
     }
 
-  if (update_children)
+  if (recurse_mode != PROCESS_UPDATES_NO_RECURSE)
     {
       /* process updates in reverse stacking order so composition or
        * painting over achieves the desired effect for offscreen windows
@@ -4285,8 +4390,14 @@ gdk_window_process_updates (GdkWindow *window,
 
       for (node = g_list_last (children); node; node = node->prev)
        {
-         gdk_window_process_updates (node->data, TRUE);
-         g_object_unref (node->data);
+          GdkWindow *child = node->data;
+          if (recurse_mode == PROCESS_UPDATES_WITH_ALL_CHILDREN ||
+              (recurse_mode == PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN &&
+               child->frame_clock == NULL))
+            {
+              gdk_window_process_updates (child, TRUE);
+            }
+         g_object_unref (child);
        }
 
       g_list_free (children);
@@ -4295,6 +4406,33 @@ gdk_window_process_updates (GdkWindow *window,
   g_object_unref (window);
 }
 
+/**
+ * gdk_window_process_updates:
+ * @window: a #GdkWindow
+ * @update_children: whether to also process updates for child windows
+ *
+ * Sends one or more expose events to @window. The areas in each
+ * expose event will cover the entire update area for the window (see
+ * gdk_window_invalidate_region() for details). Normally GDK calls
+ * gdk_window_process_all_updates() on your behalf, so there's no
+ * need to call this function unless you want to force expose events
+ * to be delivered immediately and synchronously (vs. the usual
+ * case, where GDK delivers them in an idle handler). Occasionally
+ * this is useful to produce nicer scrolling behavior, for example.
+ *
+ **/
+void
+gdk_window_process_updates (GdkWindow *window,
+                           gboolean   update_children)
+{
+  g_return_if_fail (GDK_IS_WINDOW (window));
+
+  return gdk_window_process_updates_with_mode (window,
+                                               update_children ?
+                                               PROCESS_UPDATES_WITH_ALL_CHILDREN :
+                                               PROCESS_UPDATES_NO_RECURSE);
+}
+
 static void
 gdk_window_invalidate_rect_full (GdkWindow          *window,
                                  const GdkRectangle *rect,
@@ -4781,6 +4919,7 @@ gdk_window_freeze_toplevel_updates_libgtk_only (GdkWindow *window)
   g_return_if_fail (window->window_type != GDK_WINDOW_CHILD);
 
   window->update_and_descendants_freeze_count++;
+  gdk_frame_clock_freeze (gdk_window_get_frame_clock (window));
 }
 
 /**
@@ -4801,6 +4940,7 @@ gdk_window_thaw_toplevel_updates_libgtk_only (GdkWindow *window)
   g_return_if_fail (window->update_and_descendants_freeze_count > 0);
 
   window->update_and_descendants_freeze_count--;
+  gdk_frame_clock_thaw (gdk_window_get_frame_clock (window));
 
   gdk_window_schedule_update (window);
 }
@@ -9091,10 +9231,17 @@ do_synthesize_crossing_event (gpointer data)
                                 serial);
           if (new_window_under_pointer != pointer_info->window_under_pointer)
             {
+              GdkDevice *source_device;
+
+              if (pointer_info->last_slave)
+                source_device = pointer_info->last_slave;
+              else
+                source_device = device;
+
               _gdk_synthesize_crossing_events (display,
                                                pointer_info->window_under_pointer,
                                                new_window_under_pointer,
-                                               device, pointer_info->last_slave,
+                                               device, source_device,
                                                GDK_CROSSING_NORMAL,
                                                pointer_info->toplevel_x,
                                                pointer_info->toplevel_y,
@@ -9891,7 +10038,7 @@ _gdk_windowing_got_event (GdkDisplay *display,
 {
   GdkWindow *event_window;
   gdouble x, y;
-  gboolean unlink_event;
+  gboolean unlink_event = FALSE;
   GdkDeviceGrabInfo *button_release_grab;
   GdkPointerWindowInfo *pointer_info = NULL;
   GdkDevice *device, *source_device;
@@ -9934,7 +10081,7 @@ _gdk_windowing_got_event (GdkDisplay *display,
 
   event_window = event->any.window;
   if (!event_window)
-    return;
+    goto out;
 
 #ifdef DEBUG_WINDOW_PRINTING
   if (event->type == GDK_KEY_PRESS &&
@@ -9949,13 +10096,13 @@ _gdk_windowing_got_event (GdkDisplay *display,
     {
       event_window->native_visibility = event->visibility.state;
       gdk_window_update_visibility_recursively (event_window, event_window);
-      return;
+      goto out;
     }
 
   if (!(is_button_type (event->type) ||
         is_motion_type (event->type)) ||
       event_window->window_type == GDK_WINDOW_ROOT)
-    return;
+    goto out;
 
   is_toplevel = gdk_window_is_toplevel (event_window);
 
@@ -10048,7 +10195,6 @@ _gdk_windowing_got_event (GdkDisplay *display,
         _gdk_display_enable_motion_hints (display, device);
     }
 
-  unlink_event = FALSE;
   if (is_motion_type (event->type))
     unlink_event = proxy_pointer_event (display, event, serial);
   else if (is_button_type (event->type))
@@ -10090,6 +10236,13 @@ _gdk_windowing_got_event (GdkDisplay *display,
       g_list_free_1 (event_link);
       gdk_event_free (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);
 }
 
 /**
@@ -11104,19 +11257,23 @@ gdk_window_configure_finished (GdkWindow *window)
 
 /**
  * gdk_window_set_opacity:
- * @window: a top-level #GdkWindow
+ * @window: a top-level or non-native #GdkWindow
  * @opacity: opacity
  *
- * Request the windowing system to make @window partially transparent,
+ * Set @window to render as partially transparent,
  * with opacity 0 being fully transparent and 1 fully opaque. (Values
  * of the opacity parameter are clamped to the [0,1] range.) 
  *
- * On X11, this works only on X screens with a compositing manager 
- * running.
+ * For toplevel windows this depends on support from the windowing system
+ * that may not always be there. For instance, On X11, this works only on
+ * X screens with a compositing manager running.
+ *
+ * For child windows this function only works for non-native windows.
+ *
+ * For setting up per-pixel alpha topelevels, see gdk_screen_get_rgba_visual(),
+ * and for non-toplevels, see gdk_window_set_composited().
  *
- * For setting up per-pixel alpha, see gdk_screen_get_rgba_visual().
- * For making non-toplevel windows translucent, see 
- * gdk_window_set_composited().
+ * Support for non-toplevel windows was added in 3.8.
  *
  * Since: 2.12
  */
@@ -11124,7 +11281,23 @@ void
 gdk_window_set_opacity (GdkWindow *window,
                        gdouble    opacity)
 {
-  GDK_WINDOW_IMPL_GET_CLASS (window->impl)->set_opacity (window, opacity);
+  if (opacity < 0)
+    opacity = 0;
+  else if (opacity > 1)
+    opacity = 1;
+
+  window->alpha = round (opacity * 255);
+
+  if (window->destroyed)
+    return;
+
+  if (gdk_window_has_impl (window))
+    GDK_WINDOW_IMPL_GET_CLASS (window->impl)->set_opacity (window, opacity);
+  else
+    {
+      recompute_visible_regions (window, TRUE, FALSE);
+      gdk_window_invalidate_rect_full (window, NULL, TRUE, CLEAR_BG_ALL);
+    }
 }
 
 /* This function is called when the XWindow is really gone.
@@ -11435,3 +11608,156 @@ gdk_property_delete (GdkWindow *window,
 {
   GDK_WINDOW_IMPL_GET_CLASS (window->impl)->delete_property (window, property);
 }
+
+static void
+gdk_window_flush_events (GdkFrameClock *clock,
+                         void          *data)
+{
+  GdkWindow *window;
+  GdkDisplay *display;
+
+  window = GDK_WINDOW (data);
+
+  display = gdk_window_get_display (window);
+  _gdk_display_flush_events (display);
+  _gdk_display_pause_events (display);
+
+  gdk_frame_clock_request_phase (clock, GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS);
+}
+
+static void
+gdk_window_paint_on_clock (GdkFrameClock *clock,
+                          void          *data)
+{
+  GdkWindow *window;
+
+  window = GDK_WINDOW (data);
+
+  /* Update window and any children on the same clock.
+   */
+  gdk_window_process_updates_with_mode (window, PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN);
+}
+
+static void
+gdk_window_resume_events (GdkFrameClock *clock,
+                          void          *data)
+{
+  GdkWindow *window;
+  GdkDisplay *display;
+
+  window = GDK_WINDOW (data);
+
+  display = gdk_window_get_display (window);
+  _gdk_display_unpause_events (display);
+}
+
+/**
+ * gdk_window_set_frame_clock:
+ * @window: window to set frame clock on
+ * @clock: the clock
+ *
+ * Sets the frame clock for the window. The frame clock for a window
+ * cannot be changed while the window is mapped. Set the frame
+ * clock to #NULL to use the default frame clock. (By default the
+ * frame clock comes from the window's parent or is a global default
+ * frame clock.)
+ *
+ * Since: 3.0
+ */
+void
+gdk_window_set_frame_clock (GdkWindow     *window,
+                            GdkFrameClock *clock)
+{
+  g_return_if_fail (GDK_IS_WINDOW (window));
+  g_return_if_fail (clock == NULL || GDK_IS_FRAME_CLOCK (clock));
+  g_return_if_fail (!GDK_WINDOW_IS_MAPPED (window));
+
+  if (clock == window->frame_clock)
+    return;
+
+  /* If we are using our parent's clock, then the parent will repaint
+   * us when that clock fires. If we are using the default clock, then
+   * it does a gdk_window_process_all_updates() which will repaint us
+   * when the clock fires. If we are using our own clock, then we have
+   * to connect to "paint" on it ourselves and paint ourselves and
+   * any child windows.
+   */
+
+  if (clock)
+    {
+      g_object_ref (clock);
+      g_signal_connect (G_OBJECT (clock),
+                        "flush-events",
+                        G_CALLBACK (gdk_window_flush_events),
+                        window);
+      g_signal_connect (G_OBJECT (clock),
+                        "paint",
+                        G_CALLBACK (gdk_window_paint_on_clock),
+                        window);
+      g_signal_connect (G_OBJECT (clock),
+                        "resume-events",
+                        G_CALLBACK (gdk_window_resume_events),
+                        window);
+    }
+
+  if (window->frame_clock)
+    {
+      g_signal_handlers_disconnect_by_func (G_OBJECT (window->frame_clock),
+                                            G_CALLBACK (gdk_window_flush_events),
+                                            window);
+      g_signal_handlers_disconnect_by_func (G_OBJECT (window->frame_clock),
+                                            G_CALLBACK (gdk_window_paint_on_clock),
+                                            window);
+      g_signal_handlers_disconnect_by_func (G_OBJECT (window->frame_clock),
+                                            G_CALLBACK (gdk_window_resume_events),
+                                            window);
+      g_object_unref (window->frame_clock);
+    }
+
+  window->frame_clock = clock;
+  g_object_notify (G_OBJECT (window), "paint-clock");
+
+  /* We probably should recurse child windows and emit notify on their
+   * paint-clock properties also, and we should emit notify when a
+   * window is first parented.
+   */
+}
+
+/**
+ * gdk_window_get_frame_clock:
+ * @window: window to get frame clock for
+ *
+ * Gets the frame clock for the window. The frame clock for a window
+ * never changes while the window is mapped. It may be changed at
+ * other times.
+ *
+ * Since: 3.0
+ * Return value: (transfer none): the frame clock
+ */
+GdkFrameClock*
+gdk_window_get_frame_clock (GdkWindow *window)
+{
+  g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
+
+  if (window->frame_clock != NULL)
+    {
+      /* Frame clock set explicitly on this window */
+      return window->frame_clock;
+    }
+  else
+    {
+      GdkWindow *parent;
+
+      /* parent's frame clock or default */
+      parent = gdk_window_get_effective_parent (window);
+      if (parent != NULL)
+        {
+          return gdk_window_get_frame_clock (parent);
+        }
+      else
+        {
+          gdk_ensure_default_frame_clock ();
+          return _gdk_default_frame_clock;
+        }
+    }
+}