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 <gdk/gdkx.h>
11 #endif /* MOZ_X11 */
12 #ifdef MOZ_WAYLAND
13 #include <gdk/gdkwayland.h>
14 #endif /* MOZ_WAYLAND */
15 #include <dlfcn.h>
16 #include <gtk/gtk.h>
17 
18 #include "gfxPlatformGtk.h"
19 #include "mozilla/Logging.h"
20 #include "nsGtkUtils.h"
21 #include "nsTArray.h"
22 
23 namespace mozilla {
24 namespace widget {
25 
26 static LazyLogModule sScreenLog("WidgetScreen");
27 
monitors_changed(GdkScreen * aScreen,gpointer aClosure)28 static void monitors_changed(GdkScreen* aScreen, gpointer aClosure) {
29   MOZ_LOG(sScreenLog, LogLevel::Debug, ("Received monitors-changed event"));
30   ScreenHelperGTK* self = static_cast<ScreenHelperGTK*>(aClosure);
31   self->RefreshScreens();
32 }
33 
root_window_event_filter(GdkXEvent * aGdkXEvent,GdkEvent * aGdkEvent,gpointer aClosure)34 static GdkFilterReturn root_window_event_filter(GdkXEvent* aGdkXEvent,
35                                                 GdkEvent* aGdkEvent,
36                                                 gpointer aClosure) {
37 #ifdef MOZ_X11
38   ScreenHelperGTK* self = static_cast<ScreenHelperGTK*>(aClosure);
39   XEvent* xevent = static_cast<XEvent*>(aGdkXEvent);
40 
41   switch (xevent->type) {
42     case PropertyNotify: {
43       XPropertyEvent* propertyEvent = &xevent->xproperty;
44       if (propertyEvent->atom == self->NetWorkareaAtom()) {
45         MOZ_LOG(sScreenLog, LogLevel::Debug, ("Work area size changed"));
46         self->RefreshScreens();
47       }
48     } break;
49     default:
50       break;
51   }
52 #endif
53 
54   return GDK_FILTER_CONTINUE;
55 }
56 
ScreenHelperGTK()57 ScreenHelperGTK::ScreenHelperGTK()
58     : mRootWindow(nullptr)
59 #ifdef MOZ_X11
60       ,
61       mNetWorkareaAtom(0)
62 #endif
63 {
64   MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperGTK created"));
65   GdkScreen* defaultScreen = gdk_screen_get_default();
66   if (!defaultScreen) {
67     // Sometimes we don't initial X (e.g., xpcshell)
68     MOZ_LOG(sScreenLog, LogLevel::Debug,
69             ("defaultScreen is nullptr, running headless"));
70     return;
71   }
72   mRootWindow = gdk_get_default_root_window();
73   MOZ_ASSERT(mRootWindow);
74 
75   g_object_ref(mRootWindow);
76 
77   // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
78   gdk_window_set_events(mRootWindow,
79                         GdkEventMask(gdk_window_get_events(mRootWindow) |
80                                      GDK_PROPERTY_CHANGE_MASK));
81 
82   g_signal_connect(defaultScreen, "monitors-changed",
83                    G_CALLBACK(monitors_changed), this);
84 #ifdef MOZ_X11
85   gdk_window_add_filter(mRootWindow, root_window_event_filter, this);
86   if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
87     mNetWorkareaAtom =
88         XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow), "_NET_WORKAREA", False);
89   }
90 #endif
91   RefreshScreens();
92 }
93 
~ScreenHelperGTK()94 ScreenHelperGTK::~ScreenHelperGTK() {
95   if (mRootWindow) {
96     g_signal_handlers_disconnect_by_func(
97         gdk_screen_get_default(), FuncToGpointer(monitors_changed), this);
98 
99     gdk_window_remove_filter(mRootWindow, root_window_event_filter, this);
100     g_object_unref(mRootWindow);
101     mRootWindow = nullptr;
102   }
103 }
104 
GetGTKMonitorScaleFactor(gint aMonitorNum)105 gint ScreenHelperGTK::GetGTKMonitorScaleFactor(gint aMonitorNum) {
106 #if (MOZ_WIDGET_GTK >= 3)
107   // Since GDK 3.10
108   static auto sGdkScreenGetMonitorScaleFactorPtr =
109       (gint(*)(GdkScreen*, gint))dlsym(RTLD_DEFAULT,
110                                        "gdk_screen_get_monitor_scale_factor");
111   if (sGdkScreenGetMonitorScaleFactorPtr) {
112     GdkScreen* screen = gdk_screen_get_default();
113     return sGdkScreenGetMonitorScaleFactorPtr(screen, aMonitorNum);
114   }
115 #endif
116   return 1;
117 }
118 
GetGTKPixelDepth()119 static uint32_t GetGTKPixelDepth() {
120   GdkVisual* visual = gdk_screen_get_system_visual(gdk_screen_get_default());
121   return gdk_visual_get_depth(visual);
122 }
123 
MakeScreen(GdkScreen * aScreen,gint aMonitorNum)124 static already_AddRefed<Screen> MakeScreen(GdkScreen* aScreen,
125                                            gint aMonitorNum) {
126   GdkRectangle monitor;
127   GdkRectangle workarea;
128   gdk_screen_get_monitor_geometry(aScreen, aMonitorNum, &monitor);
129   gdk_screen_get_monitor_workarea(aScreen, aMonitorNum, &workarea);
130   gint gdkScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor(aMonitorNum);
131 
132   // gdk_screen_get_monitor_geometry / workarea returns application pixels
133   // (desktop pixels), so we need to convert it to device pixels with
134   // gdkScaleFactor.
135   LayoutDeviceIntRect rect(
136       monitor.x * gdkScaleFactor, monitor.y * gdkScaleFactor,
137       monitor.width * gdkScaleFactor, monitor.height * gdkScaleFactor);
138   LayoutDeviceIntRect availRect(
139       workarea.x * gdkScaleFactor, workarea.y * gdkScaleFactor,
140       workarea.width * gdkScaleFactor, workarea.height * gdkScaleFactor);
141   uint32_t pixelDepth = GetGTKPixelDepth();
142 
143   // Use per-monitor scaling factor in gtk/wayland, or 1.0 otherwise.
144   DesktopToLayoutDeviceScale contentsScale(1.0);
145 #ifdef MOZ_WAYLAND
146   GdkDisplay* gdkDisplay = gdk_display_get_default();
147   if (GDK_IS_WAYLAND_DISPLAY(gdkDisplay)) {
148     contentsScale.scale = gdkScaleFactor;
149   }
150 #endif
151 
152   CSSToLayoutDeviceScale defaultCssScale(gdkScaleFactor *
153                                          gfxPlatformGtk::GetFontScaleFactor());
154 
155   float dpi = 96.0f;
156   gint heightMM = gdk_screen_get_monitor_height_mm(aScreen, aMonitorNum);
157   if (heightMM > 0) {
158     dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT);
159   }
160 
161   MOZ_LOG(sScreenLog, LogLevel::Debug,
162           ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.x, rect.y,
163            rect.width, rect.height, availRect.x, availRect.y, availRect.width,
164            availRect.height, pixelDepth, contentsScale.scale,
165            defaultCssScale.scale, dpi));
166   RefPtr<Screen> screen = new Screen(rect, availRect, pixelDepth, pixelDepth,
167                                      contentsScale, defaultCssScale, dpi);
168   return screen.forget();
169 }
170 
RefreshScreens()171 void ScreenHelperGTK::RefreshScreens() {
172   MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
173   AutoTArray<RefPtr<Screen>, 4> screenList;
174 
175   GdkScreen* defaultScreen = gdk_screen_get_default();
176   gint numScreens = gdk_screen_get_n_monitors(defaultScreen);
177   MOZ_LOG(sScreenLog, LogLevel::Debug, ("GDK reports %d screens", numScreens));
178 
179   for (gint i = 0; i < numScreens; i++) {
180     screenList.AppendElement(MakeScreen(defaultScreen, i));
181   }
182 
183   ScreenManager& screenManager = ScreenManager::GetSingleton();
184   screenManager.Refresh(Move(screenList));
185 }
186 
187 }  // namespace widget
188 }  // namespace mozilla
189