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/StaticPtr.h"
24 #include "mozilla/WidgetUtilsGtk.h"
25 #include "nsGtkUtils.h"
32 # include "nsWaylandDisplay.h"
35 namespace mozilla::widget
{
38 static LazyLogModule
sScreenLog("WidgetScreen");
39 # define LOG_SCREEN(...) MOZ_LOG(sScreenLog, LogLevel::Debug, (__VA_ARGS__))
41 # define LOG_SCREEN(...)
42 #endif /* MOZ_LOGGING */
44 using GdkMonitor
= struct _GdkMonitor
;
46 class ScreenGetterGtk final
{
48 ScreenGetterGtk() = default;
54 Atom
NetWorkareaAtom() { return mNetWorkareaAtom
; }
57 // For internal use from signal callback functions
58 void RefreshScreens();
61 GdkWindow
* mRootWindow
= nullptr;
63 Atom mNetWorkareaAtom
= 0;
67 static GdkMonitor
* GdkDisplayGetMonitor(GdkDisplay
* aDisplay
, int aMonitorNum
) {
68 static auto s_gdk_display_get_monitor
= (GdkMonitor
* (*)(GdkDisplay
*, int))
69 dlsym(RTLD_DEFAULT
, "gdk_display_get_monitor");
70 if (!s_gdk_display_get_monitor
) {
73 return s_gdk_display_get_monitor(aDisplay
, aMonitorNum
);
76 RefPtr
<Screen
> ScreenHelperGTK::GetScreenForWindow(nsWindow
* aWindow
) {
77 LOG_SCREEN("GetScreenForWindow() [%p]", aWindow
);
79 static auto s_gdk_display_get_monitor_at_window
=
80 (GdkMonitor
* (*)(GdkDisplay
*, GdkWindow
*))
81 dlsym(RTLD_DEFAULT
, "gdk_display_get_monitor_at_window");
83 if (!s_gdk_display_get_monitor_at_window
) {
84 LOG_SCREEN(" failed, missing Gtk helpers");
88 GdkWindow
* gdkWindow
= aWindow
->GetToplevelGdkWindow();
90 LOG_SCREEN(" failed, can't get GdkWindow");
94 GdkDisplay
* display
= gdk_display_get_default();
95 GdkMonitor
* monitor
= s_gdk_display_get_monitor_at_window(display
, gdkWindow
);
97 LOG_SCREEN(" failed, can't get monitor for GdkWindow");
102 while (GdkMonitor
* m
= GdkDisplayGetMonitor(display
, ++index
)) {
104 return ScreenManager::GetSingleton().CurrentScreenList().SafeElementAt(
109 LOG_SCREEN(" Couldn't find monitor %p", monitor
);
113 static StaticAutoPtr
<ScreenGetterGtk
> gScreenGetter
;
115 static void monitors_changed(GdkScreen
* aScreen
, gpointer aClosure
) {
116 LOG_SCREEN("Received monitors-changed event");
117 auto* self
= static_cast<ScreenGetterGtk
*>(aClosure
);
118 self
->RefreshScreens();
121 static void screen_resolution_changed(GdkScreen
* aScreen
, GParamSpec
* aPspec
,
122 ScreenGetterGtk
* self
) {
123 self
->RefreshScreens();
126 static GdkFilterReturn
root_window_event_filter(GdkXEvent
* aGdkXEvent
,
130 ScreenGetterGtk
* self
= static_cast<ScreenGetterGtk
*>(aClosure
);
131 XEvent
* xevent
= static_cast<XEvent
*>(aGdkXEvent
);
133 switch (xevent
->type
) {
134 case PropertyNotify
: {
135 XPropertyEvent
* propertyEvent
= &xevent
->xproperty
;
136 if (propertyEvent
->atom
== self
->NetWorkareaAtom()) {
137 LOG_SCREEN("Work area size changed");
138 self
->RefreshScreens();
146 return GDK_FILTER_CONTINUE
;
149 void ScreenGetterGtk::Init() {
150 LOG_SCREEN("ScreenGetterGtk created");
151 GdkScreen
* defaultScreen
= gdk_screen_get_default();
152 if (!defaultScreen
) {
153 // Sometimes we don't initial X (e.g., xpcshell)
154 MOZ_LOG(sScreenLog
, LogLevel::Debug
,
155 ("defaultScreen is nullptr, running headless"));
158 mRootWindow
= gdk_get_default_root_window();
159 MOZ_ASSERT(mRootWindow
);
161 g_object_ref(mRootWindow
);
163 // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
164 gdk_window_set_events(mRootWindow
,
165 GdkEventMask(gdk_window_get_events(mRootWindow
) |
166 GDK_PROPERTY_CHANGE_MASK
));
168 g_signal_connect(defaultScreen
, "monitors-changed",
169 G_CALLBACK(monitors_changed
), this);
170 // Use _after to ensure this callback is run after gfxPlatformGtk.cpp's
172 g_signal_connect_after(defaultScreen
, "notify::resolution",
173 G_CALLBACK(screen_resolution_changed
), this);
175 gdk_window_add_filter(mRootWindow
, root_window_event_filter
, this);
176 if (GdkIsX11Display()) {
177 mNetWorkareaAtom
= XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow
),
178 "_NET_WORKAREA", X11False
);
184 ScreenGetterGtk::~ScreenGetterGtk() {
186 g_signal_handlers_disconnect_by_data(gdk_screen_get_default(), this);
188 gdk_window_remove_filter(mRootWindow
, root_window_event_filter
, this);
189 g_object_unref(mRootWindow
);
190 mRootWindow
= nullptr;
194 static uint32_t GetGTKPixelDepth() {
195 GdkVisual
* visual
= gdk_screen_get_system_visual(gdk_screen_get_default());
196 return gdk_visual_get_depth(visual
);
199 static already_AddRefed
<Screen
> MakeScreenGtk(GdkScreen
* aScreen
,
201 gint gdkScaleFactor
= ScreenHelperGTK::GetGTKMonitorScaleFactor(aMonitorNum
);
203 // gdk_screen_get_monitor_geometry / workarea returns application pixels
204 // (desktop pixels), so we need to convert it to device pixels with
206 gint geometryScaleFactor
= gdkScaleFactor
;
208 gint refreshRate
= [&] {
210 static auto s_gdk_monitor_get_refresh_rate
= (int (*)(GdkMonitor
*))dlsym(
211 RTLD_DEFAULT
, "gdk_monitor_get_refresh_rate");
213 if (!s_gdk_monitor_get_refresh_rate
) {
216 GdkMonitor
* monitor
=
217 GdkDisplayGetMonitor(gdk_display_get_default(), aMonitorNum
);
222 return NSToIntRound(s_gdk_monitor_get_refresh_rate(monitor
) / 1000.0f
);
225 GdkRectangle workarea
;
226 gdk_screen_get_monitor_workarea(aScreen
, aMonitorNum
, &workarea
);
227 LayoutDeviceIntRect
availRect(workarea
.x
* geometryScaleFactor
,
228 workarea
.y
* geometryScaleFactor
,
229 workarea
.width
* geometryScaleFactor
,
230 workarea
.height
* geometryScaleFactor
);
231 LayoutDeviceIntRect rect
;
232 DesktopToLayoutDeviceScale
contentsScale(1.0);
233 if (GdkIsX11Display()) {
234 GdkRectangle monitor
;
235 gdk_screen_get_monitor_geometry(aScreen
, aMonitorNum
, &monitor
);
236 rect
= LayoutDeviceIntRect(monitor
.x
* geometryScaleFactor
,
237 monitor
.y
* geometryScaleFactor
,
238 monitor
.width
* geometryScaleFactor
,
239 monitor
.height
* geometryScaleFactor
);
241 // Don't report screen shift in Wayland, see bug 1795066.
242 availRect
.MoveTo(0, 0);
243 // We use Gtk workarea on Wayland as it matches our needs (Bug 1732682).
245 // Use per-monitor scaling factor in Wayland.
246 contentsScale
.scale
= gdkScaleFactor
;
249 uint32_t pixelDepth
= GetGTKPixelDepth();
250 if (pixelDepth
== 32) {
251 // If a device uses 32 bits per pixel, it's still only using 8 bits
252 // per color component, which is what our callers want to know.
253 // (Some devices report 32 and some devices report 24.)
257 CSSToLayoutDeviceScale
defaultCssScale(gdkScaleFactor
);
260 gint heightMM
= gdk_screen_get_monitor_height_mm(aScreen
, aMonitorNum
);
262 dpi
= rect
.height
/ (heightMM
/ MM_PER_INCH_FLOAT
);
267 if (GdkIsWaylandDisplay()) {
268 isHDR
= WaylandDisplayGet()->IsHDREnabled();
273 "New monitor %d size [%d,%d -> %d x %d] depth %d scale %f CssScale %f "
274 "DPI %f refresh %d HDR %d]",
275 aMonitorNum
, rect
.x
, rect
.y
, rect
.width
, rect
.height
, pixelDepth
,
276 contentsScale
.scale
, defaultCssScale
.scale
, dpi
, refreshRate
, isHDR
);
277 return MakeAndAddRef
<Screen
>(
278 rect
, availRect
, pixelDepth
, pixelDepth
, refreshRate
, contentsScale
,
279 defaultCssScale
, dpi
, Screen::IsPseudoDisplay::No
, Screen::IsHDR(isHDR
));
282 void ScreenGetterGtk::RefreshScreens() {
283 LOG_SCREEN("ScreenGetterGtk::RefreshScreens()");
284 AutoTArray
<RefPtr
<Screen
>, 4> screenList
;
286 GdkScreen
* defaultScreen
= gdk_screen_get_default();
287 gint numScreens
= gdk_screen_get_n_monitors(defaultScreen
);
288 LOG_SCREEN("GDK reports %d screens", numScreens
);
290 for (gint i
= 0; i
< numScreens
; i
++) {
291 screenList
.AppendElement(MakeScreenGtk(defaultScreen
, i
));
294 ScreenManager::Refresh(std::move(screenList
));
297 gint
ScreenHelperGTK::GetGTKMonitorScaleFactor(gint aMonitorNum
) {
298 MOZ_ASSERT(NS_IsMainThread());
299 GdkScreen
* screen
= gdk_screen_get_default();
300 return aMonitorNum
< gdk_screen_get_n_monitors(screen
)
301 ? gdk_screen_get_monitor_scale_factor(screen
, aMonitorNum
)
305 ScreenHelperGTK::ScreenHelperGTK() {
306 gScreenGetter
= new ScreenGetterGtk();
307 gScreenGetter
->Init();
310 int ScreenHelperGTK::GetMonitorCount() {
311 return gdk_screen_get_n_monitors(gdk_screen_get_default());
314 ScreenHelperGTK::~ScreenHelperGTK() { gScreenGetter
= nullptr; }
316 } // namespace mozilla::widget