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