Bug 1350643 - Part 2: Use gdk functions to enumerate monitors instead of Xinerama. r?karlt draft
authorSamael Wang <freesamael@gmail.com>
Tue, 06 Jun 2017 17:57:54 +0800
changeset 611030 c6cc7dc6199206f5649eff5493c17ea75b6ec34b
parent 611029 b49aeda5bea9cfa6b90456e517bd702ce331ce7b
child 611031 ef9b6bc092765b1b5353673698ed6e9adfbd6052
push id69104
push userbmo:sawang@mozilla.com
push dateWed, 19 Jul 2017 06:01:44 +0000
reviewerskarlt
bugs1350643
milestone56.0a1
Bug 1350643 - Part 2: Use gdk functions to enumerate monitors instead of Xinerama. r?karlt MozReview-Commit-ID: D9f65oZMBuV
widget/gtk/ScreenHelperGTK.cpp
widget/gtk/ScreenHelperGTK.h
widget/gtk/mozgtk/mozgtk.c
--- a/widget/gtk/ScreenHelperGTK.cpp
+++ b/widget/gtk/ScreenHelperGTK.cpp
@@ -2,40 +2,26 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ScreenHelperGTK.h"
 
 #ifdef MOZ_X11
-#include <X11/Xatom.h>
 #include <gdk/gdkx.h>
-// from Xinerama.h
-typedef struct {
-   int   screen_number;
-   short x_org;
-   short y_org;
-   short width;
-   short height;
-} XineramaScreenInfo;
-// prototypes from Xinerama.h
-typedef Bool (*_XnrmIsActive_fn)(Display *dpy);
-typedef XineramaScreenInfo* (*_XnrmQueryScreens_fn)(Display *dpy, int *number);
 #endif
 #include <dlfcn.h>
 #include <gtk/gtk.h>
 
 #include "gfxPlatformGtk.h"
 #include "mozilla/Logging.h"
 #include "nsGtkUtils.h"
 #include "nsTArray.h"
 
