[gtk+] GDK W32: Use layered windows



commit c05f254a6e3a715105f10c495caebed9a3a682c9
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date:   Tue Feb 23 09:20:55 2016 +0000

    GDK W32: Use layered windows
    
    Toplevels are now true layered windows that are moved,
    resized and repainted via UpdateLayeredWindow() API call.
    This achieves transparency without any extra effort,
    and prevents window size and window contents desychronization
    (bug 761629).
    
    This also changes the way CSD windows are detected. We now
    use window decorations to detect CSDiness of a window,
    and to decide whether a window should be layered (CSD windows should
    be) or not.
    
    Decorations are now stored in the window implementation,
    not as a quark-based property of the window-as-gobject.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=748872

 gdk/win32/gdkevents-win32.c  |    9 +-
 gdk/win32/gdkprivate-win32.h |    2 +
 gdk/win32/gdkwindow-win32.c  |  315 +++++++++++++++++++++++++++++++++++++-----
 gdk/win32/gdkwindow-win32.h  |   30 ++++
 4 files changed, 314 insertions(+), 42 deletions(-)
---
diff --git a/gdk/win32/gdkevents-win32.c b/gdk/win32/gdkevents-win32.c
index da21e9f..5058545 100644
--- a/gdk/win32/gdkevents-win32.c
+++ b/gdk/win32/gdkevents-win32.c
@@ -1645,17 +1645,14 @@ handle_nchittest (HWND hwnd,
                   gint *ret_valp)
 {
   RECT rect;
-  LONG style;
 
   if (window == NULL || window->input_shape == NULL)
     return FALSE;
 
-  style = GetWindowLong (hwnd, GWL_STYLE);
-
-  /* Assume that these styles are incompatible with CSD,
-   * so there's no reason for us to override the defaults.
+  /* If the window has decorations, DefWindowProc() will take
+   * care of NCHITTEST.
    */
-  if (style & (WS_BORDER | WS_THICKFRAME))
+  if (!_gdk_win32_window_lacks_wm_decorations (window))
     return FALSE;
 
   if (!GetWindowRect (hwnd, &rect))
diff --git a/gdk/win32/gdkprivate-win32.h b/gdk/win32/gdkprivate-win32.h
index ba79a1b..b604df6 100644
--- a/gdk/win32/gdkprivate-win32.h
+++ b/gdk/win32/gdkprivate-win32.h
@@ -535,6 +535,8 @@ void      gdk_win32_window_end_move_resize_drag (GdkWindow  *window);
 gboolean _gdk_win32_window_fill_min_max_info    (GdkWindow  *window,
                                                  MINMAXINFO *mmi);
 
+gboolean _gdk_win32_window_lacks_wm_decorations (GdkWindow *window);
+
 /* Initialization */
 void _gdk_win32_windowing_init (void);
 void _gdk_dnd_init    (void);
diff --git a/gdk/win32/gdkwindow-win32.c b/gdk/win32/gdkwindow-win32.c
index 513a3bf..cc88413 100644
--- a/gdk/win32/gdkwindow-win32.c
+++ b/gdk/win32/gdkwindow-win32.c
@@ -181,22 +181,38 @@ gdk_window_impl_win32_finalize (GObject *object)
       window_impl->hicon_small = NULL;
     }
 
