]> Pileus Git - ~andy/gtk/blobdiff - gdk/x11/gdkwindow-x11.c
GdkWindowX11: Only start a frame when we emit damage
[~andy/gtk] / gdk / x11 / gdkwindow-x11.c
index 70a6ba030346f25e7fc6ea1f84630b787ca07dea..f2c5735e79d5c8d26e6c7d4245c81547bbf2cf37 100644 (file)
@@ -35,6 +35,7 @@
 #include "gdkasync.h"
 #include "gdkeventsource.h"
 #include "gdkdisplay-x11.h"
+#include "gdkframeclockidle.h"
 #include "gdkprivate-x11.h"
 
 #include <stdlib.h>
@@ -97,6 +98,7 @@ const int _gdk_x11_event_mask_table[21] =
 const gint _gdk_x11_event_mask_table_size = G_N_ELEMENTS (_gdk_x11_event_mask_table);
 
 /* Forward declarations */
+static void     gdk_x11_window_apply_fullscreen_mode (GdkWindow  *window);
 static void     gdk_window_set_static_win_gravity (GdkWindow  *window,
                                                   gboolean    on);
 static gboolean gdk_window_icon_name_set          (GdkWindow  *window);
@@ -198,6 +200,135 @@ _gdk_x11_window_update_size (GdkWindowImplX11 *impl)
     }
 }
 
+static void
+set_sync_counter(Display     *display,
+                XSyncCounter counter,
+                 gint64       value)
+{
+    XSyncValue sync_value;
+
+    XSyncIntsToValue(&sync_value,
+                     value & G_GINT64_CONSTANT(0xFFFFFFFF),
+                     value >> 32);
+    XSyncSetCounter(display, counter, sync_value);
+}
+
+static void
+window_pre_damage (GdkWindow *window)
+{
+  GdkWindow *toplevel_window = gdk_window_get_toplevel (window);
+  GdkWindowImplX11 *impl;
+
+  if (!toplevel_window || !WINDOW_IS_TOPLEVEL (toplevel_window))
+    return;
+
+  impl = GDK_WINDOW_IMPL_X11 (toplevel_window->impl);
+
+  if (impl->toplevel->in_frame &&
+      impl->toplevel->current_counter_value % 2 == 0)
+    {
+      impl->toplevel->current_counter_value += 1;
+      set_sync_counter(GDK_WINDOW_XDISPLAY (impl->wrapper),
+                      impl->toplevel->extended_update_counter,
+                      impl->toplevel->current_counter_value);
+    }
+}
+
+static void
+on_surface_changed (void *data)
+{
+  GdkWindow *window = data;
+
+  window_pre_damage (window);
+}
+
+/* We want to know when cairo drawing causes damage to the window,
+ * so we engage in the _NET_WM_FRAME_DRAWN protocol with the
+ * window only when there actually is drawing. To do that we use
+ * a technique (hack) suggested by Uli Schlachter - if we set
+ * a dummy "mime data" on the cairo surface (this facility is
+ * used to attach JPEG data to an imager), then cairo wil flush
+ * and remove the mime data before making any changes to the window.
+ */
+
+static void
+hook_surface_changed (GdkWindow *window)
+{
+  GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
+
+  if (impl->cairo_surface)
+    cairo_surface_set_mime_data (impl->cairo_surface,
+                                 "x-gdk/change-notify",
+                                 (unsigned char *)"X",
+                                 1,
+                                 on_surface_changed,
+                                 window);
+}
+
+static void
+unhook_surface_changed (GdkWindow *window)
+{
+  GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
+
+  if (impl->cairo_surface)
+    cairo_surface_set_mime_data (impl->cairo_surface,
+                                 "x-gdk/change-notify",
+                                 NULL, 0,
+                                 NULL, NULL);
+}
+
+static void
+gdk_x11_window_begin_frame (GdkWindow *window)
+{
+  GdkWindowImplX11 *impl;
+
+  g_return_if_fail (GDK_IS_WINDOW (window));
+
+  impl = GDK_WINDOW_IMPL_X11 (window->impl);
+
+  if (!WINDOW_IS_TOPLEVEL (window) ||
+      impl->toplevel->extended_update_counter == None)
+    return;
+
+  impl->toplevel->in_frame = TRUE;
+
+  hook_surface_changed (window);
+}
+
+static void
+gdk_x11_window_end_frame (GdkWindow *window)
+{
+  GdkWindowImplX11 *impl;
+
+  g_return_if_fail (GDK_IS_WINDOW (window));
+
+  impl = GDK_WINDOW_IMPL_X11 (window->impl);
+
+  if (!WINDOW_IS_TOPLEVEL (window) ||
+      impl->toplevel->extended_update_counter == None ||
+      !impl->toplevel->in_frame)
+    return;
+
+  impl->toplevel->in_frame = FALSE;
+
+  if (impl->toplevel->current_counter_value % 2 == 1)
+    {
+      impl->toplevel->current_counter_value += 1;
+      set_sync_counter(GDK_WINDOW_XDISPLAY (impl->wrapper),
+                      impl->toplevel->extended_update_counter,
+                      impl->toplevel->current_counter_value);
+
+      if (gdk_x11_screen_supports_net_wm_hint (gdk_window_get_screen (window),
+                                              gdk_atom_intern_static_string ("_NET_WM_FRAME_DRAWN")))
+        {
+          impl->toplevel->frame_pending = TRUE;
+          gdk_frame_clock_freeze (gdk_window_get_frame_clock (window));
+        }
+    }
+
+  unhook_surface_changed (window);
+}
+
 /*****************************************************
  * X11 specific implementations of generic functions *
  *****************************************************/
