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/. */
7 #include "ScreenHelperGTK.h"
10 # include <gdk/gdkx.h>
11 # include <X11/Xlib.h>
12 # include "X11UndefineNone.h"
15 # include <gdk/gdkwayland.h>
16 #endif /* MOZ_WAYLAND */
20 #include "gfxPlatformGtk.h"
21 #include "mozilla/dom/DOMTypes.h"
22 #include "mozilla/Logging.h"
23 #include "mozilla/WidgetUtilsGtk.h"
24 #include "nsGtkUtils.h"
31 # include "nsWaylandDisplay.h"
34 namespace mozilla::widget
{
37 static LazyLogModule
sScreenLog("WidgetScreen");
38 # define LOG_SCREEN(...) MOZ_LOG(sScreenLog, LogLevel::Debug, (__VA_ARGS__))
40 # define LOG_SCREEN(...)
41 #endif /* MOZ_LOGGING */
43 using GdkMonitor
= struct _GdkMonitor
;
45 class ScreenGetterGtk final
{
47 ScreenGetterGtk() = default;
53 Atom
NetWorkareaAtom() { return mNetWorkareaAtom
; }
56 // For internal use from signal callback functions
57 void RefreshScreens();
60 GdkWindow
* mRootWindow
= nullptr;
62 Atom mNetWorkareaAtom
= 0;
66 static GdkMonitor
* GdkDisplayGetMonitor(GdkDisplay
* aDisplay
, int aMonitorNum
) {
67 static auto s_gdk_display_get_monitor
= (GdkMonitor
* (*)(GdkDisplay
*, int))
68 dlsym(RTLD_DEFAULT
, "gdk_display_get_monitor");
69 if (!s_gdk_display_get_monitor
) {
72 return s_gdk_display_get_monitor(aDisplay
, aMonitorNum
);
75 RefPtr
<Screen
> ScreenHelperGTK::GetScreenForWindow(nsWindow
* aWindow
) {
76 LOG_SCREEN("GetScreenForWindow() [%p]", aWindow
);
78 static auto s_gdk_display_get_monitor_at_window
=
79 (GdkMonitor
* (*)(GdkDisplay
*, GdkWindow
*))
80 dlsym(RTLD_DEFAULT
, "gdk_display_get_monitor_at_window");
82 if (!s_gdk_display_get_monitor_at_window
) {
83 LOG_SCREEN(" failed, missing Gtk helpers");
87 GdkWindow
* gdkWindow
= gtk_widget_get_window(aWindow
->GetGtkWidget());
89 LOG_SCREEN(" failed, can't get GdkWindow");
93 GdkDisplay
* display
= gdk_display_get_default();
94 GdkMonitor
* monitor
= s_gdk_display_get_monitor_at_window(display
, gdkWindow
);
96 LOG_SCREEN(" failed, can't get monitor for GdkWindow");
101 while (GdkMonitor
* m
= GdkDisplayGetMonitor(display
, ++index
)) {
103 return ScreenManager::GetSingleton().CurrentScreenList().SafeElementAt(
108 LOG_SCREEN(" Couldn't find monitor %p", monitor
);
112 static UniquePtr
<ScreenGetterGtk
> gScreenGetter
;
114 static void monitors_changed(GdkScreen
* aScreen
, gpointer aClosure
) {
115 LOG_SCREEN("Received monitors-changed event");
116 auto* self
= static_cast<ScreenGetterGtk
*>(aClosure
);
117 self
->RefreshScreens();
120 static void screen_resolution_changed(GdkScreen
* aScreen
, GParamSpec
* aPspec
,
121 ScreenGetterGtk
* self
) {
122 self
->RefreshScreens();
125 static GdkFilterReturn
root_window_event_filter(GdkXEvent
* aGdkXEvent
,
129 ScreenGetterGtk
* self
= static_cast<ScreenGetterGtk
*>(aClosure
);
130 XEvent
* xevent
= static_cast<XEvent
*>(aGdkXEvent
);
132 switch (xevent
->type
) {
133 case PropertyNotify
: {
134 XPropertyEvent
* propertyEvent
= &xevent
->xproperty
;
135 if (propertyEvent
->atom
== self
->NetWorkareaAtom()) {
136 LOG_SCREEN("Work area size changed");
137 self
->RefreshScreens();
145 return GDK_FILTER_CONTINUE
;
148 void ScreenGetterGtk::Init() {
149 LOG_SCREEN("ScreenGetterGtk created");
150 GdkScreen
* defaultScreen
= gdk_screen_get_default();
151 if (!defaultScreen
) {
152 // Sometimes we don't initial X (e.g., xpcshell)
153 MOZ_LOG(sScreenLog
, LogLevel::Debug
,
154 ("defaultScreen is nullptr, running headless"));
157 mRootWindow
= gdk_get_default_root_window();
158 MOZ_ASSERT(mRootWindow
);
160 g_object_ref(mRootWindow
);
162 // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
163 gdk_window_set_events(mRootWindow
,
164 GdkEventMask(gdk_window_get_events(mRootWindow
) |
165 GDK_PROPERTY_CHANGE_MASK
));
167 g_signal_connect(defaultScreen
, "monitors-changed",
168 G_CALLBACK(monitors_changed
), this);
169 // Use _after to ensure this callback is run after gfxPlatformGtk.cpp's
171 g_signal_connect_after(defaultScreen
, "notify::resolution",
172 G_CALLBACK(screen_resolution_changed
), this);
174 gdk_window_add_filter(mRootWindow
, root_window_event_filter
, this);
175 if (GdkIsX11Display()) {
176 mNetWorkareaAtom
= XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow
),
177 "_NET_WORKAREA", X11False
);
183 ScreenGetterGtk::~ScreenGetterGtk() {
185 g_signal_handlers_disconnect_by_data(gdk_screen_get_default(), this);
187 gdk_window_remove_filter(mRootWindow
, root_window_event_filter
, this);
188 g_object_unref(mRootWindow
);
189 mRootWindow
= nullptr;
193 static uint32_t GetGTKPixelDepth() {
194 GdkVisual
* visual
= gdk_screen_get_system_visual(gdk_screen_get_default());
195 return gdk_visual_get_depth(visual
);
198 static already_AddRefed
<Screen
> MakeScreenGtk(GdkScreen
* aScreen
,
200 gint gdkScaleFactor
= ScreenHelperGTK::GetGTKMonitorScaleFactor(aMonitorNum
);
202 // gdk_screen_get_monitor_geometry / workarea returns application pixels
203 // (desktop pixels), so we need to convert it to device pixels with
205 gint geometryScaleFactor
= gdkScaleFactor
;
207 gint refreshRate
= [&] {
209 static auto s_gdk_monitor_get_refresh_rate
= (int (*)(GdkMonitor
*))dlsym(
210 RTLD_DEFAULT
, "gdk_monitor_get_refresh_rate");
212 if (!s_gdk_monitor_get_refresh_rate
) {
215 GdkMonitor
* monitor
=
216 GdkDisplayGetMonitor(gdk_display_get_default(), aMonitorNum
);
221 return NSToIntRound(s_gdk_monitor_get_refresh_rate(monitor
) / 1000.0f
);
224 GdkRectangle workarea
;
225 gdk_screen_get_monitor_workarea(aScreen
, aMonitorNum
, &workarea
);
226 LayoutDeviceIntRect
availRect(workarea
.x
* geometryScaleFactor
,
227 workarea
.y
* geometryScaleFactor
,
228 workarea
.width
* geometryScaleFactor
,
229 workarea
.height
* geometryScaleFactor
);
230 LayoutDeviceIntRect rect
;
231 DesktopToLayoutDeviceScale
contentsScale(1.0);
232 if (GdkIsX11Display()) {
233 GdkRectangle monitor
;
234 gdk_screen_get_monitor_geometry(aScreen
, aMonitorNum
, &monitor
);
235 rect
= LayoutDeviceIntRect(monitor
.x
* geometryScaleFactor
,
236 monitor
.y
* geometryScaleFactor
,
237 monitor
.width
* geometryScaleFactor
,
238 monitor
.height
* geometryScaleFactor
);
240 // Don't report screen shift in Wayland, see bug 1795066.
241 availRect
.MoveTo(0, 0);
242 // We use Gtk workarea on Wayland as it matches our needs (Bug 1732682).
244 // Use per-monitor scaling factor in Wayland.
245 contentsScale
.scale
= gdkScaleFactor
;
248 uint32_t pixelDepth
= GetGTKPixelDepth();
250 CSSToLayoutDeviceScale
defaultCssScale(gdkScaleFactor
);
253 gint heightMM
= gdk_screen_get_monitor_height_mm(aScreen
, aMonitorNum
);
255 dpi
= rect
.height
/ (heightMM
/ MM_PER_INCH_FLOAT
);
259 "New monitor %d size [%d,%d -> %d x %d] depth %d scale %f CssScale %f "
260 "DPI %f refresh %d ]",
261 aMonitorNum
, rect
.x
, rect
.y
, rect
.width
, rect
.height
, pixelDepth
,
262 contentsScale
.scale
, defaultCssScale
.scale
, dpi
, refreshRate
);
263 return MakeAndAddRef
<Screen
>(rect
, availRect
, pixelDepth
, pixelDepth
,
264 refreshRate
, contentsScale
, defaultCssScale
, dpi
,
265 Screen::IsPseudoDisplay::No
);
268 void ScreenGetterGtk::RefreshScreens() {
269 LOG_SCREEN("ScreenGetterGtk::RefreshScreens()");
270 AutoTArray
<RefPtr
<Screen
>, 4> screenList
;
272 GdkScreen
* defaultScreen
= gdk_screen_get_default();
273 gint numScreens
= gdk_screen_get_n_monitors(defaultScreen
);
274 LOG_SCREEN("GDK reports %d screens", numScreens
);
276 for (gint i
= 0; i
< numScreens
; i
++) {
277 screenList
.AppendElement(MakeScreenGtk(defaultScreen
, i
));
280 ScreenManager::Refresh(std::move(screenList
));
283 gint
ScreenHelperGTK::GetGTKMonitorScaleFactor(gint aMonitorNum
) {
284 GdkScreen
* screen
= gdk_screen_get_default();
285 return gdk_screen_get_monitor_scale_factor(screen
, aMonitorNum
);
288 ScreenHelperGTK::ScreenHelperGTK() {
289 gScreenGetter
= MakeUnique
<ScreenGetterGtk
>();
290 gScreenGetter
->Init();
293 int ScreenHelperGTK::GetMonitorCount() {
294 return gdk_screen_get_n_monitors(gdk_screen_get_default());
297 ScreenHelperGTK::~ScreenHelperGTK() { gScreenGetter
= nullptr; }
299 } // namespace mozilla::widget