LCOV - code coverage report
Current view: top level - widget/gtk - ScreenHelperGTK.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 88 125 70.4 %
Date: 2017-07-14 16:53:18 Functions: 7 11 63.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "ScreenHelperGTK.h"
       8             : 
       9             : #ifdef MOZ_X11
      10             : #include <X11/Xatom.h>
      11             : #include <gdk/gdkx.h>
      12             : // from Xinerama.h
      13             : typedef struct {
      14             :    int   screen_number;
      15             :    short x_org;
      16             :    short y_org;
      17             :    short width;
      18             :    short height;
      19             : } XineramaScreenInfo;
      20             : // prototypes from Xinerama.h
      21             : typedef Bool (*_XnrmIsActive_fn)(Display *dpy);
      22             : typedef XineramaScreenInfo* (*_XnrmQueryScreens_fn)(Display *dpy, int *number);
      23             : #endif
      24             : #include <dlfcn.h>
      25             : #include <gtk/gtk.h>
      26             : 
      27             : #include "gfxPlatformGtk.h"
      28             : #include "mozilla/Logging.h"
      29             : #include "nsGtkUtils.h"
      30             : #include "nsTArray.h"
      31             : 
      32             : #define SCREEN_MANAGER_LIBRARY_LOAD_FAILED ((PRLibrary*)1)
      33             : 
      34             : namespace mozilla {
      35             : namespace widget {
      36             : 
      37             : static LazyLogModule sScreenLog("WidgetScreen");
      38             : 
      39             : static void
      40           0 : monitors_changed(GdkScreen* aScreen, gpointer aClosure)
      41             : {
      42           0 :   MOZ_LOG(sScreenLog, LogLevel::Debug, ("Received monitors-changed event"));
      43           0 :   ScreenHelperGTK* self = static_cast<ScreenHelperGTK*>(aClosure);
      44           0 :   self->RefreshScreens();
      45           0 : }
      46             : 
      47             : static GdkFilterReturn
      48           3 : root_window_event_filter(GdkXEvent* aGdkXEvent, GdkEvent* aGdkEvent,
      49             :                          gpointer aClosure)
      50             : {
      51             : #ifdef MOZ_X11
      52           3 :   ScreenHelperGTK* self = static_cast<ScreenHelperGTK*>(aClosure);
      53           3 :   XEvent *xevent = static_cast<XEvent*>(aGdkXEvent);
      54             : 
      55           3 :   switch (xevent->type) {
      56             :     case PropertyNotify:
      57             :       {
      58           3 :         XPropertyEvent *propertyEvent = &xevent->xproperty;
      59           3 :         if (propertyEvent->atom == self->NetWorkareaAtom()) {
      60           0 :           MOZ_LOG(sScreenLog, LogLevel::Debug, ("Work area size changed"));
      61           0 :           self->RefreshScreens();
      62             :         }
      63             :       }
      64           3 :       break;
      65             :     default:
      66           0 :       break;
      67             :   }
      68             : #endif
      69             : 
      70           3 :   return GDK_FILTER_CONTINUE;
      71             : }
      72             : 
      73           1 : ScreenHelperGTK::ScreenHelperGTK()
      74             :   : mXineramalib(nullptr)
      75             :   , mRootWindow(nullptr)
      76           1 :   , mNetWorkareaAtom(0)
      77             : {
      78           1 :   MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperGTK created"));
      79           1 :   GdkScreen *defaultScreen = gdk_screen_get_default();
      80           1 :   if (!defaultScreen) {
      81             :     // Sometimes we don't initial X (e.g., xpcshell)
      82           0 :     MOZ_LOG(sScreenLog, LogLevel::Debug, ("mRootWindow is nullptr, running headless"));
      83           0 :     return;
      84             :   }
      85           1 :   mRootWindow = gdk_get_default_root_window();
      86           1 :   MOZ_ASSERT(mRootWindow);
      87             : 
      88           1 :   g_object_ref(mRootWindow);
      89             : 
      90             :   // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
      91           1 :   gdk_window_set_events(mRootWindow,
      92           1 :                         GdkEventMask(gdk_window_get_events(mRootWindow) |
      93           1 :                                      GDK_PROPERTY_CHANGE_MASK));
      94             : 
      95             :   g_signal_connect(defaultScreen, "monitors-changed",
      96           1 :                    G_CALLBACK(monitors_changed), this);
      97             : #ifdef MOZ_X11
      98           1 :   gdk_window_add_filter(mRootWindow, root_window_event_filter, this);
      99           1 :   if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
     100           1 :     mNetWorkareaAtom =
     101           1 :       XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow), "_NET_WORKAREA", False);
     102             :   }
     103             : #endif
     104           1 :   RefreshScreens();
     105             : }
     106             : 
     107           0 : ScreenHelperGTK::~ScreenHelperGTK()
     108             : {
     109           0 :   if (mRootWindow) {
     110           0 :     g_signal_handlers_disconnect_by_func(gdk_screen_get_default(),
     111             :                                          FuncToGpointer(monitors_changed),
     112           0 :                                          this);
     113             : 
     114           0 :     gdk_window_remove_filter(mRootWindow, root_window_event_filter, this);
     115           0 :     g_object_unref(mRootWindow);
     116           0 :     mRootWindow = nullptr;
     117             :   }
     118             : 
     119             :   /* XineramaIsActive() registers a callback function close_display()
     120             :    * in X, which is to be called in XCloseDisplay(). This is the case
     121             :    * if Xinerama is active, even if only with one screen.
     122             :    *
     123             :    * We can't unload libXinerama.so.1 here because this will make
     124             :    * the address of close_display() registered in X to be invalid and
     125             :    * it will crash when XCloseDisplay() is called later. */
     126           0 : }
     127             : 
     128             : gint
     129         502 : ScreenHelperGTK::GetGTKMonitorScaleFactor()
     130             : {
     131             : #if (MOZ_WIDGET_GTK >= 3)
     132             :   // Since GDK 3.10
     133           2 :   static auto sGdkScreenGetMonitorScaleFactorPtr = (gint (*)(GdkScreen*, gint))
     134         504 :     dlsym(RTLD_DEFAULT, "gdk_screen_get_monitor_scale_factor");
     135         502 :   if (sGdkScreenGetMonitorScaleFactorPtr) {
     136             :     // FIXME: In the future, we'll want to fix this for GTK on Wayland which
     137             :     // supports a variable scale factor per display.
     138         502 :     GdkScreen *screen = gdk_screen_get_default();
     139         502 :     return sGdkScreenGetMonitorScaleFactorPtr(screen, 0);
     140             :   }
     141             : #endif
     142           0 :   return 1;
     143             : }
     144             : 
     145             : static float
     146           1 : GetDefaultCssScale()
     147             : {
     148           1 :   return ScreenHelperGTK::GetGTKMonitorScaleFactor() * gfxPlatformGtk::GetDPIScale();
     149             : }
     150             : 
     151             : static uint32_t
     152           1 : GetGTKPixelDepth()
     153             : {
     154           1 :   GdkVisual * visual = gdk_screen_get_system_visual(gdk_screen_get_default());
     155           1 :   return gdk_visual_get_depth(visual);
     156             : }
     157             : 
     158             : static already_AddRefed<Screen>
     159           1 : MakeScreen(GdkWindow* aRootWindow)
     160             : {
     161           2 :   RefPtr<Screen> screen;
     162             : 
     163           1 :   gint scale = ScreenHelperGTK::GetGTKMonitorScaleFactor();
     164           1 :   gint width = gdk_screen_width() * scale;
     165           1 :   gint height = gdk_screen_height() * scale;
     166           1 :   uint32_t pixelDepth = GetGTKPixelDepth();
     167           1 :   DesktopToLayoutDeviceScale contentsScale(1.0);
     168           1 :   CSSToLayoutDeviceScale defaultCssScale(GetDefaultCssScale());
     169             : 
     170           1 :   LayoutDeviceIntRect rect;
     171           1 :   LayoutDeviceIntRect availRect;
     172           1 :   rect = availRect = LayoutDeviceIntRect(0, 0, width, height);
     173             : 
     174             : #ifdef MOZ_X11
     175             :   // We need to account for the taskbar, etc in the available rect.
     176             :   // See http://freedesktop.org/Standards/wm-spec/index.html#id2767771
     177             : 
     178             :   // XXX do we care about _NET_WM_STRUT_PARTIAL?  That will
     179             :   // add much more complexity to the code here (our screen
     180             :   // could have a non-rectangular shape), but should
     181             :   // lead to greater accuracy.
     182             : 
     183             :   long *workareas;
     184             :   GdkAtom type_returned;
     185             :   int format_returned;
     186             :   int length_returned;
     187             : 
     188           1 :   GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
     189             : 
     190           1 :   gdk_error_trap_push();
     191             : 
     192             :   // gdk_property_get uses (length + 3) / 4, hence G_MAXLONG - 3 here.
     193           1 :   if (!gdk_property_get(aRootWindow,
     194             :                         gdk_atom_intern ("_NET_WORKAREA", FALSE),
     195             :                         cardinal_atom,
     196             :                         0, G_MAXLONG - 3, FALSE,
     197             :                         &type_returned,
     198             :                         &format_returned,
     199             :                         &length_returned,
     200             :                         (guchar **) &workareas)) {
     201             :     // This window manager doesn't support the freedesktop standard.
     202             :     // Nothing we can do about it, so assume full screen size.
     203           0 :     MOZ_LOG(sScreenLog, LogLevel::Debug, ("New screen [%d %d %d %d %d %f]",
     204             :                                           rect.x, rect.y, rect.width, rect.height,
     205             :                                           pixelDepth, defaultCssScale.scale));
     206             :     screen = new Screen(rect, availRect,
     207             :                         pixelDepth, pixelDepth,
     208           0 :                         contentsScale, defaultCssScale);
     209           0 :     return screen.forget();
     210             :   }
     211             : 
     212             :   // Flush the X queue to catch errors now.
     213           1 :   gdk_flush();
     214             : 
     215           3 :   if (!gdk_error_trap_pop() &&
     216           2 :       type_returned == cardinal_atom &&
     217           4 :       length_returned && (length_returned % 4) == 0 &&
     218           1 :       format_returned == 32) {
     219           1 :     int num_items = length_returned / sizeof(long);
     220             : 
     221           2 :     for (int i = 0; i < num_items; i += 4) {
     222           2 :       LayoutDeviceIntRect workarea(workareas[i],     workareas[i + 1],
     223           3 :                                    workareas[i + 2], workareas[i + 3]);
     224           1 :       if (!rect.Contains(workarea)) {
     225             :         // Note that we hit this when processing screen size changes,
     226             :         // since we'll get the configure event before the toolbars have
     227             :         // been moved.  We'll end up cleaning this up when we get the
     228             :         // change notification to the _NET_WORKAREA property.  However,
     229             :         // we still want to listen to both, so we'll handle changes
     230             :         // properly for desktop environments that don't set the
     231             :         // _NET_WORKAREA property.
     232           0 :         NS_WARNING("Invalid bounds");
     233           0 :         continue;
     234             :       }
     235             : 
     236           1 :       availRect.IntersectRect(availRect, workarea);
     237             :     }
     238             :   }
     239           1 :   g_free(workareas);
     240             : #endif
     241           1 :   MOZ_LOG(sScreenLog, LogLevel::Debug, ("New screen [%d %d %d %d %d %f]",
     242             :                                         rect.x, rect.y, rect.width, rect.height,
     243             :                                         pixelDepth, defaultCssScale.scale));
     244             :   screen = new Screen(rect, availRect,
     245             :                       pixelDepth, pixelDepth,
     246           2 :                       contentsScale, defaultCssScale);
     247           1 :   return screen.forget();
     248             : }
     249             : 
     250             : static already_AddRefed<Screen>
     251           0 : MakeScreen(const XineramaScreenInfo& aScreenInfo)
     252             : {
     253           0 :   LayoutDeviceIntRect xineRect(aScreenInfo.x_org, aScreenInfo.y_org,
     254           0 :                                aScreenInfo.width, aScreenInfo.height);
     255           0 :   uint32_t pixelDepth = GetGTKPixelDepth();
     256           0 :   DesktopToLayoutDeviceScale contentsScale(1.0);
     257           0 :   CSSToLayoutDeviceScale defaultCssScale(GetDefaultCssScale());
     258             : 
     259           0 :   MOZ_LOG(sScreenLog, LogLevel::Debug, ("New screen [%d %d %d %d %d %f]",
     260             :                                         xineRect.x, xineRect.y,
     261             :                                         xineRect.width, xineRect.height,
     262             :                                         pixelDepth, defaultCssScale.scale));
     263             :   RefPtr<Screen> screen = new Screen(xineRect, xineRect,
     264             :                                      pixelDepth, pixelDepth,
     265           0 :                                      contentsScale, defaultCssScale);
     266           0 :   return screen.forget();
     267             : }
     268             : 
     269             : void
     270           1 : ScreenHelperGTK::RefreshScreens()
     271             : {
     272           1 :   MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
     273           2 :   AutoTArray<RefPtr<Screen>, 4> screenList;
     274             : #ifdef MOZ_X11
     275           1 :   XineramaScreenInfo *screenInfo = nullptr;
     276             :   int numScreens;
     277             : 
     278           1 :   bool useXinerama = GDK_IS_X11_DISPLAY(gdk_display_get_default());
     279             : 
     280           1 :   if (useXinerama && !mXineramalib) {
     281           1 :     mXineramalib = PR_LoadLibrary("libXinerama.so.1");
     282           1 :     if (!mXineramalib) {
     283           0 :       mXineramalib = SCREEN_MANAGER_LIBRARY_LOAD_FAILED;
     284             :     }
     285             :   }
     286           1 :   if (mXineramalib && mXineramalib != SCREEN_MANAGER_LIBRARY_LOAD_FAILED) {
     287             :     _XnrmIsActive_fn _XnrmIsActive = (_XnrmIsActive_fn)
     288           1 :         PR_FindFunctionSymbol(mXineramalib, "XineramaIsActive");
     289             : 
     290             :     _XnrmQueryScreens_fn _XnrmQueryScreens = (_XnrmQueryScreens_fn)
     291           1 :         PR_FindFunctionSymbol(mXineramalib, "XineramaQueryScreens");
     292             : 
     293             :     // get the number of screens via xinerama
     294           1 :     Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
     295           1 :     if (_XnrmIsActive && _XnrmQueryScreens && _XnrmIsActive(display)) {
     296           1 :       screenInfo = _XnrmQueryScreens(display, &numScreens);
     297             :     }
     298             :   }
     299             : 
     300             :   // screenInfo == nullptr if either Xinerama couldn't be loaded or
     301             :   // isn't running on the current display
     302           1 :   if (!screenInfo || numScreens == 1) {
     303           1 :     numScreens = 1;
     304             : #endif
     305           1 :     MOZ_LOG(sScreenLog, LogLevel::Debug, ("Find only one screen available"));
     306             :     // Get primary screen
     307           1 :     screenList.AppendElement(MakeScreen(mRootWindow));
     308             : #ifdef MOZ_X11
     309             :   }
     310             :   // If Xinerama is enabled and there's more than one screen, fill
     311             :   // in the info for all of the screens.  If that's not the case
     312             :   // then defaults to the screen width + height
     313             :   else {
     314           0 :     MOZ_LOG(sScreenLog, LogLevel::Debug,
     315             :             ("Xinerama enabled for %d screens", numScreens));
     316           0 :     for (int i = 0; i < numScreens; ++i) {
     317           0 :       screenList.AppendElement(MakeScreen(screenInfo[i]));
     318             :     }
     319             :   }
     320             : 
     321           1 :   if (screenInfo) {
     322           1 :     XFree(screenInfo);
     323             :   }
     324             : #endif
     325           1 :   ScreenManager& screenManager = ScreenManager::GetSingleton();
     326           1 :   screenManager.Refresh(Move(screenList));
     327           1 : }
     328             : 
     329             : } // namespace widget
     330             : } // namespace mozilla

Generated by: LCOV version 1.13