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