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