-#define SCREEN_MANAGER_LIBRARY_LOAD_FAILED ((PRLibrary*)1)
-
 namespace mozilla {
 namespace widget {
 
 static LazyLogModule sScreenLog("WidgetScreen");
 
 static void
 monitors_changed(GdkScreen* aScreen, gpointer aClosure)
 {
@@ -66,25 +52,26 @@ root_window_event_filter(GdkXEvent* aGdk
       break;
   }
 #endif
 
   return GDK_FILTER_CONTINUE;
 }
 
 ScreenHelperGTK::ScreenHelperGTK()
-  : mXineramalib(nullptr)
-  , mRootWindow(nullptr)
+  : mRootWindow(nullptr)
+#ifdef MOZ_X11
   , mNetWorkareaAtom(0)
+#endif
 {
   MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperGTK created"));
   GdkScreen *defaultScreen = gdk_screen_get_default();
   if (!defaultScreen) {
     // Sometimes we don't initial X (e.g., xpcshell)
-    MOZ_LOG(sScreenLog, LogLevel::Debug, ("mRootWindow is nullptr, running headless"));
+    MOZ_LOG(sScreenLog, LogLevel::Debug, ("defaultScreen is nullptr, running headless"));
     return;
   }
   mRootWindow = gdk_get_default_root_window();
   MOZ_ASSERT(mRootWindow);
 
   g_object_ref(mRootWindow);
 
   // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
@@ -110,221 +97,89 @@ ScreenHelperGTK::~ScreenHelperGTK()
     g_signal_handlers_disconnect_by_func(gdk_screen_get_default(),
                                          FuncToGpointer(monitors_changed),
                                          this);
 
     gdk_window_remove_filter(mRootWindow, root_window_event_filter, this);
     g_object_unref(mRootWindow);
     mRootWindow = nullptr;
   }
-
-  /* XineramaIsActive() registers a callback function close_display()
-   * in X, which is to be called in XCloseDisplay(). This is the case
-   * if Xinerama is active, even if only with one screen.
-   *
-   * We can't unload libXinerama.so.1 here because this will make
-   * the address of close_display() registered in X to be invalid and
-   * it will crash when XCloseDisplay() is called later. */
 }
 
 gint
-ScreenHelperGTK::GetGTKMonitorScaleFactor()
+ScreenHelperGTK::GetGTKMonitorScaleFactor(gint aMonitorNum)
 {
 #if (MOZ_WIDGET_GTK >= 3)
   // Since GDK 3.10
   static auto sGdkScreenGetMonitorScaleFactorPtr = (gint (*)(GdkScreen*, gint))
     dlsym(RTLD_DEFAULT, "gdk_screen_get_monitor_scale_factor");
   if (sGdkScreenGetMonitorScaleFactorPtr) {
-    // FIXME: In the future, we'll want to fix this for GTK on Wayland which
-    // supports a variable scale factor per display.
     GdkScreen *screen = gdk_screen_get_default();
-    return sGdkScreenGetMonitorScaleFactorPtr(screen, 0);
+    return sGdkScreenGetMonitorScaleFactorPtr(screen, aMonitorNum);
   }
 #endif
   return 1;
 }
 
-static float
-GetDefaultCssScale()
-{
-  return ScreenHelperGTK::GetGTKMonitorScaleFactor() * gfxPlatformGtk::GetFontScaleFactor();
-}
-
 static uint32_t
 GetGTKPixelDepth()
 {
   GdkVisual * visual = gdk_screen_get_system_visual(gdk_screen_get_default());
   return gdk_visual_get_depth(visual);
 }
 
 static already_AddRefed<Screen>
-MakeScreen(GdkWindow* aRootWindow)
+MakeScreen(GdkScreen* aScreen, gint aMonitorNum)
 {
-  RefPtr<Screen> screen;
+  GdkRectangle monitor;
+  GdkRectangle workarea;
+  gdk_screen_get_monitor_geometry(aScreen, aMonitorNum, &monitor);
+  gdk_screen_get_monitor_workarea(aScreen, aMonitorNum, &workarea);
+  gint gdkScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor(aMonitorNum);
 
-  gint scale = ScreenHelperGTK::GetGTKMonitorScaleFactor();
-  gint width = gdk_screen_width() * scale;
-  gint height = gdk_screen_height() * scale;
+  // gdk_screen_get_monitor_geometry / workarea returns application pixels
+  // (desktop pixels), so we need to convert it to device pixels with
+  // gdkScaleFactor.
+  LayoutDeviceIntRect rect(monitor.x * gdkScaleFactor,
+                           monitor.y * gdkScaleFactor,
+                           monitor.width * gdkScaleFactor,
+                           monitor.height * gdkScaleFactor);
+  LayoutDeviceIntRect availRect(workarea.x * gdkScaleFactor,
+                                workarea.y * gdkScaleFactor,
+                                workarea.width * gdkScaleFactor,
+                                workarea.height * gdkScaleFactor);
   uint32_t pixelDepth = GetGTKPixelDepth();
   DesktopToLayoutDeviceScale contentsScale(1.0);
-  CSSToLayoutDeviceScale defaultCssScale(GetDefaultCssScale());
-
-  LayoutDeviceIntRect rect;
-  LayoutDeviceIntRect availRect;
-  rect = availRect = LayoutDeviceIntRect(0, 0, width, height);
-
-#ifdef MOZ_X11
-  // We need to account for the taskbar, etc in the available rect.
-  // See http://freedesktop.org/Standards/wm-spec/index.html#id2767771
-
-  // XXX do we care about _NET_WM_STRUT_PARTIAL?  That will
-  // add much more complexity to the code here (our screen
-  // could have a non-rectangular shape), but should
-  // lead to greater accuracy.
-
-  long *workareas;
-  GdkAtom type_returned;
-  int format_returned;
-  int length_returned;
-
-  GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
-
-  gdk_error_trap_push();
-
-  // gdk_property_get uses (length + 3) / 4, hence G_MAXLONG - 3 here.
-  if (!gdk_property_get(aRootWindow,
-                        gdk_atom_intern ("_NET_WORKAREA", FALSE),
-                        cardinal_atom,
-                        0, G_MAXLONG - 3, FALSE,
-                        &type_returned,
-                        &format_returned,
-                        &length_returned,
-                        (guchar **) &workareas)) {
-    // This window manager doesn't support the freedesktop standard.
-    // Nothing we can do about it, so assume full screen size.
-    MOZ_LOG(sScreenLog, LogLevel::Debug, ("New screen [%d %d %d %d %d %f]",
-                                          rect.x, rect.y, rect.width, rect.height,
-                                          pixelDepth, defaultCssScale.scale));
-    screen = new Screen(rect, availRect,
-                        pixelDepth, pixelDepth,
-                        contentsScale, defaultCssScale);
-    return screen.forget();
-  }
-
-  // Flush the X queue to catch errors now.
-  gdk_flush();
+  CSSToLayoutDeviceScale defaultCssScale(
+    gdkScaleFactor * gfxPlatformGtk::GetFontScaleFactor());
 
-  if (!gdk_error_trap_pop() &&
-      type_returned == cardinal_atom &&
-      length_returned && (length_returned % 4) == 0 &&
-      format_returned == 32) {
-    int num_items = length_returned / sizeof(long);
-
-    for (int i = 0; i < num_items; i += 4) {
-      LayoutDeviceIntRect workarea(workareas[i],     workareas[i + 1],
-                                   workareas[i + 2], workareas[i + 3]);
-      if (!rect.Contains(workarea)) {
-        // Note that we hit this when processing screen size changes,
-        // since we'll get the configure event before the toolbars have
-        // been moved.  We'll end up cleaning this up when we get the
-        // change notification to the _NET_WORKAREA property.  However,
-        // we still want to listen to both, so we'll handle changes
-        // properly for desktop environments that don't set the
-        // _NET_WORKAREA property.
-        NS_WARNING("Invalid bounds");
-        continue;
-      }
-
-      availRect.IntersectRect(availRect, workarea);
-    }
-  }
-  g_free(workareas);
-#endif
-  MOZ_LOG(sScreenLog, LogLevel::Debug, ("New screen [%d %d %d %d %d %f]",
-                                        rect.x, rect.y, rect.width, rect.height,
-                                        pixelDepth, defaultCssScale.scale));
-  screen = new Screen(rect, availRect,
-                      pixelDepth, pixelDepth,
-                      contentsScale, defaultCssScale);
-  return screen.forget();
-}
-
-static already_AddRefed<Screen>
-MakeScreen(const XineramaScreenInfo& aScreenInfo)
-{
-  LayoutDeviceIntRect xineRect(aScreenInfo.x_org, aScreenInfo.y_org,
-                               aScreenInfo.width, aScreenInfo.height);
-  uint32_t pixelDepth = GetGTKPixelDepth();
-  DesktopToLayoutDeviceScale contentsScale(1.0);
-  CSSToLayoutDeviceScale defaultCssScale(GetDefaultCssScale());
-
-  MOZ_LOG(sScreenLog, LogLevel::Debug, ("New screen [%d %d %d %d %d %f]",
-                                        xineRect.x, xineRect.y,
-                                        xineRect.width, xineRect.height,
-                                        pixelDepth, defaultCssScale.scale));
-  RefPtr<Screen> screen = new Screen(xineRect, xineRect,
+  MOZ_LOG(sScreenLog, LogLevel::Debug,
+           ("New screen [%d %d %d %d (%d %d %d %d) %d %f]",
+            rect.x, rect.y, rect.width, rect.height,
+            availRect.x, availRect.y, availRect.width, availRect.height,
+            pixelDepth, defaultCssScale.scale));
+  RefPtr<Screen> screen = new Screen(rect, availRect,
                                      pixelDepth, pixelDepth,
                                      contentsScale, defaultCssScale);
   return screen.forget();
 }
 
 void
 ScreenHelperGTK::RefreshScreens()
 {
   MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
   AutoTArray<RefPtr<Screen>, 4> screenList;
-#ifdef MOZ_X11
-  XineramaScreenInfo *screenInfo = nullptr;
-  int numScreens;
-
-  bool useXinerama = GDK_IS_X11_DISPLAY(gdk_display_get_default());
 
-  if (useXinerama && !mXineramalib) {
-    mXineramalib = PR_LoadLibrary("libXinerama.so.1");
-    if (!mXineramalib) {
-      mXineramalib = SCREEN_MANAGER_LIBRARY_LOAD_FAILED;
-    }
-  }
-  if (mXineramalib && mXineramalib != SCREEN_MANAGER_LIBRARY_LOAD_FAILED) {
-    _XnrmIsActive_fn _XnrmIsActive = (_XnrmIsActive_fn)
-        PR_FindFunctionSymbol(mXineramalib, "XineramaIsActive");
+  GdkScreen *defaultScreen = gdk_screen_get_default();
+  gint numScreens = gdk_screen_get_n_monitors(defaultScreen);
+  MOZ_LOG(sScreenLog, LogLevel::Debug,
+          ("GDK reports %d screens", numScreens));
 
-    _XnrmQueryScreens_fn _XnrmQueryScreens = (_XnrmQueryScreens_fn)
-        PR_FindFunctionSymbol(mXineramalib, "XineramaQueryScreens");
-
-    // get the number of screens via xinerama
-    Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-    if (_XnrmIsActive && _XnrmQueryScreens && _XnrmIsActive(display)) {
-      screenInfo = _XnrmQueryScreens(display, &numScreens);
-    }
+  for (gint i = 0; i < numScreens; i++) {
+    screenList.AppendElement(MakeScreen(defaultScreen, i));
   }
 
-  // screenInfo == nullptr if either Xinerama couldn't be loaded or
-  // isn't running on the current display
-  if (!screenInfo || numScreens == 1) {
-    numScreens = 1;
-#endif
-    MOZ_LOG(sScreenLog, LogLevel::Debug, ("Find only one screen available"));
-    // Get primary screen
-    screenList.AppendElement(MakeScreen(mRootWindow));
-#ifdef MOZ_X11
-  }
-  // If Xinerama is enabled and there's more than one screen, fill
-  // in the info for all of the screens.  If that's not the case
-  // then defaults to the screen width + height
-  else {
-    MOZ_LOG(sScreenLog, LogLevel::Debug,
-            ("Xinerama enabled for %d screens", numScreens));
-    for (int i = 0; i < numScreens; ++i) {
-      screenList.AppendElement(MakeScreen(screenInfo[i]));
-    }
-  }
-
-  if (screenInfo) {
-    XFree(screenInfo);
-  }
-#endif
   ScreenManager& screenManager = ScreenManager::GetSingleton();
   screenManager.Refresh(Move(screenList));
 }
 
 } // namespace widget
 } // namespace mozilla
--- a/widget/gtk/ScreenHelperGTK.h
+++ b/widget/gtk/ScreenHelperGTK.h
@@ -4,42 +4,40 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_widget_gtk_ScreenHelperGTK_h
 #define mozilla_widget_gtk_ScreenHelperGTK_h
 
 #include "mozilla/widget/ScreenManager.h"
 
-#include "prlink.h"
 #include "gdk/gdk.h"
 #ifdef MOZ_X11
 #include <X11/Xlib.h>
 #endif
 
 namespace mozilla {
 namespace widget {
 
 class ScreenHelperGTK final : public ScreenManager::Helper
 {
 public:
   ScreenHelperGTK();
   ~ScreenHelperGTK() override;
 
-  static gint GetGTKMonitorScaleFactor();
+  static gint GetGTKMonitorScaleFactor(gint aMonitorNum = 0);
 
 #ifdef MOZ_X11
   Atom NetWorkareaAtom() { return mNetWorkareaAtom; }
 #endif
 
   // For internal use from signal callback functions
   void RefreshScreens();
 
 private:
-  PRLibrary* mXineramalib;
   GdkWindow* mRootWindow;
 #ifdef MOZ_X11
   Atom mNetWorkareaAtom;
 #endif
 };
 
 } // namespace widget
 } // namespace mozilla
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -53,18 +53,21 @@ STUB(gdk_pango_context_get)
 STUB(gdk_pointer_grab)
 STUB(gdk_pointer_ungrab)
 STUB(gdk_property_get)
 STUB(gdk_screen_get_default)
 STUB(gdk_screen_get_display)
 STUB(gdk_screen_get_font_options)
 STUB(gdk_screen_get_height)
 STUB(gdk_screen_get_height_mm)
+STUB(gdk_screen_get_n_monitors)
 STUB(gdk_screen_get_monitor_at_window)
 STUB(gdk_screen_get_monitor_geometry)
+STUB(gdk_screen_get_monitor_workarea)
+STUB(gdk_screen_get_monitor_height_mm)
 STUB(gdk_screen_get_number)
 STUB(gdk_screen_get_resolution)
 STUB(gdk_screen_get_rgba_visual)
 STUB(gdk_screen_get_root_window)
 STUB(gdk_screen_get_system_visual)
 STUB(gdk_screen_get_width)
 STUB(gdk_screen_height)
 STUB(gdk_screen_is_composited)