@@ -241,6 +372,9 @@ gdk_x11_ref_cairo_surface (GdkWindow *window)
       if (impl->cairo_surface)
        cairo_surface_set_user_data (impl->cairo_surface, &gdk_x11_cairo_key,
                                     impl, gdk_x11_cairo_surface_destroy);
+
+      if (WINDOW_IS_TOPLEVEL (window) && impl->toplevel->in_frame)
+        hook_surface_changed (window);
     }
   else
     cairo_surface_reference (impl->cairo_surface);
@@ -260,6 +394,9 @@ gdk_window_impl_x11_finalize (GObject *object)
 
   wrapper = impl->wrapper;
 
+  if (WINDOW_IS_TOPLEVEL (wrapper) && impl->toplevel->in_frame)
+    unhook_surface_changed (wrapper);
+
   _gdk_x11_window_grab_check_destroy (wrapper);
 
   if (!GDK_WINDOW_DESTROYED (wrapper))
@@ -601,29 +738,32 @@ ensure_sync_counter (GdkWindow *window)
     {
       GdkDisplay *display = GDK_WINDOW_DISPLAY (window);
       GdkToplevelX11 *toplevel = _gdk_x11_window_get_toplevel (window);
-      GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
 
-      if (toplevel && impl->use_synchronized_configure &&
+      if (toplevel &&
          toplevel->update_counter == None &&
          GDK_X11_DISPLAY (display)->use_sync)
        {
          Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
          XSyncValue value;
          Atom atom;
+         XID counters[2];
 
          XSyncIntToValue (&value, 0);
          
          toplevel->update_counter = XSyncCreateCounter (xdisplay, value);
+         toplevel->extended_update_counter = XSyncCreateCounter (xdisplay, value);
          
          atom = gdk_x11_get_xatom_by_name_for_display (display,
                                                        "_NET_WM_SYNC_REQUEST_COUNTER");
-         
+
+         counters[0] = toplevel->update_counter;
+         counters[1] = toplevel->extended_update_counter;
          XChangeProperty (xdisplay, GDK_WINDOW_XID (window),
                           atom, XA_CARDINAL,
                           32, PropModeReplace,
-                          (guchar *)&toplevel->update_counter, 1);
+                          (guchar *)counters, 2);
          
-         XSyncIntToValue (&toplevel->current_counter_value, 0);
+         toplevel->current_counter_value = 0;
        }
     }
 #endif
@@ -697,6 +837,23 @@ setup_toplevel_window (GdkWindow *window,
     gdk_x11_window_set_user_time (window, GDK_X11_DISPLAY (x11_screen->display)->user_time);
 
   ensure_sync_counter (window);
+
+  /* Start off in a frozen state - we'll finish this when we first paint */
+  gdk_x11_window_begin_frame (window);
+}
+
+static void
+on_frame_clock_before_paint (GdkFrameClock *clock,
+                             GdkWindow     *window)
+{
+  gdk_x11_window_begin_frame (window);
+}
+
+static void
+on_frame_clock_after_paint (GdkFrameClock *clock,
+                            GdkWindow     *window)
+{
+  gdk_x11_window_end_frame (window);
 }
 
 void
