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
|