Bug 1751217 Part 3: Make HDR-capable macOS screens report 30 pixelDepth. r=mstange
[gecko.git] / widget / gtk / ScreenHelperGTK.cpp
blob2a2b068e93cebc0d0828ebbc2ffe0e4899d71c2e
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"
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>
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"
25 namespace mozilla {
26 namespace widget {
28 #ifdef MOZ_LOGGING
29 static LazyLogModule sScreenLog("WidgetScreen");
30 # define LOG_SCREEN(args) MOZ_LOG(sScreenLog, LogLevel::Debug, args)
31 #else
32 # define LOG_SCREEN(args)
33 #endif /* MOZ_LOGGING */
35 using GdkMonitor = struct _GdkMonitor;
37 static UniquePtr<ScreenGetter> gScreenGetter;
39 static void monitors_changed(GdkScreen* aScreen, gpointer aClosure) {
40 LOG_SCREEN(("Received monitors-changed event"));
41 ScreenGetterGtk* self = static_cast<ScreenGetterGtk*>(aClosure);
42 self->RefreshScreens();
45 static void screen_resolution_changed(GdkScreen* aScreen, GParamSpec* aPspec,
46 ScreenGetterGtk* self) {
47 self->RefreshScreens();
50 static GdkFilterReturn root_window_event_filter(GdkXEvent* aGdkXEvent,
51 GdkEvent* aGdkEvent,
52 gpointer aClosure) {
53 #ifdef MOZ_X11
54 ScreenGetterGtk* self = static_cast<ScreenGetterGtk*>(aClosure);
55 XEvent* xevent = static_cast<XEvent*>(aGdkXEvent);
57 switch (xevent->type) {
58 case PropertyNotify: {
59 XPropertyEvent* propertyEvent = &xevent->xproperty;
60 if (propertyEvent->atom == self->NetWorkareaAtom()) {
61 LOG_SCREEN(("Work area size changed"));
62 self->RefreshScreens();
64 } break;
65 default:
66 break;
68 #endif
70 return GDK_FILTER_CONTINUE;
73 ScreenGetterGtk::ScreenGetterGtk()
74 : mRootWindow(nullptr)
75 #ifdef MOZ_X11
77 mNetWorkareaAtom(0)
78 #endif
82 void ScreenGetterGtk::Init() {
83 LOG_SCREEN(("ScreenGetterGtk created"));
84 GdkScreen* defaultScreen = gdk_screen_get_default();
85 if (!defaultScreen) {
86 // Sometimes we don't initial X (e.g., xpcshell)
87 MOZ_LOG(sScreenLog, LogLevel::Debug,
88 ("defaultScreen is nullptr, running headless"));
89 return;
91 mRootWindow = gdk_get_default_root_window();
92 MOZ_ASSERT(mRootWindow);
94 g_object_ref(mRootWindow);
96 // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
97 gdk_window_set_events(mRootWindow,
98 GdkEventMask(gdk_window_get_events(mRootWindow) |
99 GDK_PROPERTY_CHANGE_MASK));
101 g_signal_connect(defaultScreen, "monitors-changed",
102 G_CALLBACK(monitors_changed), this);
103 // Use _after to ensure this callback is run after gfxPlatformGtk.cpp's
104 // handler.
105 g_signal_connect_after(defaultScreen, "notify::resolution",
106 G_CALLBACK(screen_resolution_changed), this);
107 #ifdef MOZ_X11
108 gdk_window_add_filter(mRootWindow, root_window_event_filter, this);
109 if (GdkIsX11Display()) {
110 mNetWorkareaAtom = XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow),
111 "_NET_WORKAREA", X11False);
113 #endif
114 RefreshScreens();
117 ScreenGetterGtk::~ScreenGetterGtk() {
118 if (mRootWindow) {
119 g_signal_handlers_disconnect_by_data(gdk_screen_get_default(), this);
121 gdk_window_remove_filter(mRootWindow, root_window_event_filter, this);
122 g_object_unref(mRootWindow);
123 mRootWindow = nullptr;
127 static uint32_t GetGTKPixelDepth() {
128 GdkVisual* visual = gdk_screen_get_system_visual(gdk_screen_get_default());
129 return gdk_visual_get_depth(visual);
132 static bool IsGNOMECompositor() {
133 const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
134 return currentDesktop && strstr(currentDesktop, "GNOME") != nullptr;
137 static already_AddRefed<Screen> MakeScreenGtk(GdkScreen* aScreen,
138 gint aMonitorNum) {
139 gint gdkScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor(aMonitorNum);
141 // gdk_screen_get_monitor_geometry / workarea returns application pixels
142 // (desktop pixels), so we need to convert it to device pixels with
143 // gdkScaleFactor on X11.
144 // GNOME/Wayland reports scales differently (Bug 1732682).
145 gint geometryScaleFactor = 1;
146 if (GdkIsX11Display() || (GdkIsWaylandDisplay() && !IsGNOMECompositor())) {
147 geometryScaleFactor = gdkScaleFactor;
150 LayoutDeviceIntRect rect;
152 GdkRectangle workarea;
153 gdk_screen_get_monitor_workarea(aScreen, aMonitorNum, &workarea);
154 LayoutDeviceIntRect availRect(workarea.x * geometryScaleFactor,
155 workarea.y * geometryScaleFactor,
156 workarea.width * geometryScaleFactor,
157 workarea.height * geometryScaleFactor);
158 if (GdkIsX11Display()) {
159 GdkRectangle monitor;
160 gdk_screen_get_monitor_geometry(aScreen, aMonitorNum, &monitor);
161 rect = LayoutDeviceIntRect(monitor.x * geometryScaleFactor,
162 monitor.y * geometryScaleFactor,
163 monitor.width * geometryScaleFactor,
164 monitor.height * geometryScaleFactor);
165 } else {
166 // We use Gtk workarea on Wayland as it matches our needs (Bug 1732682).
167 rect = availRect;
170 uint32_t pixelDepth = GetGTKPixelDepth();
172 // Use per-monitor scaling factor in gtk/wayland, or 1.0 otherwise.
173 DesktopToLayoutDeviceScale contentsScale(1.0);
174 #ifdef MOZ_WAYLAND
175 if (GdkIsWaylandDisplay()) {
176 contentsScale.scale = gdkScaleFactor;
178 #endif
180 CSSToLayoutDeviceScale defaultCssScale(gdkScaleFactor *
181 gfxPlatformGtk::GetFontScaleFactor());
183 float dpi = 96.0f;
184 gint heightMM = gdk_screen_get_monitor_height_mm(aScreen, aMonitorNum);
185 if (heightMM > 0) {
186 dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT);
189 LOG_SCREEN(
190 ("New monitor %d size [%d,%d -> %d x %d] depth %d scale %f CssScale %f "
191 "DPI %f ]",
192 aMonitorNum, rect.x, rect.y, rect.width, rect.height, pixelDepth,
193 contentsScale.scale, defaultCssScale.scale, dpi));
194 return MakeAndAddRef<Screen>(rect, availRect, pixelDepth, pixelDepth,
195 contentsScale, defaultCssScale, dpi);
198 void ScreenGetterGtk::RefreshScreens() {
199 LOG_SCREEN(("Refreshing screens"));
200 AutoTArray<RefPtr<Screen>, 4> screenList;
202 GdkScreen* defaultScreen = gdk_screen_get_default();
203 gint numScreens = gdk_screen_get_n_monitors(defaultScreen);
204 LOG_SCREEN(("GDK reports %d screens", numScreens));
206 for (gint i = 0; i < numScreens; i++) {
207 screenList.AppendElement(MakeScreenGtk(defaultScreen, i));
210 ScreenManager::Refresh(std::move(screenList));
213 #ifdef MOZ_WAYLAND
214 static void output_handle_geometry(void* data, struct wl_output* wl_output,
215 int x, int y, int physical_width,
216 int physical_height, int subpixel,
217 const char* make, const char* model,
218 int32_t transform) {
219 MonitorConfig* monitor = (MonitorConfig*)data;
220 LOG_SCREEN(("wl_output: geometry position %d %d physical size %d %d", x, y,
221 physical_width, physical_height));
222 monitor->x = x;
223 monitor->y = y;
224 monitor->width_mm = physical_width;
225 monitor->height_mm = physical_height;
228 static void output_handle_done(void* data, struct wl_output* wl_output) {
229 LOG_SCREEN(("done"));
230 gScreenGetter->RefreshScreens();
233 static void output_handle_scale(void* data, struct wl_output* wl_output,
234 int32_t scale) {
235 MonitorConfig* monitor = (MonitorConfig*)data;
236 LOG_SCREEN(("wl_output: scale %d", scale));
237 monitor->scale = scale;
240 static void output_handle_mode(void* data, struct wl_output* wl_output,
241 uint32_t flags, int width, int height,
242 int refresh) {
243 MonitorConfig* monitor = (MonitorConfig*)data;
245 LOG_SCREEN(("wl_output: mode output size %d x %d refresh %d", width, height,
246 refresh));
248 if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) return;
250 monitor->width = width;
251 monitor->height = height;
254 static const struct wl_output_listener output_listener = {
255 output_handle_geometry,
256 output_handle_mode,
257 output_handle_done,
258 output_handle_scale,
261 static void screen_registry_handler(void* data, wl_registry* registry,
262 uint32_t id, const char* interface,
263 uint32_t version) {
264 ScreenGetterWayland* getter = static_cast<ScreenGetterWayland*>(data);
265 if (strcmp(interface, "wl_output") == 0 && version > 1) {
266 auto* output =
267 WaylandRegistryBind<wl_output>(registry, id, &wl_output_interface, 2);
268 wl_output_add_listener(output, &output_listener,
269 getter->AddMonitorConfig(id));
273 static void screen_registry_remover(void* data, struct wl_registry* registry,
274 uint32_t id) {
275 auto* getter = static_cast<ScreenGetterWayland*>(data);
276 if (getter->RemoveMonitorConfig(id)) {
277 getter->RefreshScreens();
279 /* TODO: the object needs to be destroyed here, we're leaking */
282 static const struct wl_registry_listener screen_registry_listener = {
283 screen_registry_handler, screen_registry_remover};
285 void ScreenGetterWayland::Init() {
286 MOZ_ASSERT(GdkIsWaylandDisplay());
287 LOG_SCREEN(("ScreenGetterWayland created"));
288 wl_display* display = WaylandDisplayGetWLDisplay();
289 mRegistry = wl_display_get_registry(display);
290 wl_registry_add_listener((wl_registry*)mRegistry, &screen_registry_listener,
291 this);
292 wl_display_roundtrip(display);
293 wl_display_roundtrip(display);
296 MonitorConfig* ScreenGetterWayland::AddMonitorConfig(int aId) {
297 mMonitors.EmplaceBack(aId);
298 LOG_SCREEN(("Add Monitor ID %d num %d", aId, (int)(mMonitors.Length() - 1)));
299 return &(mMonitors[mMonitors.Length() - 1]);
302 bool ScreenGetterWayland::RemoveMonitorConfig(int aId) {
303 for (unsigned int i = 0; i < mMonitors.Length(); i++) {
304 if (mMonitors[i].id == aId) {
305 LOG_SCREEN(("Remove Monitor ID %d num %d", aId, i));
306 mMonitors.RemoveElementAt(i);
307 return true;
310 return false;
313 ScreenGetterWayland::~ScreenGetterWayland() {
314 g_clear_pointer(&mRegistry, wl_registry_destroy);
317 static bool GdkMonitorGetWorkarea(GdkMonitor* monitor, GdkRectangle* workarea) {
318 static auto s_gdk_monitor_get_workarea =
319 (void (*)(GdkMonitor*, GdkRectangle*))dlsym(RTLD_DEFAULT,
320 "gdk_monitor_get_workarea");
321 if (!s_gdk_monitor_get_workarea) {
322 return false;
325 s_gdk_monitor_get_workarea(monitor, workarea);
326 return true;
329 already_AddRefed<Screen> ScreenGetterWayland::MakeScreenWayland(gint aMonitor) {
330 MonitorConfig monitor = mMonitors[aMonitor];
332 // On GNOME/Mutter we use results from wl_output directly
333 LayoutDeviceIntRect rect(monitor.x, monitor.y, monitor.width, monitor.height);
335 uint32_t pixelDepth = GetGTKPixelDepth();
337 // Use per-monitor scaling factor in gtk/wayland, or 1.0 otherwise.
338 DesktopToLayoutDeviceScale contentsScale(1.0);
339 contentsScale.scale = monitor.scale;
341 CSSToLayoutDeviceScale defaultCssScale(monitor.scale *
342 gfxPlatformGtk::GetFontScaleFactor());
344 float dpi = 96.0f;
345 gint heightMM = monitor.height_mm;
346 if (heightMM > 0) {
347 dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT);
350 LOG_SCREEN(
351 ("Monitor %d [%d %d -> %d x %d depth %d content scale %f css scale %f "
352 "DPI %f]",
353 aMonitor, rect.x, rect.y, rect.width, rect.height, pixelDepth,
354 contentsScale.scale, defaultCssScale.scale, dpi));
355 return MakeAndAddRef<Screen>(rect, rect, pixelDepth, pixelDepth,
356 contentsScale, defaultCssScale, dpi);
359 void ScreenGetterWayland::RefreshScreens() {
360 LOG_SCREEN(("Refreshing screens"));
361 AutoTArray<RefPtr<Screen>, 4> managerScreenList;
363 mScreenList.Clear();
364 const gint numScreens = mMonitors.Length();
365 for (gint i = 0; i < numScreens; i++) {
366 RefPtr<Screen> screen = MakeScreenWayland(i);
367 mScreenList.AppendElement(screen);
368 managerScreenList.AppendElement(screen);
371 ScreenManager::Refresh(std::move(managerScreenList));
374 int ScreenGetterWayland::GetMonitorForWindow(nsWindow* aWindow) {
375 LOG_SCREEN(("GetMonitorForWindow() [%p]", aWindow));
377 static auto s_gdk_display_get_monitor_at_window =
378 (GdkMonitor * (*)(GdkDisplay*, GdkWindow*))
379 dlsym(RTLD_DEFAULT, "gdk_display_get_monitor_at_window");
381 if (!s_gdk_display_get_monitor_at_window) {
382 LOG_SCREEN((" failed, missing Gtk helpers"));
383 return -1;
386 GdkWindow* gdkWindow = gtk_widget_get_window(aWindow->GetGtkWidget());
387 if (!gdkWindow) {
388 LOG_SCREEN((" failed, can't get GdkWindow"));
389 return -1;
392 GdkMonitor* monitor =
393 s_gdk_display_get_monitor_at_window(gdk_display_get_default(), gdkWindow);
394 if (!monitor) {
395 LOG_SCREEN((" failed, can't get monitor for GdkWindow"));
396 return -1;
399 GdkRectangle workArea;
400 if (!GdkMonitorGetWorkarea(monitor, &workArea)) {
401 return -1;
404 for (unsigned int i = 0; i < mMonitors.Length(); i++) {
405 // Although Gtk/Mutter is very creative in reporting various screens sizes
406 // we can rely on Gtk work area start position to match wl_output.
407 if (mMonitors[i].x == workArea.x && mMonitors[i].y == workArea.y) {
408 LOG_SCREEN((" monitor %d values %d %d -> %d x %d", i, mMonitors[i].x,
409 mMonitors[i].y, mMonitors[i].width, mMonitors[i].height));
410 return i;
414 return -1;
417 RefPtr<nsIScreen> ScreenGetterWayland::GetScreenForWindow(nsWindow* aWindow) {
418 if (mScreenList.IsEmpty()) {
419 return nullptr;
422 int monitor = GetMonitorForWindow(aWindow);
423 if (monitor < 0) {
424 return nullptr;
426 return mScreenList[monitor];
428 #endif
430 RefPtr<nsIScreen> ScreenHelperGTK::GetScreenForWindow(nsWindow* aWindow) {
431 return gScreenGetter->GetScreenForWindow(aWindow);
434 gint ScreenHelperGTK::GetGTKMonitorScaleFactor(gint aMonitorNum) {
435 GdkScreen* screen = gdk_screen_get_default();
436 return gdk_screen_get_monitor_scale_factor(screen, aMonitorNum);
439 ScreenHelperGTK::ScreenHelperGTK() {
440 #ifdef MOZ_WAYLAND
441 // Use ScreenGetterWayland on Gnome/Mutter only. It uses additional wl_output
442 // to track screen size changes (which are wrongly reported by mutter)
443 // and causes issues on Sway (Bug 1730476).
444 // https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/3941
445 if (GdkIsWaylandDisplay() && IsGNOMECompositor()) {
446 gScreenGetter = mozilla::MakeUnique<ScreenGetterWayland>();
448 #endif
449 if (!gScreenGetter) {
450 gScreenGetter = mozilla::MakeUnique<ScreenGetterGtk>();
452 gScreenGetter->Init();
455 ScreenHelperGTK::~ScreenHelperGTK() { gScreenGetter = nullptr; }
457 } // namespace widget
458 } // namespace mozilla