@@ -711,6 +868,7 @@ _gdk_x11_display_create_window_impl (GdkDisplay    *display,
   GdkWindowImplX11 *impl;
   GdkX11Screen *x11_screen;
   GdkX11Display *display_x11;
+  GdkFrameClock *clock;
 
   Window xparent;
   Visual *xvisual;
@@ -855,6 +1013,16 @@ _gdk_x11_display_create_window_impl (GdkDisplay    *display,
   gdk_x11_event_source_select_events ((GdkEventSource *) display_x11->event_source,
                                       GDK_WINDOW_XID (window), event_mask,
                                       StructureNotifyMask | PropertyChangeMask);
+
+  clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE, NULL);
+  gdk_window_set_frame_clock (window, clock);
+  g_signal_connect (clock, "before-paint",
+                    G_CALLBACK (on_frame_clock_before_paint), window);
+  g_signal_connect (clock, "after-paint",
+                    G_CALLBACK (on_frame_clock_after_paint), window);
+
+  if (GDK_WINDOW_TYPE (window) != GDK_WINDOW_CHILD)
+    gdk_window_freeze_toplevel_updates_libgtk_only (window);
 }
 
 static GdkEventMask
@@ -1000,7 +1168,7 @@ gdk_toplevel_x11_free_contents (GdkDisplay *display,
                           toplevel->update_counter);
       toplevel->update_counter = None;
 
-      XSyncIntToValue (&toplevel->current_counter_value, 0);
+      toplevel->current_counter_value = 0;
     }
 #endif
 }
@@ -1354,6 +1522,13 @@ gdk_window_x11_show (GdkWindow *window, gboolean already_mapped)
   
   if (unset_bg)
     _gdk_x11_window_tmp_reset_bg (window, TRUE);
+
+  /* Fullscreen on current monitor is the default, no need to apply this mode
+   * when mapping a window. This also ensures that the default behavior remains
+   * consistent with pre-fullscreen mode implementation.
+   */
+  if (window->fullscreen_mode != GDK_FULLSCREEN_ON_CURRENT_MONITOR)
+    gdk_x11_window_apply_fullscreen_mode (window);
 }
 
 static void