+  g_free (window_impl->decorations);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
+static gboolean
+gdk_win32_window_begin_paint (GdkWindow *window)
+{
+  GdkWindowImplWin32 *impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
+  return !impl->layered;
+}
+
 static void
 gdk_win32_window_end_paint (GdkWindow *window)
 {
   GdkWindowImplWin32 *impl;
   RECT window_rect;
   gint x, y;
+  HDC hdc;
+  POINT window_position;
+  SIZE window_size;
+  POINT source_point;
+  BLENDFUNCTION blender;
+  cairo_t *cr;
 
   if (window == NULL || GDK_WINDOW_DESTROYED (window))
     return;
 
   impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
 
-  if (!impl->drag_move_resize_context.native_move_resize_pending)
+  if (!impl->layered && !impl->drag_move_resize_context.native_move_resize_pending)
     return;
 
   impl->drag_move_resize_context.native_move_resize_pending = FALSE;
@@ -216,16 +232,57 @@ gdk_win32_window_end_paint (GdkWindow *window)
   window_rect.top -= _gdk_offset_y;
   window_rect.bottom -= _gdk_offset_y;
 
-  GDK_NOTE (EVENTS, g_print ("Setting window position ... "));
+  if (!impl->layered)
+    {
+      GDK_NOTE (EVENTS, g_print ("Setting window position ... "));
+
+      API_CALL (SetWindowPos, (GDK_WINDOW_HWND (window),
+                               SWP_NOZORDER_SPECIFIED,
+                              window_rect.left, window_rect.top,
+                               window_rect.right - window_rect.left,
+                               window_rect.bottom - window_rect.top,
+                               SWP_NOACTIVATE | SWP_NOZORDER));
+
+      GDK_NOTE (EVENTS, g_print (" ... set window position\n"));
+
+      return;
+    }
+
+  window_position.x = window_rect.left;
+  window_position.y = window_rect.top;
+  window_size.cx = window_rect.right - window_rect.left;
+  window_size.cy = window_rect.bottom - window_rect.top;
+
+  cairo_surface_flush (impl->cairo_surface);
 
-  API_CALL (SetWindowPos, (GDK_WINDOW_HWND (window),
-                           SWP_NOZORDER_SPECIFIED,
-                          window_rect.left, window_rect.top,
-                           window_rect.right - window_rect.left,
-                           window_rect.bottom - window_rect.top,
-                          SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW));
+  /* we always draw in the top-left corner of the surface */
+  source_point.x = source_point.y = 0;
 
-  GDK_NOTE (EVENTS, g_print (" ... set window position\n"));
+  blender.BlendOp = AC_SRC_OVER;
+  blender.BlendFlags = 0;
+  blender.AlphaFormat = AC_SRC_ALPHA;
+  blender.SourceConstantAlpha = impl->layered_opacity * 255;
+
+  /* Update cache surface contents */
+  cr = cairo_create (impl->cache_surface);
+
+  cairo_set_source_surface (cr, window->current_paint.surface, 0, 0);
+  gdk_cairo_region (cr, window->current_paint.region);
+  cairo_clip (cr);
+
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+  cairo_paint (cr);
+
+  cairo_destroy (cr);
+
+  cairo_surface_flush (impl->cache_surface);
+  hdc = cairo_win32_surface_get_dc (impl->cache_surface);
+
+  /* Move, resize and redraw layered window in one call */
+  API_CALL (UpdateLayeredWindow, (GDK_WINDOW_HWND (window), NULL,
+                                  &window_position, &window_size,
+                                  hdc, &source_point,
+                                  0, &blender, ULW_ALPHA));
 }
 
 void
