Bug 1421974 - use gtk_window_set_titlebar() to hide titlebar on WM where GDK_DECOR_BORDER does not work, r?jhorak draft
authorMartin Stransky <stransky@redhat.com>
Fri, 01 Dec 2017 15:22:48 +0100
changeset 707587 d33ef927659cfa53c7bdd2f7b237eb423a116294
parent 706229 36da4aee5eba3e01e0092efd36dd216070046e30
child 708237 ba922a6baf4570262c742a481539b434719737fd
push id92168
push userstransky@redhat.com
push dateTue, 05 Dec 2017 15:26:27 +0000
reviewersjhorak
bugs1421974, 1419456, 791081
milestone59.0a1
Bug 1421974 - use gtk_window_set_titlebar() to hide titlebar on WM where GDK_DECOR_BORDER does not work, r?jhorak This patch is based on Karl Tomlinson's (:karlt) demo from Bug 1419456. We use gtk_window_set_titlebar() to set invisible widget. The widget takes place of GtkHeaderBar which leads Gtk+ to render CSD shadows and handle window resizing, it does not render any titlebar. gtk_window_set_titlebar() works on unrealized windows only and mShell is already realized at time of nsWindow::SetDrawsInTitlebar() call so we need to update recent GtkWidget setup. In that case we create GdkWindow for mContainer (instead of mShell), create a temporary GtkWindow, reparent mContainer (which owns mGdkWindow) to it, unrealize mShell and set up the titlebar for mShell toplevel window. As a workaround for Gtk+ Bug 791081 we also allocate some valid size for mShell before it's newly realized with the updated titlebar. MozReview-Commit-ID: A3KwRoOzoko
widget/gtk/mozgtk/mozgtk.c
widget/gtk/nsWindow.cpp
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -433,16 +433,18 @@ STUB(gtk_widget_get_accessible)
 STUB(gtk_widget_get_allocation)
 STUB(gtk_widget_get_default_direction)
 STUB(gtk_widget_get_display)
 STUB(gtk_widget_get_events)
 STUB(gtk_widget_get_has_window)
 STUB(gtk_widget_get_mapped)
 STUB(gtk_widget_get_parent)
 STUB(gtk_widget_get_parent_window)
+STUB(gtk_widget_get_preferred_width)
+STUB(gtk_widget_get_preferred_height)
 STUB(gtk_widget_get_realized)
 STUB(gtk_widget_get_screen)
 STUB(gtk_widget_get_settings)
 STUB(gtk_widget_get_style)
 STUB(gtk_widget_get_toplevel)
 STUB(gtk_widget_get_type)
 STUB(gtk_widget_get_visible)
 STUB(gtk_widget_get_visual)
@@ -474,16 +476,17 @@ STUB(gtk_widget_set_realized)
 STUB(gtk_widget_set_redraw_on_allocate)
 STUB(gtk_widget_set_sensitive)
 STUB(gtk_widget_set_window)
 STUB(gtk_widget_show)
 STUB(gtk_widget_show_all)
 STUB(gtk_widget_size_allocate)
 STUB(gtk_widget_style_get)
 STUB(gtk_widget_unparent)