@@ -1465,6 +1640,8 @@ window_x11_move (GdkWindow *window,
 
   if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_CHILD)
     {
+      /* The window isn't actually damaged, but it's parent is */
+      window_pre_damage (window);
       _gdk_x11_window_move_resize_child (window,
                                          x, y,
                                          window->width, window->height);
@@ -1494,6 +1671,8 @@ window_x11_resize (GdkWindow *window,
   if (height < 1)
     height = 1;
 
+  window_pre_damage (window);
+
   if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_CHILD)
     {
       _gdk_x11_window_move_resize_child (window,
@@ -1537,6 +1716,8 @@ window_x11_move_resize (GdkWindow *window,
   if (height < 1)
     height = 1;
 
+  window_pre_damage (window);
+
   if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_CHILD)
     {
       _gdk_x11_window_move_resize_child (window, x, y, width, height);
@@ -3626,17 +3807,116 @@ gdk_x11_window_unmaximize (GdkWindow *window)
 }
 
 static void
-gdk_x11_window_fullscreen (GdkWindow *window)
+gdk_x11_window_apply_fullscreen_mode (GdkWindow *window)
 {
   if (GDK_WINDOW_DESTROYED (window) ||
       !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window))
     return;
 
+  /* _NET_WM_FULLSCREEN_MONITORS gives an indication to the window manager as
+   * to which monitors so span across when the window is fullscreen, but it's
+   * not a state in itself so this would have no effect if the window is not
+   * mapped.
+   */
+
   if (GDK_WINDOW_IS_MAPPED (window))
-    gdk_wmspec_change_state (TRUE, window,
-                            gdk_atom_intern_static_string ("_NET_WM_STATE_FULLSCREEN"),
-                             GDK_NONE);
+    {
+      XClientMessageEvent xclient;
+      gint                gdk_monitors[4];
+      gint                i;
 
+      memset (&xclient, 0, sizeof (xclient));
+      xclient.type = ClientMessage;
+      xclient.window = GDK_WINDOW_XID (window);
+      xclient.display = GDK_WINDOW_XDISPLAY (window);
+      xclient.format = 32;
+
+      switch (window->fullscreen_mode)
+       {
+       case GDK_FULLSCREEN_ON_CURRENT_MONITOR:
+
+         /* FIXME: This is not part of the EWMH spec!
+          *
+          * There is no documented mechanism to remove the property
+          * _NET_WM_FULLSCREEN_MONITORS once set, so we use use a set of
+          * invalid, largest possible value.
+          *
+          * When given values larger than actual possible monitor values, most
+          * window managers who support the _NET_WM_FULLSCREEN_MONITORS spec
+          * will simply unset _NET_WM_FULLSCREEN_MONITORS and revert to their
+          * default behavior.
+          *
+          * Successfully tested on mutter/metacity, kwin, compiz and xfwm4.
+          *
+          * Note, this (non documented) mechanism is unlikely to be an issue
+          * as it's used only for transitionning back from "all monitors" to
+          * "current monitor" mode.
+          *
+          * Applications who don't change the default mode won't trigger this
+          * mechanism.
+          */
+         for (i = 0; i < 4; ++i)
+           xclient.data.l[i] = G_MAXLONG;
+
+         break;
+
+       case GDK_FULLSCREEN_ON_ALL_MONITORS:
+
+         _gdk_x11_screen_get_edge_monitors (GDK_WINDOW_SCREEN (window),
+                                            &gdk_monitors[0],
+                                            &gdk_monitors[1],
+                                            &gdk_monitors[2],
+                                            &gdk_monitors[3]);
+         /* Translate all 4 monitors from the GDK set into XINERAMA indices */
+         for (i = 0; i < 4; ++i)
+           {
+             xclient.data.l[i] = _gdk_x11_screen_get_xinerama_index (GDK_WINDOW_SCREEN (window),
+                                                                     gdk_monitors[i]);
+             /* Sanity check, if XINERAMA is not available, we could have invalid
+              * negative values for the XINERAMA indices.
+              */
+             if (xclient.data.l[i] < 0)
+               {
+                 g_warning ("gdk_x11_window_apply_fullscreen_mode: Invalid XINERAMA monitor index");
+                 return;
+               }
+           }
+         break;
+
+       default:
+         g_warning ("gdk_x11_window_apply_fullscreen_mode: Unhandled fullscreen mode %d",
+                    window->fullscreen_mode);
+         return;
+       }
+
+      /* Send fullscreen monitors client message */
+      xclient.data.l[4] = 1; /* source indication */
+      xclient.message_type = gdk_x11_get_xatom_by_name_for_display (GDK_WINDOW_DISPLAY (window),
+                                                                   "_NET_WM_FULLSCREEN_MONITORS");
+      XSendEvent (GDK_WINDOW_XDISPLAY (window), GDK_WINDOW_XROOTWIN (window), False,
+                  SubstructureRedirectMask | SubstructureNotifyMask,
+                  (XEvent *)&xclient);
+    }
+}
+
+static void
+gdk_x11_window_fullscreen (GdkWindow *window)
+{
+  if (GDK_WINDOW_DESTROYED (window) ||
+      !WINDOW_IS_TOPLEVEL_OR_FOREIGN (window))
+    return;
+
+  if (GDK_WINDOW_IS_MAPPED (window))
+    {
+      gdk_wmspec_change_state (TRUE, window,
+                              gdk_atom_intern_static_string ("_NET_WM_STATE_FULLSCREEN"),
+                               GDK_NONE);
+      /* Actual XRandR layout may have change since we computed the fullscreen
+       * monitors in GDK_FULLSCREEN_ON_ALL_MONITORS mode.
+       */
+      if (window->fullscreen_mode == GDK_FULLSCREEN_ON_ALL_MONITORS)
+       gdk_x11_window_apply_fullscreen_mode (window);
+    }
   else
     gdk_synthesize_window_state (window,
                                  0,
@@ -4751,13 +5031,21 @@ gdk_x11_window_configure_finished (GdkWindow *window)
 
       if (toplevel && toplevel->update_counter != None &&
          GDK_X11_DISPLAY (display)->use_sync &&
-         !XSyncValueIsZero (toplevel->current_counter_value))
+         toplevel->configure_counter_value != 0)
        {
-         XSyncSetCounter (GDK_WINDOW_XDISPLAY (window), 
-                          toplevel->update_counter,
-                          toplevel->current_counter_value);
-         
-         XSyncIntToValue (&toplevel->current_counter_value, 0);
+         set_sync_counter (GDK_WINDOW_XDISPLAY (window),
+                           toplevel->update_counter,
+                           toplevel->configure_counter_value);
+
+         toplevel->current_counter_value = toplevel->configure_counter_value;
+         if ((toplevel->current_counter_value % 2) == 1)
+           toplevel->current_counter_value += 1;
+
+         toplevel->configure_counter_value = 0;
+
+         set_sync_counter (GDK_WINDOW_XDISPLAY (window),
+                           toplevel->extended_update_counter,
+                           toplevel->current_counter_value);
        }
     }
 #endif
@@ -5022,6 +5310,7 @@ gdk_window_impl_x11_class_init (GdkWindowImplX11Class *klass)
   impl_class->maximize = gdk_x11_window_maximize;
   impl_class->unmaximize = gdk_x11_window_unmaximize;
   impl_class->fullscreen = gdk_x11_window_fullscreen;
+  impl_class->apply_fullscreen_mode = gdk_x11_window_apply_fullscreen_mode;
   impl_class->unfullscreen = gdk_x11_window_unfullscreen;
   impl_class->set_keep_above = gdk_x11_window_set_keep_above;
   impl_class->set_keep_below = gdk_x11_window_set_keep_below;