@@ -242,6 +299,7 @@ _gdk_win32_adjust_client_rect (GdkWindow *window,
 gboolean
 _gdk_win32_window_enable_transparency (GdkWindow *window)
 {
+  GdkWindowImplWin32 *impl;
   GdkScreen *screen;
   DWM_BLURBEHIND blur_behind;
   HRGN empty_region;
@@ -251,6 +309,12 @@ _gdk_win32_window_enable_transparency (GdkWindow *window)
   if (window == NULL || GDK_WINDOW_HWND (window) == NULL)
     return FALSE;
 
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
+  /* layered windows don't need blurbehind for transparency */
+  if (impl->layered)
+    return TRUE;
+
   screen = gdk_window_get_screen (window);
 
   if (!gdk_screen_is_composited (screen))
@@ -537,6 +601,8 @@ _gdk_win32_display_create_window_impl (GdkDisplay    *display,
               (gdk_screen_get_rgba_visual (screen) == attributes->visual));
 
   impl->override_redirect = override_redirect;
+  impl->layered = FALSE;
+  impl->layered_opacity = 1.0;
 
   /* wclass is not any longer set always, but if is ... */
   if ((attributes_mask & GDK_WA_WMCLASS) == GDK_WA_WMCLASS)
@@ -2435,6 +2501,72 @@ update_single_bit (LONG    *style,
     *style &= ~style_bit;
 }
 
+/*
+ * Returns TRUE if window has no decorations.
+ * Usually it means CSD windows, because GTK
+ * calls gdk_window_set_decorations (window, 0);
+ * This is used to decide whether a toplevel should
+ * be made layered, thus it
+ * only returns TRUE for toplevels (until GTK minimal
+ * system requirements are lifted to Windows 8 or newer,
+ * because only toplevels can be layered).
+ */
+gboolean
+_gdk_win32_window_lacks_wm_decorations (GdkWindow *window)
+{
+  GdkWindowImplWin32 *impl;
+  LONG style;
+  gboolean has_any_decorations;
+
+  if (GDK_WINDOW_DESTROYED (window))
+    return FALSE;
+
+  /* only toplevels can be layered */
+  if (!WINDOW_IS_TOPLEVEL (window))
+    return FALSE;
+
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
+  /* This is because GTK calls gdk_window_set_decorations (window, 0),
+   * even though GdkWMDecoration docs indicate that 0 does NOT mean
+   * "no decorations".
+   */
+  if (impl->decorations &&
+      *impl->decorations == 0)
+    return TRUE;
+
+  if (GDK_WINDOW_HWND (window) == 0)
+    return FALSE;
+
+  style = GetWindowLong (GDK_WINDOW_HWND (window), GWL_STYLE);
+
+  if (style == 0)
+    {
+      DWORD w32_error = GetLastError ();
+
+      GDK_NOTE (MISC, g_print ("Failed to get style of window %p (handle %p): %lu\n",
+                               window, GDK_WINDOW_HWND (window), w32_error));
+      return FALSE;
+    }
+
+  /* Keep this in sync with update_style_bits() */
+  /* We don't check what get_effective_window_decorations()
+   * has to say, because it gives suggestions based on
+   * various hints, while we want *actual* decorations,
+   * or their absence.
+   */
+  has_any_decorations = FALSE;
+
+  if (style & (WS_BORDER | WS_THICKFRAME | WS_CAPTION |
+               WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX))
+    has_any_decorations = TRUE;
+  else
+    GDK_NOTE (MISC, g_print ("Window %p (handle %p): has no decorations (style %lx)\n",
+                             window, GDK_WINDOW_HWND (window), style));
+
+  return !has_any_decorations;
+}
+
 static void
 update_style_bits (GdkWindow *window)
 {
@@ -2480,9 +2612,22 @@ update_style_bits (GdkWindow *window)
       new_exstyle &= ~WS_EX_TOOLWINDOW;
     }
 
+  /* We can get away with using layered windows
+   * only when no decorations are needed. It can mean
+   * CSD or borderless non-CSD windows (tooltips?).
+   */
+  if (_gdk_win32_window_lacks_wm_decorations (window))
+    impl->layered = g_strcmp0 (g_getenv ("GDK_WIN32_LAYERED"), "0") != 0;
+
+  if (impl->layered)
+    new_exstyle |= WS_EX_LAYERED;
+  else
+    new_exstyle &= ~WS_EX_LAYERED;
+
   if (get_effective_window_decorations (window, &decorations))
     {
       all = (decorations & GDK_DECOR_ALL);
+      /* Keep this in sync with the test in _gdk_win32_window_lacks_wm_decorations() */
       update_single_bit (&new_style, all, decorations & GDK_DECOR_BORDER, WS_BORDER);
       update_single_bit (&new_style, all, decorations & GDK_DECOR_RESIZEH, WS_THICKFRAME);
       update_single_bit (&new_style, all, decorations & GDK_DECOR_TITLE, WS_CAPTION);
@@ -2583,25 +2728,17 @@ update_system_menu (GdkWindow *window)
     }
 }
 
-static GQuark
-get_decorations_quark ()
-{
-  static GQuark quark = 0;
-
-  if (!quark)
-    quark = g_quark_from_static_string ("gdk-window-decorations");
-
-  return quark;
-}
-
 static void
 gdk_win32_window_set_decorations (GdkWindow      *window,
-                           GdkWMDecoration decorations)
+                                 GdkWMDecoration decorations)
 {
+  GdkWindowImplWin32 *impl;
   GdkWMDecoration* decorations_copy;
 
   g_return_if_fail (GDK_IS_WINDOW (window));
 
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
   GDK_NOTE (MISC, g_print ("gdk_window_set_decorations: %p: %s %s%s%s%s%s%s\n",
                           GDK_WINDOW_HWND (window),
                           (decorations & GDK_DECOR_ALL ? "clearing" : "setting"),
@@ -2612,26 +2749,30 @@ gdk_win32_window_set_decorations (GdkWindow      *window,
                           (decorations & GDK_DECOR_MINIMIZE ? "MINIMIZE " : ""),
                           (decorations & GDK_DECOR_MAXIMIZE ? "MAXIMIZE " : "")));
 
-  decorations_copy = g_malloc (sizeof (GdkWMDecoration));
-  *decorations_copy = decorations;
-  g_object_set_qdata_full (G_OBJECT (window), get_decorations_quark (), decorations_copy, g_free);
+  if (!impl->decorations)
+    impl->decorations = g_malloc (sizeof (GdkWMDecoration));
+
+  *impl->decorations = decorations;
 
   update_style_bits (window);
 }
 
 static gboolean
 gdk_win32_window_get_decorations (GdkWindow       *window,
-                           GdkWMDecoration *decorations)
+                                 GdkWMDecoration *decorations)
 {
-  GdkWMDecoration* decorations_set;
+  GdkWindowImplWin32 *impl;
 
   g_return_val_if_fail (GDK_IS_WINDOW (window), FALSE);
 
-  decorations_set = g_object_get_qdata (G_OBJECT (window), get_decorations_quark ());
-  if (decorations_set)
-    *decorations = *decorations_set;
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
+  if (impl->decorations == NULL)
+    return FALSE;
 
-  return (decorations_set != NULL);
+  *decorations = *impl->decorations;
+
+  return TRUE;
 }
 
 static GQuark