+STUB(gtk_widget_unrealize)
 STUB(gtk_window_deiconify)
 STUB(gtk_window_fullscreen)
 STUB(gtk_window_get_group)
 STUB(gtk_window_get_transient_for)
 STUB(gtk_window_get_type)
 STUB(gtk_window_get_type_hint)
 STUB(gtk_window_get_window_type)
 STUB(gtk_window_group_add_window)
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -3785,22 +3785,23 @@ nsWindow::Create(nsIWidget* aParent,
         // it explicitly now.
         gtk_widget_realize(mShell);
 
         /* There are two cases here:
          *
          * 1) We're running on Gtk+ without client side decorations.
          *    Content is rendered to mShell window and we listen
          *    to the Gtk+ events on mShell
-         * 2) We're running on Gtk+ > 3.20 and client side decorations
+         * 2) We're running on Gtk+ and client side decorations
          *    are drawn by Gtk+ to mShell. Content is rendered to mContainer
          *    and we listen to the Gtk+ events on mContainer.
          */
         GtkStyleContext* style = gtk_widget_get_style_context(mShell);
-        drawToContainer = gtk_style_context_has_class(style, "csd");
+        drawToContainer = mIsCSDAvailable ||
+                          gtk_style_context_has_class(style, "csd");
 #endif
         eventWidget = (drawToContainer) ? container : mShell;
 
         gtk_widget_add_events(eventWidget, kEvents);
 
         // Prevent GtkWindow from painting a background to avoid flickering.
         gtk_widget_set_app_paintable(eventWidget, TRUE);
 
@@ -6645,16 +6646,73 @@ nsWindow::SetDrawsInTitlebar(bool aState
 {
   if (!mIsCSDAvailable || aState == mIsCSDEnabled)
       return;
 
   if (mShell) {
       if (GetCSDSupportLevel() == CSD_SUPPORT_FULL) {
           SetWindowDecoration(aState ? eBorderStyle_border : mBorderStyle);
       }
+      else {
+          /* Window manager does not support GDK_DECOR_BORDER,
+           * emulate it by CSD.
+           *
+           * gtk_window_set_titlebar() works on unrealized widgets only,
+           * we need to handle mShell carefully here.
+           * When CSD is enabled mGdkWindow is owned by mContainer which is good
+           * as we can't delete our mGdkWindow. To make mShell unrealized while
+           * mContainer is preserved we temporary reparent mContainer to an
+           * invisible GtkWindow.
+           */
+          NativeShow(false);
+
+          // Using GTK_WINDOW_POPUP rather than
+          // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
+          // initialization and window manager interaction.
+          GtkWidget* tmpWindow = gtk_window_new(GTK_WINDOW_POPUP);
+          gtk_widget_realize(tmpWindow);
+
+          gtk_widget_reparent(GTK_WIDGET(mContainer), tmpWindow);
+          gtk_widget_unrealize(GTK_WIDGET(mShell));
+
+          // Available as of GTK 3.10+
+          static auto sGtkWindowSetTitlebar = (void (*)(GtkWindow*, GtkWidget*))
+              dlsym(RTLD_DEFAULT, "gtk_window_set_titlebar");
+          MOZ_ASSERT(sGtkWindowSetTitlebar,
+              "Missing gtk_window_set_titlebar(), old Gtk+ library?");
+
+          if (aState) {
+              // Add a hidden titlebar widget to trigger CSD, but disable the default
+              // titlebar.  GtkFixed is a somewhat random choice for a simple unused
+              // widget. gtk_window_set_titlebar() takes ownership of the titlebar
+              // widget.
+              sGtkWindowSetTitlebar(GTK_WINDOW(mShell), gtk_fixed_new());
+          } else {
+              sGtkWindowSetTitlebar(GTK_WINDOW(mShell), nullptr);
+          }
+
+          /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=791081
+           * gtk_widget_realize() throws:
+           * "In pixman_region32_init_rect: Invalid rectangle passed"
+           * when mShell has default 1x1 size.
+           */
+          GtkAllocation allocation = {0, 0, 0, 0};
+          gtk_widget_get_preferred_width(GTK_WIDGET(mShell), nullptr,
+                                         &allocation.width);
+          gtk_widget_get_preferred_height(GTK_WIDGET(mShell), nullptr,
+                                          &allocation.height);
+          gtk_widget_size_allocate(GTK_WIDGET(mShell), &allocation);
+
+          gtk_widget_realize(GTK_WIDGET(mShell));
+          gtk_widget_reparent(GTK_WIDGET(mContainer), GTK_WIDGET(mShell));
+          mNeedsShow = true;
+          NativeResize();
+
+          gtk_widget_destroy(tmpWindow);
+      }
   }
 
   mIsCSDEnabled = aState;
 }
 
 gint
 nsWindow::GdkScaleFactor()
 {