From 5ceb9a76922030552437afd359d6f5480263ede8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Mon, 8 Jan 2024 14:37:20 +0100 Subject: [PATCH 1/4] [gtk4/wayland] Fix GdkMonitor sizes on Mutter without fractional scaling. This fixes a problem that is apparent in https://bugzilla.mozilla.org/show_bug.cgi?id=1869724, but that also reproduces on any GTK application as described in https://bugzilla.mozilla.org/show_bug.cgi?id=1869724#c16. xdg_output sizes might be physical if the compositor doesn't scale them, it seems. So to report the correct logical geometry in GDK pixels, we need to detect this case. We do this by checking whether the wl_output size matches the xdg_output size. --- gdk/wayland/gdkdisplay-wayland.c | 68 +++++++++++++++++--------------- gdk/wayland/gdkmonitor-wayland.h | 9 ++--- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/gdk/wayland/gdkdisplay-wayland.c b/gdk/wayland/gdkdisplay-wayland.c index d205ca5b10..8133a7c0a9 100644 --- a/gdk/wayland/gdkdisplay-wayland.c +++ b/gdk/wayland/gdkdisplay-wayland.c @@ -2430,13 +2430,35 @@ apply_monitor_change (GdkWaylandMonitor *monitor) { GDK_DEBUG (MISC, "monitor %d changed position %d %d, size %d %d", monitor->id, - monitor->x, monitor->y, - monitor->width, monitor->height); + monitor->output_geometry.x, monitor->output_geometry.y, + monitor->output_geometry.width, monitor->output_geometry.height); - gdk_monitor_set_geometry (GDK_MONITOR (monitor), - &(GdkRectangle) { - monitor->x, monitor->y, - monitor->width, monitor->height }); + GdkRectangle logical_geometry; + gboolean needs_scaling = FALSE; + + if (monitor_has_xdg_output(monitor)) + { + logical_geometry = monitor->xdg_output_geometry; + needs_scaling = + logical_geometry.width == monitor->output_geometry.width || + logical_geometry.height == monitor->output_geometry.height; + } + else + { + logical_geometry = monitor->output_geometry; + needs_scaling = TRUE; + } + + if (needs_scaling) + { + int scale = gdk_monitor_get_scale_factor (GDK_MONITOR (monitor)); + logical_geometry.y /= scale; + logical_geometry.x /= scale; + logical_geometry.width /= scale; + logical_geometry.height /= scale; + } + + gdk_monitor_set_geometry (GDK_MONITOR (monitor), &logical_geometry); gdk_monitor_set_connector (GDK_MONITOR (monitor), monitor->name); gdk_monitor_set_description (GDK_MONITOR (monitor), monitor->description); monitor->wl_output_done = FALSE; @@ -2456,8 +2478,8 @@ xdg_output_handle_logical_position (void *data, GDK_DEBUG (MISC, "handle logical position xdg-output %d, position %d %d", monitor->id, x, y); - monitor->x = x; - monitor->y = y; + monitor->xdg_output_geometry.x = x; + monitor->xdg_output_geometry.y = y; } static void @@ -2471,8 +2493,8 @@ xdg_output_handle_logical_size (void *data, GDK_DEBUG (MISC, "handle logical size xdg-output %d, size %d %d", monitor->id, width, height); - monitor->width = width; - monitor->height = height; + monitor->xdg_output_geometry.width = width; + monitor->xdg_output_geometry.height = height; } static void @@ -2558,8 +2580,8 @@ output_handle_geometry (void *data, make, model, transform_to_string (transform)); - monitor->x = x; - monitor->y = y; + monitor->output_geometry.x = x; + monitor->output_geometry.y = y; switch (transform) { @@ -2603,28 +2625,12 @@ output_handle_scale (void *data, int32_t scale) { GdkWaylandMonitor *monitor = (GdkWaylandMonitor *)data; - GdkRectangle previous_geometry; - int previous_scale; - int width; - int height; GDK_DEBUG (MISC, "handle scale output %d, scale %d", monitor->id, scale); - gdk_monitor_get_geometry (GDK_MONITOR (monitor), &previous_geometry); - previous_scale = gdk_monitor_get_scale_factor (GDK_MONITOR (monitor)); - /* Set the scale from wl_output protocol, regardless of xdg-output support */ gdk_monitor_set_scale_factor (GDK_MONITOR (monitor), scale); - if (monitor_has_xdg_output (monitor)) - return; - - width = previous_geometry.width * previous_scale; - height = previous_geometry.height * previous_scale; - - monitor->width = width / scale; - monitor->height = height / scale; - if (should_update_monitor (monitor)) apply_monitor_change (monitor); } @@ -2638,7 +2644,6 @@ output_handle_mode (void *data, int refresh) { GdkWaylandMonitor *monitor = (GdkWaylandMonitor *)data; - int scale; GDK_DEBUG (MISC, "handle mode output %d, size %d %d, rate %d", monitor->id, width, height, refresh); @@ -2646,9 +2651,8 @@ output_handle_mode (void *data, if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) return; - scale = gdk_monitor_get_scale_factor (GDK_MONITOR (monitor)); - monitor->width = width / scale; - monitor->height = height / scale; + monitor->output_geometry.width = width; + monitor->output_geometry.height = height; gdk_monitor_set_refresh_rate (GDK_MONITOR (monitor), refresh); if (should_update_monitor (monitor) || !monitor_has_xdg_output (monitor)) diff --git a/gdk/wayland/gdkmonitor-wayland.h b/gdk/wayland/gdkmonitor-wayland.h index a9658cd8cc..a6950303df 100644 --- a/gdk/wayland/gdkmonitor-wayland.h +++ b/gdk/wayland/gdkmonitor-wayland.h @@ -31,11 +31,10 @@ struct _GdkWaylandMonitor { gboolean added; struct zxdg_output_v1 *xdg_output; - /* Size and position, can be either from wl_output or xdg_output */ - int32_t x; - int32_t y; - int32_t width; - int32_t height; + /* Raw wl_output data */ + GdkRectangle output_geometry; + /* Raw xdg_output data */ + GdkRectangle xdg_output_geometry; char *name; char *description; gboolean wl_output_done; From ea6ad66ee3fd862a755794f33fe8ed88063e87eb Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 30 Jan 2024 20:50:53 -0500 Subject: [PATCH 2/4] wayland: Small fixup to monitor size handling The first time this function is called, has_xdg_output() returns true, but haven't yet received all the xdg-output events, so wait for that to be done. Otherwise, the logical size is 0, and nothing useful comes from that. --- gdk/wayland/gdkdisplay-wayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/wayland/gdkdisplay-wayland.c b/gdk/wayland/gdkdisplay-wayland.c index 8133a7c0a9..3792d4ff43 100644 --- a/gdk/wayland/gdkdisplay-wayland.c +++ b/gdk/wayland/gdkdisplay-wayland.c @@ -2436,7 +2436,7 @@ apply_monitor_change (GdkWaylandMonitor *monitor) GdkRectangle logical_geometry; gboolean needs_scaling = FALSE; - if (monitor_has_xdg_output(monitor)) + if (monitor->xdg_output_done) { logical_geometry = monitor->xdg_output_geometry; needs_scaling = From 8df8d0bd5d50c872e13a2daad69ede3ea252466d Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 30 Jan 2024 20:08:01 -0500 Subject: [PATCH 3/4] monitor: Add gdk_monitor_get_scale This matches the surface api we have, and closes an api gap. --- gdk/gdkmonitor.c | 76 +++++++++++++++++++++++++++++++- gdk/gdkmonitor.h | 2 + gdk/gdkmonitorprivate.h | 6 ++- gdk/wayland/gdkdisplay-wayland.c | 20 ++++++--- 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/gdk/gdkmonitor.c b/gdk/gdkmonitor.c index b99408696e..685a5ad62f 100644 --- a/gdk/gdkmonitor.c +++ b/gdk/gdkmonitor.c @@ -26,6 +26,8 @@ #include "gdkenumtypes.h" #include "gdkrectangle.h" +#include + /** * GdkMonitor: * @@ -46,6 +48,7 @@ enum { PROP_MODEL, PROP_CONNECTOR, PROP_SCALE_FACTOR, + PROP_SCALE, PROP_GEOMETRY, PROP_WIDTH_MM, PROP_HEIGHT_MM, @@ -70,6 +73,8 @@ static void gdk_monitor_init (GdkMonitor *monitor) { monitor->scale_factor = 1; + monitor->scale = 1.0; + monitor->scale_set = FALSE; monitor->valid = TRUE; } @@ -107,6 +112,10 @@ gdk_monitor_get_property (GObject *object, g_value_set_int (value, monitor->scale_factor); break; + case PROP_SCALE: + g_value_set_double (value, monitor->scale); + break; + case PROP_GEOMETRY: g_value_set_boxed (value, &monitor->geometry); break; @@ -233,13 +242,28 @@ gdk_monitor_class_init (GdkMonitorClass *class) * GdkMonitor:scale-factor: (attributes org.gtk.Property.get=gdk_monitor_get_scale_factor) * * The scale factor. + * + * The scale factor is the next larger integer, + * compared to [property@Gdk.Surface:scale]. */ props[PROP_SCALE_FACTOR] = g_param_spec_int ("scale-factor", NULL, NULL, - 0, G_MAXINT, + 1, G_MAXINT, 1, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + /** + * GdkMonitor:scale: (attributes org.gtk.Property.get=gdk_monitor_get_scale) + * + * The scale of the monitor. + * + * Since: 4.14 + */ + props[PROP_SCALE] = + g_param_spec_double ("scale", NULL, NULL, + 1., G_MAXDOUBLE, 1., + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + /** * GdkMonitor:geometry: (attributes org.gtk.Property.get=gdk_monitor_get_geometry) * @@ -346,7 +370,7 @@ gdk_monitor_get_display (GdkMonitor *monitor) * display coordinate space. * * The returned geometry is in ”application pixels”, not in - * ”device pixels” (see [method@Gdk.Monitor.get_scale_factor]). + * ”device pixels” (see [method@Gdk.Monitor.get_scale]). */ void gdk_monitor_get_geometry (GdkMonitor *monitor, @@ -472,6 +496,29 @@ gdk_monitor_get_scale_factor (GdkMonitor *monitor) return monitor->scale_factor; } +/** + * gdk_monitor_get_scale: (attributes org.gtk.Method.get_property=scale) + * @monitor: a `GdkMonitor` + * + * Gets the internal scale factor that maps from monitor coordinates + * to device pixels. + * + * This can be used if you want to create pixel based data for a + * particular monitor, but most of the time you’re drawing to a surface + * where it is better to use [method@Gdk.Surface.get_scale] instead. + * + * Returns: the scale + * + * Since: 4.14 + */ +double +gdk_monitor_get_scale (GdkMonitor *monitor) +{ + g_return_val_if_fail (GDK_IS_MONITOR (monitor), 1.); + + return monitor->scale; +} + /** * gdk_monitor_get_refresh_rate: (attributes org.gtk.Method.get_property=refresh-rate) * @monitor: a `GdkMonitor` @@ -583,12 +630,37 @@ void gdk_monitor_set_scale_factor (GdkMonitor *monitor, int scale_factor) { + g_return_if_fail (scale_factor >= 1); + + if (monitor->scale_set) + return; + if (monitor->scale_factor == scale_factor) return; monitor->scale_factor = scale_factor; + monitor->scale = scale_factor; g_object_notify (G_OBJECT (monitor), "scale-factor"); + g_object_notify (G_OBJECT (monitor), "scale"); +} + +void +gdk_monitor_set_scale (GdkMonitor *monitor, + double scale) +{ + g_return_if_fail (scale >= 1.); + + monitor->scale_set = TRUE; + + if (monitor->scale == scale) + return; + + monitor->scale = scale; + monitor->scale_factor = (int) ceil (scale); + + g_object_notify (G_OBJECT (monitor), "scale"); + g_object_notify (G_OBJECT (monitor), "scale-factor"); } void diff --git a/gdk/gdkmonitor.h b/gdk/gdkmonitor.h index 1cdb4f9a3e..6d99c6d2bc 100644 --- a/gdk/gdkmonitor.h +++ b/gdk/gdkmonitor.h @@ -77,6 +77,8 @@ GDK_AVAILABLE_IN_ALL const char * gdk_monitor_get_connector (GdkMonitor *monitor); GDK_AVAILABLE_IN_ALL int gdk_monitor_get_scale_factor (GdkMonitor *monitor); +GDK_AVAILABLE_IN_4_14 +double gdk_monitor_get_scale (GdkMonitor *monitor); GDK_AVAILABLE_IN_ALL int gdk_monitor_get_refresh_rate (GdkMonitor *monitor); GDK_AVAILABLE_IN_ALL diff --git a/gdk/gdkmonitorprivate.h b/gdk/gdkmonitorprivate.h index 2a48ae1e28..a696fbce3c 100644 --- a/gdk/gdkmonitorprivate.h +++ b/gdk/gdkmonitorprivate.h @@ -44,6 +44,8 @@ struct _GdkMonitor { int refresh_rate; GdkSubpixelLayout subpixel_layout; gboolean valid; + double scale; + gboolean scale_set; }; struct _GdkMonitorClass { @@ -64,7 +66,9 @@ void gdk_monitor_set_physical_size (GdkMonitor *monitor, int width_mm, int height_mm); void gdk_monitor_set_scale_factor (GdkMonitor *monitor, - int scale); + int scale_factor); +void gdk_monitor_set_scale (GdkMonitor *monitor, + double scale); void gdk_monitor_set_refresh_rate (GdkMonitor *monitor, int refresh_rate); void gdk_monitor_set_subpixel_layout (GdkMonitor *monitor, diff --git a/gdk/wayland/gdkdisplay-wayland.c b/gdk/wayland/gdkdisplay-wayland.c index 3792d4ff43..b767359e10 100644 --- a/gdk/wayland/gdkdisplay-wayland.c +++ b/gdk/wayland/gdkdisplay-wayland.c @@ -2435,6 +2435,7 @@ apply_monitor_change (GdkWaylandMonitor *monitor) GdkRectangle logical_geometry; gboolean needs_scaling = FALSE; + double scale; if (monitor->xdg_output_done) { @@ -2451,16 +2452,25 @@ apply_monitor_change (GdkWaylandMonitor *monitor) if (needs_scaling) { - int scale = gdk_monitor_get_scale_factor (GDK_MONITOR (monitor)); - logical_geometry.y /= scale; - logical_geometry.x /= scale; - logical_geometry.width /= scale; - logical_geometry.height /= scale; + int scale_factor = gdk_monitor_get_scale_factor (GDK_MONITOR (monitor)); + logical_geometry.y /= scale_factor; + logical_geometry.x /= scale_factor; + logical_geometry.width /= scale_factor; + logical_geometry.height /= scale_factor; + + scale = scale_factor; + } + else + { + scale = MAX (monitor->output_geometry.width / (double) logical_geometry.width, + monitor->output_geometry.height / (double) logical_geometry.height); } gdk_monitor_set_geometry (GDK_MONITOR (monitor), &logical_geometry); gdk_monitor_set_connector (GDK_MONITOR (monitor), monitor->name); gdk_monitor_set_description (GDK_MONITOR (monitor), monitor->description); + gdk_monitor_set_scale (GDK_MONITOR (monitor), scale); + monitor->wl_output_done = FALSE; monitor->xdg_output_done = FALSE; From f80989332f20249fa020ed738ab943fcccd06957 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 30 Jan 2024 20:08:44 -0500 Subject: [PATCH 4/4] inspector: Better monitor information Show the fractional scale if we have one, and pixels. Only Wayland has fractional scales, and for monitors, we have to derive it from the logical and physical resolution of the monitor. Not ideal, but it works. --- gtk/inspector/general.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/gtk/inspector/general.c b/gtk/inspector/general.c index c485dd4388..c5b218c4c9 100644 --- a/gtk/inspector/general.c +++ b/gtk/inspector/general.c @@ -730,7 +730,7 @@ add_monitor (GtkInspectorGeneral *gen, GtkListBox *list; char *value; GdkRectangle rect; - int scale; + double scale; char *name; char *scale_str = NULL; const char *manufacturer; @@ -753,9 +753,9 @@ add_monitor (GtkInspectorGeneral *gen, add_label_row (gen, list, "Connector", gdk_monitor_get_connector (monitor), 10); gdk_monitor_get_geometry (monitor, &rect); - scale = gdk_monitor_get_scale_factor (monitor); - if (scale != 1) - scale_str = g_strdup_printf (" @ %d", scale); + scale = gdk_monitor_get_scale (monitor); + if (scale != 1.0) + scale_str = g_strdup_printf (" @ %.2f", scale); value = g_strdup_printf ("%d × %d%s at %d, %d", rect.width, rect.height, @@ -765,6 +765,12 @@ add_monitor (GtkInspectorGeneral *gen, g_free (value); g_free (scale_str); + value = g_strdup_printf ("%d × %d", + (int) (rect.width * scale), + (int) (rect.height * scale)); + add_label_row (gen, list, "Pixels", value, 10); + g_free (value); + value = g_strdup_printf ("%d × %d mm²", gdk_monitor_get_width_mm (monitor), gdk_monitor_get_height_mm (monitor));