@@ -2907,6 +3048,7 @@ gdk_win32_window_do_move_resize_drag (GdkWindow *window,
             rect.top != new_rect.top))
     {
       POINT window_position;
+      BLENDFUNCTION blender;
 
       context->native_move_resize_pending = FALSE;
 
@@ -2924,12 +3066,22 @@ gdk_win32_window_do_move_resize_drag (GdkWindow *window,
       window_position.x = new_rect.left;
       window_position.y = new_rect.top;
 
-      /* Move immediately, no need to wait for redraw */
-      API_CALL (SetWindowPos, (GDK_WINDOW_HWND (window),
-                               SWP_NOZORDER_SPECIFIED,
-                               window_position.x, window_position.y,
-                               0, 0,
-                               SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE));
+      blender.BlendOp = AC_SRC_OVER;
+      blender.BlendFlags = 0;
+      blender.AlphaFormat = AC_SRC_ALPHA;
+      blender.SourceConstantAlpha = impl->layered_opacity * 255;
+
+      /* Size didn't change, so move immediately, no need to wait for redraw */
+      if (impl->layered)
+        API_CALL (UpdateLayeredWindow, (GDK_WINDOW_HWND (window), NULL,
+                                      &window_position, NULL, NULL, NULL,
+                                      0, &blender, ULW_ALPHA));
+      else
+        API_CALL (SetWindowPos, (GDK_WINDOW_HWND (window),
+                                 SWP_NOZORDER_SPECIFIED,
+                                 window_position.x, window_position.y,
+                                 0, 0,
+                                 SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE));
     }
 }
 
@@ -3507,6 +3659,7 @@ gdk_win32_window_set_opacity (GdkWindow *window,
   LONG exstyle;
   typedef BOOL (WINAPI *PFN_SetLayeredWindowAttributes) (HWND, COLORREF, BYTE, DWORD);
   PFN_SetLayeredWindowAttributes setLayeredWindowAttributes = NULL;
+  GdkWindowImplWin32 *impl;
 
   g_return_if_fail (GDK_IS_WINDOW (window));
 
@@ -3518,6 +3671,14 @@ gdk_win32_window_set_opacity (GdkWindow *window,
   else if (opacity > 1)
     opacity = 1;
 
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
+  impl->layered_opacity = opacity;
+
+  if (impl->layered)
+    /* Layered windows have opacity applied elsewhere */
+    return;
+
   exstyle = GetWindowLong (GDK_WINDOW_HWND (window), GWL_EXSTYLE);
 
   if (!(exstyle & WS_EX_LAYERED))
@@ -3589,6 +3750,12 @@ _gdk_win32_impl_acquire_dc (GdkWindowImplWin32 *impl)
       GDK_WINDOW_DESTROYED (impl->wrapper))
     return NULL;
 
+  /* We don't call this function for layered windows, but
+   * in case we do...
+   */
+  if (impl->layered)
+    return NULL;
+
   if (!impl->hdc)
     {
       impl->hdc = GetDC (impl->handle);
@@ -3617,6 +3784,9 @@ _gdk_win32_impl_acquire_dc (GdkWindowImplWin32 *impl)
 static void
 _gdk_win32_impl_release_dc (GdkWindowImplWin32 *impl)
 {
+  if (impl->layered)
+    return;
+
   g_return_if_fail (impl->hdc_count > 0);
 
   impl->hdc_count--;
@@ -3654,6 +3824,75 @@ gdk_win32_cairo_surface_destroy (void *data)
 }
 
 static cairo_surface_t *
+gdk_win32_ref_cairo_surface_layered (GdkWindow          *window,
+                                     GdkWindowImplWin32 *impl)
+{
+  gint x, y, width, height;
+  RECT window_rect;
+
+  gdk_window_get_position (window, &x, &y);
+  window_rect.left = x;
+  window_rect.top = y;
+  window_rect.right = window_rect.left + gdk_window_get_width (window);
+  window_rect.bottom = window_rect.top + gdk_window_get_height (window);
+
+  /* Turn client area into window area */
+  _gdk_win32_adjust_client_rect (window, &window_rect);
+
+  width = window_rect.right - window_rect.left;
+  height = window_rect.bottom - window_rect.top;
+
+  if (width > impl->dib_width ||
+      height > impl->dib_height)
+    {
+      cairo_surface_t *new_cache;
+      cairo_t *cr;
+
+      /* Create larger cache surface, copy old cache surface over it */
+      new_cache = cairo_win32_surface_create_with_dib (CAIRO_FORMAT_ARGB32, width, height);
+
+      if (impl->cache_surface)
+        {
+          cr = cairo_create (new_cache);
+          cairo_set_source_surface (cr, impl->cache_surface, 0, 0);
+          cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+          cairo_paint (cr);
+          cairo_destroy (cr);
+          cairo_surface_flush (new_cache);
+
+          cairo_surface_destroy (impl->cache_surface);
+        }
+
+      impl->cache_surface = new_cache;
+
+      if (impl->cairo_surface)
+        cairo_surface_destroy (impl->cairo_surface);
+
+      impl->cairo_surface = NULL;
+    }
+
+  /* This is separate, because cairo_surface gets killed
+   * off frequently by outside code, whereas cache_surface
+   * is only killed by us, above.
+   */
+  if (!impl->cairo_surface)
+    {
+      impl->cairo_surface = cairo_win32_surface_create_with_dib (CAIRO_FORMAT_ARGB32, width, height);
+      impl->dib_width = width;
+      impl->dib_height = height;
+
+      cairo_surface_set_user_data (impl->cairo_surface, &gdk_win32_cairo_key,
+                                  impl, gdk_win32_cairo_surface_destroy);
+    }
+  else
+    {
+      cairo_surface_reference (impl->cairo_surface);
+    }
+
+  return impl->cairo_surface;
+}
+
+static cairo_surface_t *
 gdk_win32_ref_cairo_surface (GdkWindow *window)
 {
   GdkWindowImplWin32 *impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
@@ -3662,6 +3901,9 @@ gdk_win32_ref_cairo_surface (GdkWindow *window)
       GDK_WINDOW_DESTROYED (impl->wrapper))
     return NULL;
 
+  if (impl->layered)
+    return gdk_win32_ref_cairo_surface_layered (window, impl);
+
   if (!impl->cairo_surface)
     {
       HDC hdc = _gdk_win32_impl_acquire_dc (impl);
@@ -3714,6 +3956,7 @@ gdk_window_impl_win32_class_init (GdkWindowImplWin32Class *klass)
   impl_class->destroy_foreign = gdk_win32_window_destroy_foreign;
   impl_class->get_shape = gdk_win32_window_get_shape;
   //FIXME?: impl_class->get_input_shape = gdk_win32_window_get_input_shape;
+  impl_class->begin_paint = gdk_win32_window_begin_paint;
   impl_class->end_paint = gdk_win32_window_end_paint;
 
   //impl_class->beep = gdk_x11_window_beep;
diff --git a/gdk/win32/gdkwindow-win32.h b/gdk/win32/gdkwindow-win32.h
index b7389f1..6389f7b 100644
--- a/gdk/win32/gdkwindow-win32.h
+++ b/gdk/win32/gdkwindow-win32.h
@@ -126,12 +126,42 @@ struct _GdkWindowImplWin32
   guint inhibit_configure : 1;
   guint override_redirect : 1;
 
+  /* Set to TRUE if window is using true layered mode adjustments
+   * via UpdateLayeredWindow().
+   * Layered windows that get SetLayeredWindowAttributes() called
+   * on them are not true layered windows.
+   */
+  guint layered : 1;
+
+  /* GDK does not keep window contents around, it just draws new
+   * stuff over the window where changes occurred.
+   * cache_surface retains old window contents, because
+   * UpdateLayeredWindow() doesn't do partial redraws.
+   */
+  cairo_surface_t *cache_surface;
   cairo_surface_t *cairo_surface;
+
+  /* Unlike window-backed surfaces, DIB-backed surface
+   * does not provide a way to query its size,
+   * so we have to remember it ourselves.
+   */
+  gint             dib_width;
+  gint             dib_height;
+
+  /* If the client wants uniformly-transparent window,
+   * we remember the opacity value here and apply it
+   * during UpdateLayredWindow() call, for layered windows.
+   */
+  gdouble          layered_opacity;
+
   HDC              hdc;
   int              hdc_count;
   HBITMAP          saved_dc_bitmap; /* Original bitmap for dc */
 
   GdkW32DragMoveResizeContext drag_move_resize_context;
+
+  /* Decorations set by gdk_window_set_decorations() or NULL if unset */
+  GdkWMDecoration* decorations;
 };
 
 struct _GdkWindowImplWin32Class


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]