From af3f34ca942b68343d9baf0963f7e29013ef129f Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Feb 2022 16:43:14 -0800 Subject: [PATCH 01/56] macos: create new windows with slight origin offset When creating new windows, it is better if we create them with a slight offset to where they were created before so that they are visible to the user separately from what they might be overshadowing. --- gdk/macos/gdkmacosdisplay-wm.c | 41 ++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-wm.c b/gdk/macos/gdkmacosdisplay-wm.c index a0dd6dddae..7b508a4a9e 100644 --- a/gdk/macos/gdkmacosdisplay-wm.c +++ b/gdk/macos/gdkmacosdisplay-wm.c @@ -24,6 +24,9 @@ #include "gdkmacossurface-private.h" #include "gdkmacostoplevelsurface-private.h" +#define WARP_OFFSET_X 15 +#define WARP_OFFSET_Y 15 + static void _gdk_macos_display_position_toplevel_with_parent (GdkMacosDisplay *self, GdkMacosSurface *surface, @@ -79,6 +82,22 @@ _gdk_macos_display_position_toplevel_with_parent (GdkMacosDisplay *self, *y = surface_rect.y - surface->shadow_top; } +static inline gboolean +has_surface_at_origin (const GList *surfaces, + int x, + int y) +{ + for (const GList *iter = surfaces; iter; iter = iter->next) + { + GdkMacosSurface *surface = iter->data; + + if (surface->root_x == x && surface->root_y == y) + return TRUE; + } + + return FALSE; +} + static void _gdk_macos_display_position_toplevel (GdkMacosDisplay *self, GdkMacosSurface *surface, @@ -87,6 +106,7 @@ _gdk_macos_display_position_toplevel (GdkMacosDisplay *self, { cairo_rectangle_int_t surface_rect; GdkRectangle workarea; + const GList *surfaces; GdkMonitor *monitor; CGPoint mouse; @@ -109,10 +129,27 @@ _gdk_macos_display_position_toplevel (GdkMacosDisplay *self, if (surface_rect.y < workarea.y) surface_rect.y = workarea.y; - /* TODO: If there is another window at this same position, perhaps we should move it */ - *x = surface_rect.x - surface->shadow_left; *y = surface_rect.y - surface->shadow_top; + + /* Try to see if there are any other surfaces at this origin and if so, + * adjust until we get something better. + */ + surfaces = _gdk_macos_display_get_surfaces (self); + while (has_surface_at_origin (surfaces, *x, *y)) + { + *x += WARP_OFFSET_X; + *y += WARP_OFFSET_Y; + + /* If we reached the bottom right, just bail and try the workspace origin */ + if (*x + surface->shadow_left + WARP_OFFSET_X > workarea.x + workarea.width || + *y + surface->shadow_top + WARP_OFFSET_Y > workarea.y + workarea.height) + { + *x = workarea.x - surface->shadow_left; + *y = workarea.y - surface->shadow_top; + return; + } + } } /* From f58ece72cf95e30605ee35eea18405a849a17fdf Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Feb 2022 16:44:22 -0800 Subject: [PATCH 02/56] macos: remove unused code --- gdk/macos/gdkmacossurface-private.h | 1 - 1 file changed, 1 deletion(-) diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h index 2abc199698..08947f1ce3 100644 --- a/gdk/macos/gdkmacossurface-private.h +++ b/gdk/macos/gdkmacossurface-private.h @@ -116,7 +116,6 @@ void _gdk_macos_surface_resize (GdkMacosSurface int width, int height); void _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self); -void _gdk_macos_surface_update_position (GdkMacosSurface *self); void _gdk_macos_surface_show (GdkMacosSurface *self); void _gdk_macos_surface_publish_timings (GdkMacosSurface *self, gint64 predicted_presentation_time, From 278f976add33d1e1d3ae6c0d0efb67b9daaed125 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Feb 2022 18:55:42 -0800 Subject: [PATCH 03/56] macos: fix kinetic scrolling with overshoot Previously we had issues on macos where the overshoot would keep showing. To fix this we need to actually use discrete events instead of the generated deltas from macOS in the scroll wheel case. Additionally, we need to drop the kinetic momentum events from macOS and rely on the gtk kinetic events which are already happening anyway. We also need to submit the is_stop event for the GDK_SCROLL_SMOOTH case when we detect it. To keep the discrete scroll events correct, we need to alter the hack in gtkscrolledwindow.c to use the same path as other platforms except for when a smooth scroll event is in place. In the future, I would imagine that this falls into the boundary of high-precision scrolling and would share the same code paths as other platforms. With all of these in place, kinetic scrolling with overshoot appears the same on macOS as other platforms. --- gdk/macos/gdkmacosdisplay-translate.c | 43 ++++++++++++++++++++------- gtk/gtkscrolledwindow.c | 29 +++++++++++------- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index 8e3cd90b82..646ba0cd9e 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -612,6 +612,8 @@ fill_scroll_event (GdkMacosDisplay *self, GdkModifierType state; GdkDevice *pointer; GdkEvent *ret = NULL; + NSEventPhase phase; + NSEventPhase momentumPhase; GdkSeat *seat; double dx; double dy; @@ -619,6 +621,15 @@ fill_scroll_event (GdkMacosDisplay *self, g_assert (GDK_IS_MACOS_SURFACE (surface)); g_assert (nsevent != NULL); + phase = [nsevent phase]; + momentumPhase = [nsevent momentumPhase]; + + /* Ignore kinetic scroll events from the display server as we already + * handle those internally. + */ + if (phase == 0 && momentumPhase != 0) + return NULL; + seat = gdk_display_get_default_seat (GDK_DISPLAY (self)); pointer = gdk_seat_get_pointer (seat); state = _gdk_macos_display_get_current_mouse_modifiers (self) | @@ -684,19 +695,31 @@ fill_scroll_event (GdkMacosDisplay *self, } else { - g_assert (ret == NULL); - - ret = gdk_scroll_event_new (GDK_SURFACE (surface), - pointer, - NULL, - get_time_from_ns_event (nsevent), - state, - -dx * 32, - -dy * 32, - FALSE); + ret = gdk_scroll_event_new_discrete (GDK_SURFACE (surface), + pointer, + NULL, + get_time_from_ns_event (nsevent), + state, + direction, + FALSE); } } + if (phase == NSEventPhaseEnded || phase == NSEventPhaseCancelled) + { + /* The user must have released their fingers in a touchpad + * scroll, so try to send a scroll is_stop event. + */ + if (ret != NULL) + _gdk_event_queue_append (GDK_DISPLAY (self), g_steal_pointer (&ret)); + ret = gdk_scroll_event_new (GDK_SURFACE (surface), + pointer, + NULL, + get_time_from_ns_event (nsevent), + state, + 0.0, 0.0, TRUE); + } + return g_steal_pointer (&ret); } diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c index ee8c2b0ba4..4879fee68d 100644 --- a/gtk/gtkscrolledwindow.c +++ b/gtk/gtkscrolledwindow.c @@ -1226,16 +1226,15 @@ check_update_scrollbar_proximity (GtkScrolledWindow *sw, } static double -get_scroll_unit (GtkScrolledWindow *sw, - GtkOrientation orientation) +get_scroll_unit (GtkScrolledWindow *sw, + GtkOrientation orientation, + GtkEventControllerScroll *scroll) { - double scroll_unit; - -#ifndef GDK_WINDOWING_MACOS GtkScrolledWindowPrivate *priv = gtk_scrolled_window_get_instance_private (sw); GtkScrollbar *scrollbar; GtkAdjustment *adj; double page_size; + double scroll_unit; if (orientation == GTK_ORIENTATION_HORIZONTAL) scrollbar = GTK_SCROLLBAR (priv->hscrollbar); @@ -1248,8 +1247,16 @@ get_scroll_unit (GtkScrolledWindow *sw, adj = gtk_scrollbar_get_adjustment (scrollbar); page_size = gtk_adjustment_get_page_size (adj); scroll_unit = pow (page_size, 2.0 / 3.0); -#else - scroll_unit = 1; + +#ifdef GDK_WINDOWING_MACOS + { + GdkEvent *event = gtk_event_controller_get_current_event (GTK_EVENT_CONTROLLER (scroll)); + + if (event != NULL && + gdk_event_get_event_type (event) == GDK_SCROLL && + gdk_scroll_event_get_direction (event) == GDK_SCROLL_SMOOTH) + scroll_unit = 1; + } #endif return scroll_unit; @@ -1397,7 +1404,7 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window, double scroll_unit; adj = gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (priv->hscrollbar)); - scroll_unit = get_scroll_unit (scrolled_window, GTK_ORIENTATION_HORIZONTAL); + scroll_unit = get_scroll_unit (scrolled_window, GTK_ORIENTATION_HORIZONTAL, scroll); new_value = priv->unclamped_hadj_value + delta_x * scroll_unit; _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj, @@ -1412,7 +1419,7 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window, double scroll_unit; adj = gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (priv->vscrollbar)); - scroll_unit = get_scroll_unit (scrolled_window, GTK_ORIENTATION_VERTICAL); + scroll_unit = get_scroll_unit (scrolled_window, GTK_ORIENTATION_VERTICAL, scroll); new_value = priv->unclamped_vadj_value + delta_y * scroll_unit; _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj, @@ -1470,8 +1477,8 @@ scroll_controller_decelerate (GtkEventControllerScroll *scroll, shifted = (state & GDK_SHIFT_MASK) != 0; - unit_x = get_scroll_unit (scrolled_window, GTK_ORIENTATION_HORIZONTAL); - unit_y = get_scroll_unit (scrolled_window, GTK_ORIENTATION_VERTICAL); + unit_x = get_scroll_unit (scrolled_window, GTK_ORIENTATION_HORIZONTAL, scroll); + unit_y = get_scroll_unit (scrolled_window, GTK_ORIENTATION_VERTICAL, scroll); if (shifted) { From bad392eb2adfd21f93285f6609d1e0445c9f1839 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Feb 2022 22:31:42 -0800 Subject: [PATCH 04/56] macos: restore unfullscreen frame with style mask This doesn't give us appropriate results if we use the window delegate. Instead, we need to adjust the frame at the same time we change the style mask so that we end up in the same location. --- gdk/macos/GdkMacosWindow.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index f879decbc9..3ed60cb049 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -668,7 +668,12 @@ typedef NSString *CALayerContentsGravity; is_opaque = (([self styleMask] & NSWindowStyleMaskTitled) != 0); if (was_fullscreen != is_fullscreen) - _gdk_macos_surface_update_fullscreen_state (gdk_surface); + { + if (was_fullscreen) + [self setFrame:lastUnfullscreenFrame display:NO]; + + _gdk_macos_surface_update_fullscreen_state (gdk_surface); + } if (was_opaque != is_opaque) { @@ -753,7 +758,6 @@ typedef NSString *CALayerContentsGravity; -(void)windowWillExitFullScreen:(NSNotification *)aNotification { - [self setFrame:lastUnfullscreenFrame display:NO]; } -(void)windowDidExitFullScreen:(NSNotification *)aNotification From 5b15bc86a3c077133290d1e6b1215d1ef821e0f6 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 24 Feb 2022 23:27:09 -0800 Subject: [PATCH 05/56] macos: fix origin during live resize of titled window When using server-side-decorations, we need to avoid potential cycles with compute-size as it may not have the new sizing information yet. We can just short circuit during "live resize" to get that effect. Fixes poor window resizing from top-left on titled windows. --- gdk/macos/gdkmacostoplevelsurface.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacostoplevelsurface.c b/gdk/macos/gdkmacostoplevelsurface.c index 27289787ca..c84ebb704b 100644 --- a/gdk/macos/gdkmacostoplevelsurface.c +++ b/gdk/macos/gdkmacostoplevelsurface.c @@ -421,7 +421,8 @@ _gdk_macos_toplevel_surface_compute_size (GdkSurface *surface) GDK_TOPLEVEL_STATE_RIGHT_TILED | GDK_TOPLEVEL_STATE_BOTTOM_TILED | GDK_TOPLEVEL_STATE_LEFT_TILED | - GDK_TOPLEVEL_STATE_MINIMIZED)) + GDK_TOPLEVEL_STATE_MINIMIZED) || + [macos_surface->window inLiveResize]) return FALSE; /* If we delayed a user resize until the beginning of the frame, From 676e9ab127902a2b7c3fd85c07b638f4429f8223 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 12:30:08 -0800 Subject: [PATCH 06/56] macos: move feedback mechanisms into separate file We will eventually be needing additional feedback from the display server which would be nice to keep away from the rest of GdkMacosDisplay for cleanliness sake. Particularly for feedback from mission control and other environment factors that requires private API for proper integration. --- gdk/macos/gdkmacosdisplay-feedback.c | 105 +++++++++++++++++++++++++++ gdk/macos/gdkmacosdisplay-private.h | 2 + gdk/macos/gdkmacosdisplay.c | 67 +---------------- gdk/macos/meson.build | 1 + 4 files changed, 111 insertions(+), 64 deletions(-) create mode 100644 gdk/macos/gdkmacosdisplay-feedback.c diff --git a/gdk/macos/gdkmacosdisplay-feedback.c b/gdk/macos/gdkmacosdisplay-feedback.c new file mode 100644 index 0000000000..868ac0fbcd --- /dev/null +++ b/gdk/macos/gdkmacosdisplay-feedback.c @@ -0,0 +1,105 @@ +/* + * Copyright © 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include + +#include "gdkmacosdisplay-private.h" +#include "gdkmacossurface-private.h" + +static void +gdk_macos_display_user_defaults_changed_cb (CFNotificationCenterRef center, + void *observer, + CFStringRef name, + const void *object, + CFDictionaryRef userInfo) +{ + GdkMacosDisplay *self = observer; + + g_assert (GDK_IS_MACOS_DISPLAY (self)); + + _gdk_macos_display_reload_settings (self); +} + +static void +gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef center, + void *observer, + CFStringRef name, + const void *object, + CFDictionaryRef userInfo) +{ + GdkMacosDisplay *self = observer; + + g_assert (GDK_IS_MACOS_DISPLAY (self)); + + _gdk_macos_display_reload_monitors (self); + + /* Now we need to update all our surface positions since they + * probably just changed origins. + */ + for (const GList *iter = _gdk_macos_display_get_surfaces (self); + iter != NULL; + iter = iter->next) + { + GdkMacosSurface *surface = iter->data; + + g_assert (GDK_IS_MACOS_SURFACE (surface)); + + _gdk_macos_surface_monitor_changed (surface); + } +} + + +void +_gdk_macos_display_feedback_init (GdkMacosDisplay *self) +{ + g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); + + CFNotificationCenterAddObserver (CFNotificationCenterGetLocalCenter (), + self, + gdk_macos_display_monitors_changed_cb, + CFSTR ("NSApplicationDidChangeScreenParametersNotification"), + NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); + + CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (), + self, + gdk_macos_display_user_defaults_changed_cb, + CFSTR ("NSUserDefaultsDidChangeNotification"), + NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); +} + +void +_gdk_macos_display_feedback_destroy (GdkMacosDisplay *self) +{ + g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); + + CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (), + self, + CFSTR ("NSApplicationDidChangeScreenParametersNotification"), + NULL); + + CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (), + self, + CFSTR ("NSUserDefaultsDidChangeNotification"), + NULL); + +} diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h index be2290b89a..b9f33fe20e 100644 --- a/gdk/macos/gdkmacosdisplay-private.h +++ b/gdk/macos/gdkmacosdisplay-private.h @@ -124,6 +124,8 @@ GdkMonitor *_gdk_macos_display_get_monitor_at_display_coords (GdkMacosDisp int y); GdkEvent *_gdk_macos_display_translate (GdkMacosDisplay *self, NSEvent *event); +void _gdk_macos_display_feedback_init (GdkMacosDisplay *self); +void _gdk_macos_display_feedback_destroy (GdkMacosDisplay *self); void _gdk_macos_display_break_all_grabs (GdkMacosDisplay *self, guint32 time); GdkModifierType _gdk_macos_display_get_current_keyboard_modifiers (GdkMacosDisplay *self); diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index 74095504a2..bc44eb3e93 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -159,48 +159,6 @@ gdk_macos_display_update_bounds (GdkMacosDisplay *self) GDK_END_MACOS_ALLOC_POOL; } -static void -gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef center, - void *observer, - CFStringRef name, - const void *object, - CFDictionaryRef userInfo) -{ - GdkMacosDisplay *self = observer; - - g_assert (GDK_IS_MACOS_DISPLAY (self)); - - _gdk_macos_display_reload_monitors (self); - - /* Now we need to update all our surface positions since they - * probably just changed origins. - */ - for (const GList *iter = _gdk_macos_display_get_surfaces (self); - iter != NULL; - iter = iter->next) - { - GdkMacosSurface *surface = iter->data; - - g_assert (GDK_IS_MACOS_SURFACE (surface)); - - _gdk_macos_surface_monitor_changed (surface); - } -} - -static void -gdk_macos_display_user_defaults_changed_cb (CFNotificationCenterRef center, - void *observer, - CFStringRef name, - const void *object, - CFDictionaryRef userInfo) -{ - GdkMacosDisplay *self = observer; - - g_assert (GDK_IS_MACOS_DISPLAY (self)); - - _gdk_macos_display_reload_settings (self); -} - void _gdk_macos_display_reload_monitors (GdkMacosDisplay *self) { @@ -686,15 +644,7 @@ gdk_macos_display_finalize (GObject *object) { GdkMacosDisplay *self = (GdkMacosDisplay *)object; - CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (), - self, - CFSTR ("NSApplicationDidChangeScreenParametersNotification"), - NULL); - - CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (), - self, - CFSTR ("NSUserDefaultsDidChangeNotification"), - NULL); + _gdk_macos_display_feedback_destroy (self); g_clear_pointer (&self->active_drags, g_hash_table_unref); g_clear_pointer (&self->active_drops, g_hash_table_unref); @@ -779,19 +729,8 @@ _gdk_macos_display_open (const char *display_name) gdk_macos_display_load_display_link (self); _gdk_macos_display_reload_monitors (self); - CFNotificationCenterAddObserver (CFNotificationCenterGetLocalCenter (), - self, - gdk_macos_display_monitors_changed_cb, - CFSTR ("NSApplicationDidChangeScreenParametersNotification"), - NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); - - CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (), - self, - gdk_macos_display_user_defaults_changed_cb, - CFSTR ("NSUserDefaultsDidChangeNotification"), - NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); + /* Initialize feedback from display server */ + _gdk_macos_display_feedback_init (self); if (event_source == NULL) { diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build index d17a60ac09..bd7bbb5324 100644 --- a/gdk/macos/meson.build +++ b/gdk/macos/meson.build @@ -8,6 +8,7 @@ gdk_macos_sources = files([ 'gdkmacoscursor.c', 'gdkmacosdevice.c', 'gdkmacosdisplay.c', + 'gdkmacosdisplay-feedback.c', 'gdkmacosdisplay-settings.c', 'gdkmacosdisplay-translate.c', 'gdkmacosdisplay-wm.c', From 9767b3a97eed093380d95dcd1152899c74a7f40f Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 13:09:31 -0800 Subject: [PATCH 07/56] macos: use display id when creating CVDisplayLink Currently we're using a display link that is for all active displays which is just the display server trying to find some timings that try to overlap as many as possible. That was fine for a prototype, but we really need to do better for situations with mixed frame rate (such as 60hz and 120hz promotion displays). Additionally, the 144hz external monitor I have will never reach 144hz using the current design. This is just the first step in changing this, but the goal is to have one of these attached to each GdkMacosMonitor which we can then use to thaw surfaces specific to that monitor. --- gdk/macos/gdkdisplaylinksource.c | 15 +++++++-------- gdk/macos/gdkdisplaylinksource.h | 15 ++++++++------- gdk/macos/gdkmacosdisplay.c | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/gdk/macos/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c index 292b8be519..8b2f1a013f 100644 --- a/gdk/macos/gdkdisplaylinksource.c +++ b/gdk/macos/gdkdisplaylinksource.c @@ -147,6 +147,7 @@ gdk_display_link_source_frame_cb (CVDisplayLinkRef display_link, /** * gdk_display_link_source_new: + * @display_id: the identifier of the monitor * * Creates a new `GSource` that will activate the dispatch function upon * notification from a CVDisplayLink that a new frame should be drawn. @@ -159,7 +160,7 @@ gdk_display_link_source_frame_cb (CVDisplayLinkRef display_link, * Returns: (transfer full): A newly created `GSource` */ GSource * -gdk_display_link_source_new (void) +gdk_display_link_source_new (CGDirectDisplayID display_id) { GdkDisplayLinkSource *impl; GSource *source; @@ -168,15 +169,13 @@ gdk_display_link_source_new (void) source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl); impl = (GdkDisplayLinkSource *)source; + impl->display_id = display_id; - /* - * Create our link based on currently connected displays. - * If there are multiple displays, this will be something that tries - * to work for all of them. In the future, we may want to explore multiple - * links based on the connected displays. + /* Create DisplayLink for timing information for the display in + * question so that we can produce graphics for that display at whatever + * rate it can provide. */ - ret = CVDisplayLinkCreateWithActiveCGDisplays (&impl->display_link); - if (ret != kCVReturnSuccess) + if (CVDisplayLinkCreateWithCGDisplay (display_id, &impl->display_link) != kCVReturnSuccess) { g_warning ("Failed to initialize CVDisplayLink!"); return source; diff --git a/gdk/macos/gdkdisplaylinksource.h b/gdk/macos/gdkdisplaylinksource.h index ed769b59f8..bff1c91475 100644 --- a/gdk/macos/gdkdisplaylinksource.h +++ b/gdk/macos/gdkdisplaylinksource.h @@ -30,17 +30,18 @@ G_BEGIN_DECLS typedef struct { - GSource source; + GSource source; - CVDisplayLinkRef display_link; - gint64 refresh_interval; - guint refresh_rate; + CGDirectDisplayID display_id; + CVDisplayLinkRef display_link; + gint64 refresh_interval; + guint refresh_rate; - volatile gint64 presentation_time; - volatile guint needs_dispatch; + volatile gint64 presentation_time; + volatile guint needs_dispatch; } GdkDisplayLinkSource; -GSource *gdk_display_link_source_new (void); +GSource *gdk_display_link_source_new (CGDirectDisplayID display_id); void gdk_display_link_source_pause (GdkDisplayLinkSource *source); void gdk_display_link_source_unpause (GdkDisplayLinkSource *source); diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index bc44eb3e93..5501a16318 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -273,7 +273,7 @@ gdk_macos_display_frame_cb (gpointer data) static void gdk_macos_display_load_display_link (GdkMacosDisplay *self) { - self->frame_source = gdk_display_link_source_new (); + self->frame_source = gdk_display_link_source_new (CGMainDisplayID ()); g_source_set_callback (self->frame_source, gdk_macos_display_frame_cb, self, From 94deb551aa572e35b8e1316b98b20f6f549fe941 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 13:14:22 -0800 Subject: [PATCH 08/56] macos: remove duplicated opaque_region field This can be relied upon from GdkSurface and we do not need to keep a copy of it. Just remove it and use the GdkSurface.opaque_region field. --- gdk/macos/gdkmacossurface-private.h | 1 - gdk/macos/gdkmacossurface.c | 15 +++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h index 08947f1ce3..b853211afc 100644 --- a/gdk/macos/gdkmacossurface-private.h +++ b/gdk/macos/gdkmacossurface-private.h @@ -49,7 +49,6 @@ struct _GdkMacosSurface GdkMacosBuffer *buffer; GdkMacosBuffer *front; GPtrArray *monitors; - cairo_region_t *opaque_region; char *title; int root_x; diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 5998c4162f..e437357a96 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -115,12 +115,6 @@ gdk_macos_surface_set_opaque_region (GdkSurface *surface, g_assert (GDK_IS_MACOS_SURFACE (self)); - if (region != self->opaque_region) - { - g_clear_pointer (&self->opaque_region, cairo_region_destroy); - self->opaque_region = cairo_region_copy (region); - } - if ((nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface)))) [(GdkMacosView *)nsview setOpaqueRegion:region]; } @@ -417,7 +411,6 @@ gdk_macos_surface_destroy (GdkSurface *surface, } g_clear_pointer (&self->title, g_free); - g_clear_pointer (&self->opaque_region, cairo_region_destroy); if (window != NULL) [window close]; @@ -619,14 +612,16 @@ _gdk_macos_surface_get_shadow (GdkMacosSurface *self, gboolean _gdk_macos_surface_is_opaque (GdkMacosSurface *self) { + GdkSurface *surface = (GdkSurface *)self; + g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), FALSE); - if (self->opaque_region != NULL && - cairo_region_num_rectangles (self->opaque_region) == 1) + if (surface->opaque_region != NULL && + cairo_region_num_rectangles (surface->opaque_region) == 1) { cairo_rectangle_int_t extents; - cairo_region_get_extents (self->opaque_region, &extents); + cairo_region_get_extents (surface->opaque_region, &extents); return (extents.x == 0 && extents.y == 0 && From 433de2849d7d770a101a31eef67fab5388d3ad11 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 13:52:08 -0800 Subject: [PATCH 09/56] macos: calculate best monitor when changing screens When we change screens, we can keep track of the best monitor so that we can use it to register CVDisplayLink callbacks. --- gdk/macos/gdkmacossurface-private.h | 1 + gdk/macos/gdkmacossurface.c | 54 ++++++++++++++--------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h index b853211afc..5ac4201a42 100644 --- a/gdk/macos/gdkmacossurface-private.h +++ b/gdk/macos/gdkmacossurface-private.h @@ -49,6 +49,7 @@ struct _GdkMacosSurface GdkMacosBuffer *buffer; GdkMacosBuffer *front; GPtrArray *monitors; + GdkMonitor *best_monitor; char *title; int root_x; diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index e437357a96..bdf5335ef5 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -400,6 +400,8 @@ gdk_macos_surface_destroy (GdkSurface *surface, GdkMacosWindow *window = g_steal_pointer (&self->window); GdkFrameClock *frame_clock; + g_clear_object (&self->best_monitor); + if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self)))) { g_signal_handlers_disconnect_by_func (frame_clock, @@ -985,11 +987,13 @@ void _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) { GListModel *monitors; + GdkMonitor *best = NULL; GdkRectangle rect; GdkRectangle intersect; GdkDisplay *display; GdkMonitor *monitor; guint n_monitors; + int best_area = 0; g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); @@ -1032,6 +1036,28 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) g_clear_object (&self->buffer); g_clear_object (&self->front); + /* Determine the best-fit monitor */ + for (guint i = 0; i < self->monitors->len; i++) + { + monitor = g_ptr_array_index (self->monitors, i); + + if (gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect)) + { + int area = intersect.width * intersect.height; + + if (area > best_area) + { + best_area = area; + best = monitor; + } + } + } + + if (g_set_object (&self->best_monitor, best)) + { + /* TODO: change frame clock to new monitor */ + } + _gdk_macos_surface_configure (self); gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL); @@ -1040,35 +1066,9 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) GdkMonitor * _gdk_macos_surface_get_best_monitor (GdkMacosSurface *self) { - GdkMonitor *best = NULL; - GdkRectangle rect; - int best_area = 0; - g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL); - rect.x = self->root_x; - rect.y = self->root_y; - rect.width = GDK_SURFACE (self)->width; - rect.height = GDK_SURFACE (self)->height; - - for (guint i = 0; i < self->monitors->len; i++) - { - GdkMonitor *monitor = g_ptr_array_index (self->monitors, i); - GdkRectangle intersect; - - if (gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect)) - { - int area = intersect.width * intersect.height; - - if (area > best_area) - { - best = monitor; - best_area = area; - } - } - } - - return best; + return self->best_monitor; } NSView * From 48b408e2c320a5dd7de578a0a8febdb14cab733e Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 14:02:06 -0800 Subject: [PATCH 10/56] macos: do not inherit parents frame clock Windows can end up on different monitors despite having a parent or transient-for ancestor. We want them to be driven by the CVDisplayLink for the best-monitor, and so this needs to be unshared. --- gdk/macos/gdkmacossurface.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index bdf5335ef5..cf9d7067f8 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -553,10 +553,7 @@ _gdk_macos_surface_new (GdkMacosDisplay *display, g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL); - if (parent != NULL) - frame_clock = g_object_ref (gdk_surface_get_frame_clock (parent)); - else - frame_clock = _gdk_frame_clock_idle_new (); + frame_clock = _gdk_frame_clock_idle_new (); switch (surface_type) { From ccf18c239dd402c879dc078ccbb80ade72107d01 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 23:22:05 -0800 Subject: [PATCH 11/56] macos: remove emulated scroll events We don't appear to actually need the emulated scroll events and they get in the way of proper scrolling with the touchpad. Fixes #4734 --- gdk/macos/gdkmacosdisplay-translate.c | 32 ++++++++------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index 646ba0cd9e..db1cfa2527 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -678,31 +678,17 @@ fill_scroll_event (GdkMacosDisplay *self, dy = 0.0; } - if (dx != 0.0 || dy != 0.0) + if ((dx != 0.0 || dy != 0.0) && ![nsevent hasPreciseScrollingDeltas]) { - if ([nsevent hasPreciseScrollingDeltas]) - { - GdkEvent *emulated; + g_assert (ret == NULL); - emulated = gdk_scroll_event_new_discrete (GDK_SURFACE (surface), - pointer, - NULL, - get_time_from_ns_event (nsevent), - state, - direction, - TRUE); - _gdk_event_queue_append (GDK_DISPLAY (self), emulated); - } - else - { - ret = gdk_scroll_event_new_discrete (GDK_SURFACE (surface), - pointer, - NULL, - get_time_from_ns_event (nsevent), - state, - direction, - FALSE); - } + ret = gdk_scroll_event_new_discrete (GDK_SURFACE (surface), + pointer, + NULL, + get_time_from_ns_event (nsevent), + state, + direction, + FALSE); } if (phase == NSEventPhaseEnded || phase == NSEventPhaseCancelled) From 48c650c1021fc263f7ac9d00e84bfba19b789c05 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sat, 26 Feb 2022 13:31:18 -0800 Subject: [PATCH 12/56] macos: send stop event when fingers touch When the fingers are placed on the touchpad, we get a scroll event with the phase NSEventPhaseMayBegin. We can use this to synthesize an is_stop event. This results in the scrolledwindow stopping scroll with stop gestures. This can cause another warning as well, however, which should be addressed from #4730. Fixes #4733 --- gdk/macos/gdkmacosdisplay-translate.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index db1cfa2527..e261b8a4ae 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -635,6 +635,20 @@ fill_scroll_event (GdkMacosDisplay *self, state = _gdk_macos_display_get_current_mouse_modifiers (self) | _gdk_macos_display_get_current_keyboard_modifiers (self); + /* If we are starting a new phase, send a stop so any previous + * scrolling immediately stops. + */ + if (phase == NSEventPhaseMayBegin) + { + ret = gdk_scroll_event_new (GDK_SURFACE (surface), + pointer, + NULL, + get_time_from_ns_event (nsevent), + state, + 0.0, 0.0, TRUE); + _gdk_event_queue_append (GDK_DISPLAY (self), g_steal_pointer (&ret)); + } + dx = [nsevent deltaX]; dy = [nsevent deltaY]; From df2fb3b5209dcb0740a0af6d8fde5b490e310169 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sun, 27 Feb 2022 23:17:40 -0800 Subject: [PATCH 13/56] macos: use video mode for refresh rate and interval Using the mode allows better detection of refresh rate and refresh interval for the CVDisplayLink bridge to GdkFrameClock. Using it can help ensure that our 144hz displays can actually reach that rather than falling back to just 60hz. This will also need future commits to rework the displaylink source to be per-monitor. --- gdk/macos/gdkdisplaylinksource.c | 59 +++++++++++++++++++++++--------- gdk/macos/gdkdisplaylinksource.h | 4 ++- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/gdk/macos/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c index 8b2f1a013f..fe75eb3a55 100644 --- a/gdk/macos/gdkdisplaylinksource.c +++ b/gdk/macos/gdkdisplaylinksource.c @@ -65,7 +65,7 @@ gdk_display_link_source_dispatch (GSource *source, impl->needs_dispatch = FALSE; - if (callback != NULL) + if (!impl->paused && callback != NULL) ret = callback (user_data); return ret; @@ -76,7 +76,9 @@ gdk_display_link_source_finalize (GSource *source) { GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source; - CVDisplayLinkStop (impl->display_link); + if (!impl->paused) + CVDisplayLinkStop (impl->display_link); + CVDisplayLinkRelease (impl->display_link); } @@ -90,12 +92,18 @@ static GSourceFuncs gdk_display_link_source_funcs = { void gdk_display_link_source_pause (GdkDisplayLinkSource *source) { + g_return_if_fail (source->paused == FALSE); + + source->paused = TRUE; CVDisplayLinkStop (source->display_link); } void gdk_display_link_source_unpause (GdkDisplayLinkSource *source) { + g_return_if_fail (source->paused == TRUE); + + source->paused = FALSE; CVDisplayLinkStart (source->display_link); } @@ -160,16 +168,16 @@ gdk_display_link_source_frame_cb (CVDisplayLinkRef display_link, * Returns: (transfer full): A newly created `GSource` */ GSource * -gdk_display_link_source_new (CGDirectDisplayID display_id) +gdk_display_link_source_new (CGDirectDisplayID display_id, + CGDisplayModeRef mode) { GdkDisplayLinkSource *impl; GSource *source; - CVReturn ret; - double period; source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl); impl = (GdkDisplayLinkSource *)source; impl->display_id = display_id; + impl->paused = TRUE; /* Create DisplayLink for timing information for the display in * question so that we can produce graphics for that display at whatever @@ -178,21 +186,34 @@ gdk_display_link_source_new (CGDirectDisplayID display_id) if (CVDisplayLinkCreateWithCGDisplay (display_id, &impl->display_link) != kCVReturnSuccess) { g_warning ("Failed to initialize CVDisplayLink!"); - return source; + goto failure; } - /* - * Determine our nominal period between frames. - */ - period = CVDisplayLinkGetActualOutputVideoRefreshPeriod (impl->display_link); - if (period == 0.0) - period = 1.0 / 60.0; - impl->refresh_interval = period * 1000000L; - impl->refresh_rate = 1.0 / period * 1000L; + impl->refresh_rate = CGDisplayModeGetRefreshRate (mode) * 1000.0; - /* - * Wire up our callback to be executed within the high-priority thread. - */ + if (impl->refresh_rate == 0) + { + const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (impl->display_link); + if (!(time.flags & kCVTimeIsIndefinite)) + impl->refresh_rate = (double)time.timeScale / (double)time.timeValue * 1000.0; + } + + if (impl->refresh_rate != 0) + { + impl->refresh_interval = 1000000.0 / (double)impl->refresh_rate * 1000.0; + } + else + { + double period = CVDisplayLinkGetActualOutputVideoRefreshPeriod (impl->display_link); + + if (period == 0.0) + period = 1.0 / 60.0; + + impl->refresh_rate = 1.0 / period * 1000L; + impl->refresh_interval = period * 1000000L; + } + + /* Wire up our callback to be executed within the high-priority thread. */ CVDisplayLinkSetOutputCallback (impl->display_link, gdk_display_link_source_frame_cb, source); @@ -200,6 +221,10 @@ gdk_display_link_source_new (CGDirectDisplayID display_id) g_source_set_static_name (source, "[gdk] quartz frame clock"); return source; + +failure: + g_source_unref (source); + return NULL; } static gint64 diff --git a/gdk/macos/gdkdisplaylinksource.h b/gdk/macos/gdkdisplaylinksource.h index bff1c91475..6f465907a2 100644 --- a/gdk/macos/gdkdisplaylinksource.h +++ b/gdk/macos/gdkdisplaylinksource.h @@ -36,12 +36,14 @@ typedef struct CVDisplayLinkRef display_link; gint64 refresh_interval; guint refresh_rate; + guint paused : 1; volatile gint64 presentation_time; volatile guint needs_dispatch; } GdkDisplayLinkSource; -GSource *gdk_display_link_source_new (CGDirectDisplayID display_id); +GSource *gdk_display_link_source_new (CGDirectDisplayID display_id, + CGDisplayModeRef mode); void gdk_display_link_source_pause (GdkDisplayLinkSource *source); void gdk_display_link_source_unpause (GdkDisplayLinkSource *source); From 856728ea1074457a426c3cc9224800551849360e Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sun, 27 Feb 2022 23:31:28 -0800 Subject: [PATCH 14/56] macos: support mix-rate monitors Previously, a single CVDisplayLink was used to drive updates for all surfaces across all monitors. It used a 'best guess' rate which would allow for updates across monitors of mixed rates. This is undesirable for situations where you might have a 144hz monitor as it does not allow for reaching up to that frame rate. Instead, we want to use a per-monitor CVDisplayLink which will fire at the rate of the monitor down to the level of updates we require. This commit does just that. When a surface crosses onto a new monitor, that monitor is used to drive the GdkFrameClock. Fixes #4732 --- gdk/macos/gdkmacosdisplay-private.h | 15 --- gdk/macos/gdkmacosdisplay.c | 105 ------------------- gdk/macos/gdkmacosmonitor-private.h | 14 ++- gdk/macos/gdkmacosmonitor.c | 139 ++++++++++++++++++++++++- gdk/macos/gdkmacossurface-private.h | 7 +- gdk/macos/gdkmacossurface.c | 153 +++++++++++++++++----------- 6 files changed, 243 insertions(+), 190 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h index b9f33fe20e..ef17618d61 100644 --- a/gdk/macos/gdkmacosdisplay-private.h +++ b/gdk/macos/gdkmacosdisplay-private.h @@ -68,16 +68,6 @@ struct _GdkMacosDisplay */ GQueue sorted_surfaces; - /* Our CVDisplayLink based GSource which we use to freeze/thaw the - * GdkFrameClock for the surface. - */ - GSource *frame_source; - - /* A queue of surfaces which we know are awaiting frames to be drawn. This - * uses the GdkMacosSurface.frame link. - */ - GQueue awaiting_frames; - /* The surface that is receiving keyboard events */ GdkMacosSurface *keyboard_surface; @@ -138,10 +128,6 @@ GdkMacosSurface *_gdk_macos_display_get_surface_at_display_coords (GdkMacosDisp void _gdk_macos_display_reload_monitors (GdkMacosDisplay *self); void _gdk_macos_display_surface_removed (GdkMacosDisplay *self, GdkMacosSurface *surface); -void _gdk_macos_display_add_frame_callback (GdkMacosDisplay *self, - GdkMacosSurface *surface); -void _gdk_macos_display_remove_frame_callback (GdkMacosDisplay *self, - GdkMacosSurface *surface); NSWindow *_gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self, int *x, int *y); @@ -157,7 +143,6 @@ void _gdk_macos_display_surface_resigned_key (GdkMacosDisp GdkMacosSurface *surface); void _gdk_macos_display_surface_became_key (GdkMacosDisplay *self, GdkMacosSurface *surface); -int _gdk_macos_display_get_nominal_refresh_rate (GdkMacosDisplay *self); void _gdk_macos_display_clear_sorting (GdkMacosDisplay *self); const GList *_gdk_macos_display_get_surfaces (GdkMacosDisplay *self); void _gdk_macos_display_send_button_event (GdkMacosDisplay *self, diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index 5501a16318..cb0331419d 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -231,56 +231,6 @@ gdk_macos_display_load_seat (GdkMacosDisplay *self) g_object_unref (seat); } -static gboolean -gdk_macos_display_frame_cb (gpointer data) -{ - GdkMacosDisplay *self = data; - GdkDisplayLinkSource *source; - gint64 presentation_time; - gint64 now; - GList *iter; - - g_assert (GDK_IS_MACOS_DISPLAY (self)); - - source = (GdkDisplayLinkSource *)self->frame_source; - - presentation_time = source->presentation_time; - now = g_source_get_time ((GSource *)source); - - iter = self->awaiting_frames.head; - - while (iter != NULL) - { - GdkMacosSurface *surface = iter->data; - - g_assert (GDK_IS_MACOS_SURFACE (surface)); - - iter = iter->next; - - _gdk_macos_surface_publish_timings (surface, - source->presentation_time, - source->refresh_interval); - - _gdk_macos_display_remove_frame_callback (self, surface); - - if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (surface))) - gdk_surface_thaw_updates (GDK_SURFACE (surface)); - } - - return G_SOURCE_CONTINUE; -} - -static void -gdk_macos_display_load_display_link (GdkMacosDisplay *self) -{ - self->frame_source = gdk_display_link_source_new (CGMainDisplayID ()); - g_source_set_callback (self->frame_source, - gdk_macos_display_frame_cb, - self, - NULL); - g_source_attach (self->frame_source, NULL); -} - static const char * gdk_macos_display_get_name (GdkDisplay *display) { @@ -384,7 +334,6 @@ _gdk_macos_display_surface_added (GdkMacosDisplay *self, g_assert (GDK_IS_MACOS_SURFACE (surface)); g_assert (!queue_contains (&self->sorted_surfaces, &surface->sorted)); g_assert (!queue_contains (&self->main_surfaces, &surface->main)); - g_assert (!queue_contains (&self->awaiting_frames, &surface->frame)); g_assert (surface->sorted.data == surface); g_assert (surface->main.data == surface); g_assert (surface->frame.data == surface); @@ -411,9 +360,6 @@ _gdk_macos_display_surface_removed (GdkMacosDisplay *self, if (queue_contains (&self->main_surfaces, &surface->main)) _gdk_macos_display_surface_resigned_main (self, surface); - if (queue_contains (&self->awaiting_frames, &surface->frame)) - g_queue_unlink (&self->awaiting_frames, &surface->frame); - g_return_if_fail (self->keyboard_surface != surface); } @@ -649,7 +595,6 @@ gdk_macos_display_finalize (GObject *object) g_clear_pointer (&self->active_drags, g_hash_table_unref); g_clear_pointer (&self->active_drops, g_hash_table_unref); g_clear_object (&GDK_DISPLAY (self)->clipboard); - g_clear_pointer (&self->frame_source, g_source_unref); g_clear_object (&self->monitors); g_clear_pointer (&self->name, g_free); @@ -724,9 +669,6 @@ _gdk_macos_display_open (const char *display_name) gdk_macos_display_load_seat (self); gdk_macos_display_load_clipboard (self); - - /* Load CVDisplayLink before monitors to access refresh rates */ - gdk_macos_display_load_display_link (self); _gdk_macos_display_reload_monitors (self); /* Initialize feedback from display server */ @@ -972,42 +914,6 @@ _gdk_macos_display_get_surface_at_display_coords (GdkMacosDisplay *self, return _gdk_macos_display_get_surface_at_coords (self, x_gdk, y_gdk, surface_x, surface_y); } -void -_gdk_macos_display_add_frame_callback (GdkMacosDisplay *self, - GdkMacosSurface *surface) -{ - g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); - g_return_if_fail (GDK_IS_MACOS_SURFACE (surface)); - - if (!queue_contains (&self->awaiting_frames, &surface->frame)) - { - /* Processing frames is always head to tail, so push to the - * head so that we don't possibly re-enter this right after - * adding to the queue. - */ - g_queue_push_head_link (&self->awaiting_frames, &surface->frame); - - if (self->awaiting_frames.length == 1) - gdk_display_link_source_unpause ((GdkDisplayLinkSource *)self->frame_source); - } -} - -void -_gdk_macos_display_remove_frame_callback (GdkMacosDisplay *self, - GdkMacosSurface *surface) -{ - g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); - g_return_if_fail (GDK_IS_MACOS_SURFACE (surface)); - - if (queue_contains (&self->awaiting_frames, &surface->frame)) - { - g_queue_unlink (&self->awaiting_frames, &surface->frame); - - if (self->awaiting_frames.length == 0) - gdk_display_link_source_pause ((GdkDisplayLinkSource *)self->frame_source); - } -} - NSWindow * _gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self, int *x, @@ -1027,17 +933,6 @@ _gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self, return NULL; } -int -_gdk_macos_display_get_nominal_refresh_rate (GdkMacosDisplay *self) -{ - g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), 60 * 1000); - - if (self->frame_source == NULL) - return 60 * 1000; - - return ((GdkDisplayLinkSource *)self->frame_source)->refresh_rate; -} - void _gdk_macos_display_clear_sorting (GdkMacosDisplay *self) { diff --git a/gdk/macos/gdkmacosmonitor-private.h b/gdk/macos/gdkmacosmonitor-private.h index e15f17352d..fee511d057 100644 --- a/gdk/macos/gdkmacosmonitor-private.h +++ b/gdk/macos/gdkmacosmonitor-private.h @@ -29,11 +29,15 @@ G_BEGIN_DECLS -GdkMacosMonitor *_gdk_macos_monitor_new (GdkMacosDisplay *display, - CGDirectDisplayID screen_id); -CGDirectDisplayID _gdk_macos_monitor_get_screen_id (GdkMacosMonitor *self); -gboolean _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self); -CGColorSpaceRef _gdk_macos_monitor_copy_colorspace (GdkMacosMonitor *self); +GdkMacosMonitor *_gdk_macos_monitor_new (GdkMacosDisplay *display, + CGDirectDisplayID screen_id); +CGDirectDisplayID _gdk_macos_monitor_get_screen_id (GdkMacosMonitor *self); +gboolean _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self); +CGColorSpaceRef _gdk_macos_monitor_copy_colorspace (GdkMacosMonitor *self); +void _gdk_macos_monitor_add_frame_callback (GdkMacosMonitor *self, + GdkMacosSurface *surface); +void _gdk_macos_monitor_remove_frame_callback (GdkMacosMonitor *self, + GdkMacosSurface *surface); G_END_DECLS diff --git a/gdk/macos/gdkmacosmonitor.c b/gdk/macos/gdkmacosmonitor.c index 6df1da0edc..3773ec5355 100644 --- a/gdk/macos/gdkmacosmonitor.c +++ b/gdk/macos/gdkmacosmonitor.c @@ -22,16 +22,21 @@ #include #include +#include "gdkdisplaylinksource.h" #include "gdkmacosdisplay-private.h" #include "gdkmacosmonitor-private.h" +#include "gdkmacossurface-private.h" #include "gdkmacosutils-private.h" struct _GdkMacosMonitor { - GdkMonitor parent_instance; - CGDirectDisplayID screen_id; - NSRect workarea; - guint has_opengl : 1; + GdkMonitor parent_instance; + CGDirectDisplayID screen_id; + GdkDisplayLinkSource *display_link; + NSRect workarea; + GQueue awaiting_frames; + guint has_opengl : 1; + guint in_frame : 1; }; struct _GdkMacosMonitorClass @@ -75,9 +80,26 @@ gdk_macos_monitor_get_workarea (GdkMonitor *monitor, geometry->height = self->workarea.size.height; } +static void +gdk_macos_monitor_dispose (GObject *object) +{ + GdkMacosMonitor *self = (GdkMacosMonitor *)object; + + if (self->display_link) + { + g_source_destroy ((GSource *)self->display_link); + g_clear_pointer ((GSource **)&self->display_link, g_source_unref); + } + + G_OBJECT_CLASS (gdk_macos_monitor_parent_class)->dispose (object); +} + static void gdk_macos_monitor_class_init (GdkMacosMonitorClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gdk_macos_monitor_dispose; } static void @@ -188,6 +210,68 @@ find_screen (CGDirectDisplayID screen_id) return screen; } +static gboolean +gdk_macos_monitor_display_link_cb (GdkMacosMonitor *self) +{ + gint64 presentation_time; + gint64 refresh_interval; + gint64 now; + GList *iter; + + g_assert (GDK_IS_MACOS_MONITOR (self)); + g_assert (!self->display_link->paused); + + self->in_frame = TRUE; + + presentation_time = self->display_link->presentation_time; + refresh_interval = self->display_link->refresh_interval; + now = g_source_get_time ((GSource *)self->display_link); + + iter = self->awaiting_frames.head; + + while (iter != NULL) + { + GdkMacosSurface *surface = iter->data; + + g_assert (GDK_IS_MACOS_SURFACE (surface)); + + iter = iter->next; + + g_queue_unlink (&self->awaiting_frames, &surface->frame); + _gdk_macos_surface_frame_presented (surface, presentation_time, refresh_interval); + } + + if (self->awaiting_frames.length == 0 && !self->display_link->paused) + gdk_display_link_source_pause (self->display_link); + + self->in_frame = FALSE; + + return G_SOURCE_CONTINUE; +} + +static void +_gdk_macos_monitor_reset_display_link (GdkMacosMonitor *self, + CGDisplayModeRef mode) +{ + GSource *source; + + g_assert (GDK_IS_MACOS_MONITOR (self)); + + if (self->display_link) + { + g_source_destroy ((GSource *)self->display_link); + g_clear_pointer ((GSource **)&self->display_link, g_source_unref); + } + + source = gdk_display_link_source_new (self->screen_id, mode); + self->display_link = (GdkDisplayLinkSource *)source; + g_source_set_callback (source, + (GSourceFunc) gdk_macos_monitor_display_link_cb, + self, + NULL); + g_source_attach (source, NULL); +} + gboolean _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self) { @@ -241,7 +325,7 @@ _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self) * setting (which is also used by the frame clock). */ if (!(refresh_rate = CGDisplayModeGetRefreshRate (mode))) - refresh_rate = _gdk_macos_display_get_nominal_refresh_rate (display); + refresh_rate = 60 * 1000; gdk_monitor_set_connector (GDK_MONITOR (self), connector); gdk_monitor_set_model (GDK_MONITOR (self), name); @@ -261,6 +345,9 @@ _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self) */ self->has_opengl = !!has_opengl; + /* Create a new display link to receive feedback about when to render */ + _gdk_macos_monitor_reset_display_link (self, mode); + CGDisplayModeRelease (mode); g_free (name); g_free (connector); @@ -302,3 +389,45 @@ _gdk_macos_monitor_copy_colorspace (GdkMacosMonitor *self) return CGDisplayCopyColorSpace (self->screen_id); } + +void +_gdk_macos_monitor_add_frame_callback (GdkMacosMonitor *self, + GdkMacosSurface *surface) +{ + g_return_if_fail (GDK_IS_MACOS_MONITOR (self)); + g_return_if_fail (GDK_IS_MACOS_SURFACE (surface)); + g_return_if_fail (surface->frame.data == (gpointer)surface); + g_return_if_fail (surface->frame.prev == NULL); + g_return_if_fail (surface->frame.next == NULL); + g_return_if_fail (self->awaiting_frames.head != &surface->frame); + g_return_if_fail (self->awaiting_frames.tail != &surface->frame); + + /* Processing frames is always head to tail, so push to the + * head so that we don't possibly re-enter this right after + * adding to the queue. + */ + if (!queue_contains (&self->awaiting_frames, &surface->frame)) + { + g_queue_push_head_link (&self->awaiting_frames, &surface->frame); + + if (!self->in_frame && self->awaiting_frames.length == 1) + gdk_display_link_source_unpause (self->display_link); + } +} + +void +_gdk_macos_monitor_remove_frame_callback (GdkMacosMonitor *self, + GdkMacosSurface *surface) +{ + g_return_if_fail (GDK_IS_MACOS_MONITOR (self)); + g_return_if_fail (GDK_IS_MACOS_SURFACE (surface)); + g_return_if_fail (surface->frame.data == (gpointer)surface); + + if (queue_contains (&self->awaiting_frames, &surface->frame)) + { + g_queue_unlink (&self->awaiting_frames, &surface->frame); + + if (!self->in_frame && self->awaiting_frames.length == 0) + gdk_display_link_source_pause (self->display_link); + } +} diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h index 5ac4201a42..15a7442c17 100644 --- a/gdk/macos/gdkmacossurface-private.h +++ b/gdk/macos/gdkmacossurface-private.h @@ -75,6 +75,8 @@ struct _GdkMacosSurface guint geometry_dirty : 1; guint next_frame_set : 1; guint show_on_next_swap : 1; + guint in_frame : 1; + guint awaiting_frame : 1; }; struct _GdkMacosSurfaceClass @@ -116,10 +118,11 @@ void _gdk_macos_surface_resize (GdkMacosSurface int width, int height); void _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self); -void _gdk_macos_surface_show (GdkMacosSurface *self); -void _gdk_macos_surface_publish_timings (GdkMacosSurface *self, +void _gdk_macos_surface_request_frame (GdkMacosSurface *self); +void _gdk_macos_surface_frame_presented (GdkMacosSurface *self, gint64 predicted_presentation_time, gint64 refresh_interval); +void _gdk_macos_surface_show (GdkMacosSurface *self); void _gdk_macos_surface_synthesize_null_key (GdkMacosSurface *self); void _gdk_macos_surface_move (GdkMacosSurface *self, int x, diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index cf9d7067f8..ac2353dd03 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -63,6 +63,73 @@ window_is_fullscreen (GdkMacosSurface *self) return ([self->window styleMask] & NSWindowStyleMaskFullScreen) != 0; } +void +_gdk_macos_surface_request_frame (GdkMacosSurface *self) +{ + g_assert (GDK_IS_MACOS_SURFACE (self)); + + if (self->awaiting_frame) + return; + + self->awaiting_frame = TRUE; + _gdk_macos_monitor_add_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + gdk_surface_freeze_updates (GDK_SURFACE (self)); +} + +static void +_gdk_macos_surface_cancel_frame (GdkMacosSurface *self) +{ + g_assert (GDK_IS_MACOS_SURFACE (self)); + + if (!self->awaiting_frame) + return; + + self->awaiting_frame = FALSE; + _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + gdk_surface_thaw_updates (GDK_SURFACE (self)); +} + +void +_gdk_macos_surface_frame_presented (GdkMacosSurface *self, + gint64 presentation_time, + gint64 refresh_interval) +{ + GdkFrameTimings *timings; + GdkFrameClock *frame_clock; + + g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); + + self->awaiting_frame = FALSE; + + if (GDK_SURFACE_DESTROYED (self)) + return; + + frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self)); + + if (self->pending_frame_counter) + { + timings = gdk_frame_clock_get_timings (frame_clock, self->pending_frame_counter); + + if (timings != NULL) + { + timings->presentation_time = presentation_time - refresh_interval; + timings->complete = TRUE; + } + + self->pending_frame_counter = 0; + } + + timings = gdk_frame_clock_get_current_timings (frame_clock); + + if (timings != NULL) + { + timings->refresh_interval = refresh_interval; + timings->predicted_presentation_time = presentation_time; + } + + if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self))) + gdk_surface_thaw_updates (GDK_SURFACE (self)); +} void _gdk_macos_surface_reposition_children (GdkMacosSurface *self) @@ -131,8 +198,6 @@ gdk_macos_surface_hide (GdkSurface *surface) self->show_on_next_swap = FALSE; - _gdk_macos_display_remove_frame_callback (GDK_MACOS_DISPLAY (surface->display), self); - was_mapped = GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self)); was_key = [self->window isKeyWindow]; @@ -157,8 +222,12 @@ gdk_macos_surface_hide (GdkSurface *surface) } } - if (was_mapped) - gdk_surface_freeze_updates (GDK_SURFACE (self)); + if (self->awaiting_frame) + { + self->awaiting_frame = FALSE; + _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + gdk_surface_freeze_updates (surface); + } } static int @@ -202,6 +271,7 @@ gdk_macos_surface_begin_frame (GdkMacosSurface *self) { g_assert (GDK_IS_MACOS_SURFACE (self)); + self->in_frame = TRUE; } static void @@ -222,11 +292,9 @@ gdk_macos_surface_end_frame (GdkMacosSurface *self) if ((timings = gdk_frame_clock_get_current_timings (frame_clock))) self->pending_frame_counter = timings->frame_counter; - if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self))) - { - _gdk_macos_display_add_frame_callback (GDK_MACOS_DISPLAY (display), self); - gdk_surface_freeze_updates (GDK_SURFACE (self)); - } + self->in_frame = FALSE; + + _gdk_macos_surface_request_frame (self); } static void @@ -400,7 +468,16 @@ gdk_macos_surface_destroy (GdkSurface *surface, GdkMacosWindow *window = g_steal_pointer (&self->window); GdkFrameClock *frame_clock; - g_clear_object (&self->best_monitor); + if (self->best_monitor) + { + if (self->awaiting_frame) + { + _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + self->awaiting_frame = FALSE; + } + + g_clear_object (&self->best_monitor); + } if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self)))) { @@ -787,41 +864,6 @@ _gdk_macos_surface_configure (GdkMacosSurface *self) _gdk_macos_surface_reposition_children (self); } -void -_gdk_macos_surface_publish_timings (GdkMacosSurface *self, - gint64 presentation_time, - gint64 refresh_interval) -{ - GdkFrameTimings *timings; - GdkFrameClock *frame_clock; - - g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); - - if (!(frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self)))) - return; - - if (self->pending_frame_counter) - { - timings = gdk_frame_clock_get_timings (frame_clock, self->pending_frame_counter); - - if (timings != NULL) - { - timings->presentation_time = presentation_time - refresh_interval; - timings->complete = TRUE; - } - - self->pending_frame_counter = 0; - } - - timings = gdk_frame_clock_get_current_timings (frame_clock); - - if (timings != NULL) - { - timings->refresh_interval = refresh_interval; - timings->predicted_presentation_time = presentation_time; - } -} - void _gdk_macos_surface_show (GdkMacosSurface *self) { @@ -832,22 +874,15 @@ _gdk_macos_surface_show (GdkMacosSurface *self) if (GDK_SURFACE_DESTROYED (self)) return; + _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display)); + self->show_on_next_swap = TRUE; + was_mapped = GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self)); - if (!was_mapped) - gdk_surface_set_is_mapped (GDK_SURFACE (self), TRUE); - - _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display)); - - self->show_on_next_swap = TRUE; - if (!was_mapped) { - if (gdk_surface_get_mapped (GDK_SURFACE (self))) - { - _gdk_macos_surface_configure (self); - gdk_surface_thaw_updates (GDK_SURFACE (self)); - } + gdk_surface_set_is_mapped (GDK_SURFACE (self), TRUE); + gdk_surface_thaw_updates (GDK_SURFACE (self)); } } @@ -994,6 +1029,8 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); + _gdk_macos_surface_cancel_frame (self); + rect.x = self->root_x; rect.y = self->root_y; rect.width = GDK_SURFACE (self)->width; @@ -1056,8 +1093,8 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) } _gdk_macos_surface_configure (self); - gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL); + _gdk_macos_surface_request_frame (self); } GdkMonitor * From b19526489e385ba45756c4aefc3314d19c196d67 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 00:42:47 -0800 Subject: [PATCH 15/56] macos: short-circuit on NSEventPhaseMayBegin We only need to send a single event in this case, so just short-circuit instead of trying to return an additional event. --- gdk/macos/gdkmacosdisplay-translate.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index e261b8a4ae..e3fb03fea5 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -639,15 +639,12 @@ fill_scroll_event (GdkMacosDisplay *self, * scrolling immediately stops. */ if (phase == NSEventPhaseMayBegin) - { - ret = gdk_scroll_event_new (GDK_SURFACE (surface), - pointer, - NULL, - get_time_from_ns_event (nsevent), - state, - 0.0, 0.0, TRUE); - _gdk_event_queue_append (GDK_DISPLAY (self), g_steal_pointer (&ret)); - } + return gdk_scroll_event_new (GDK_SURFACE (surface), + pointer, + NULL, + get_time_from_ns_event (nsevent), + state, + 0.0, 0.0, TRUE); dx = [nsevent deltaX]; dy = [nsevent deltaY]; From b2ab0b1fcb8f17f992f58157a4c157c58abb613e Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 01:29:22 -0800 Subject: [PATCH 16/56] macos: add readonly IOSurfaceLock helper This can be used to lock a surface for reading to avoid causing the surface contents to be invalidated. This is needed when reading back from a front-buffer to the back-buffer as is needed when using Cairo surfaces to implement something similar to BufferAge. --- gdk/macos/gdkmacosbuffer-private.h | 2 ++ gdk/macos/gdkmacosbuffer.c | 39 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/gdk/macos/gdkmacosbuffer-private.h b/gdk/macos/gdkmacosbuffer-private.h index 6be201147c..4b446a7212 100644 --- a/gdk/macos/gdkmacosbuffer-private.h +++ b/gdk/macos/gdkmacosbuffer-private.h @@ -41,6 +41,8 @@ GdkMacosBuffer *_gdk_macos_buffer_new (int width IOSurfaceRef _gdk_macos_buffer_get_native (GdkMacosBuffer *self); void _gdk_macos_buffer_lock (GdkMacosBuffer *self); void _gdk_macos_buffer_unlock (GdkMacosBuffer *self); +void _gdk_macos_buffer_read_lock (GdkMacosBuffer *self); +void _gdk_macos_buffer_read_unlock (GdkMacosBuffer *self); guint _gdk_macos_buffer_get_width (GdkMacosBuffer *self); guint _gdk_macos_buffer_get_height (GdkMacosBuffer *self); guint _gdk_macos_buffer_get_stride (GdkMacosBuffer *self); diff --git a/gdk/macos/gdkmacosbuffer.c b/gdk/macos/gdkmacosbuffer.c index ac99302ee4..46a0504461 100644 --- a/gdk/macos/gdkmacosbuffer.c +++ b/gdk/macos/gdkmacosbuffer.c @@ -192,6 +192,45 @@ _gdk_macos_buffer_unlock (GdkMacosBuffer *self) IOSurfaceUnlock (self->surface, 0, NULL); } +/** + * _gdk_macos_buffer_lock_readonly: + * + * Like _gdk_macos_buffer_lock() but uses the read-only flag to + * indicate we are not interested in retrieving the updates from + * the GPU before modifying the CPU-side cache. + * + * Must be used with _gdk_macos_buffer_unlock_readonly(). + */ +void +_gdk_macos_buffer_read_lock (GdkMacosBuffer *self) +{ + kern_return_t ret; + + g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); + g_return_if_fail (self->lock_count == 0); + + self->lock_count++; + + ret = IOSurfaceLock (self->surface, kIOSurfaceLockReadOnly, NULL); + + g_return_if_fail (ret == KERN_SUCCESS); +} + +void +_gdk_macos_buffer_read_unlock (GdkMacosBuffer *self) +{ + kern_return_t ret; + + g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); + g_return_if_fail (self->lock_count == 1); + + self->lock_count--; + + ret = IOSurfaceUnlock (self->surface, kIOSurfaceLockReadOnly, NULL); + + g_return_if_fail (ret == KERN_SUCCESS); +} + guint _gdk_macos_buffer_get_width (GdkMacosBuffer *self) { From 998c787638b7821a5377c3475ca085e6a8506fba Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 01:42:48 -0800 Subject: [PATCH 17/56] macos: fix cairo renderer with double buffering If we are double buffering surfaces with IOSurface then we need to copy the area that was damaged in the previous frame to the back buffer. This can be done with IOSurface but we need to hold the read-only lock so that we don't cause the underlying IOSurface contents to be invalidated. Additionally, since this is only used in the context of rendering to a GdkMacosSurface, we know the life-time of the cairo_surface_t and can simply lock/unlock the IOSurface buffer from begin_frame/end_frame to have the buffer flushing semantics we want. To ensure that we don't over damage, we store the damage in begin_frame (and copy it) and then subtract it from the next frames damage to determine the smallest amount we need to copy (taking scale factor into account). We don't care to modify the damage region to swapBuffers because they already have the right contents and could potentially fall into another tile anyway and we'd like to avoid damaging that. Fixes #4735 --- gdk/macos/gdkmacosbuffer.c | 2 +- gdk/macos/gdkmacoscairocontext.c | 120 +++++++++++++++++++++++-------- 2 files changed, 93 insertions(+), 29 deletions(-) diff --git a/gdk/macos/gdkmacosbuffer.c b/gdk/macos/gdkmacosbuffer.c index 46a0504461..eb8a719dbc 100644 --- a/gdk/macos/gdkmacosbuffer.c +++ b/gdk/macos/gdkmacosbuffer.c @@ -281,7 +281,7 @@ _gdk_macos_buffer_set_damage (GdkMacosBuffer *self, return; g_clear_pointer (&self->damage, cairo_region_destroy); - self->damage = cairo_region_reference (damage); + self->damage = cairo_region_copy (damage); } gpointer diff --git a/gdk/macos/gdkmacoscairocontext.c b/gdk/macos/gdkmacoscairocontext.c index 041f1193e6..31eecd5df6 100644 --- a/gdk/macos/gdkmacoscairocontext.c +++ b/gdk/macos/gdkmacoscairocontext.c @@ -42,19 +42,6 @@ struct _GdkMacosCairoContextClass G_DEFINE_TYPE (GdkMacosCairoContext, _gdk_macos_cairo_context, GDK_TYPE_CAIRO_CONTEXT) -static const cairo_user_data_key_t buffer_key; - -static void -unlock_buffer (gpointer data) -{ - GdkMacosBuffer *buffer = data; - - g_assert (GDK_IS_MACOS_BUFFER (buffer)); - - _gdk_macos_buffer_unlock (buffer); - g_clear_object (&buffer); -} - static cairo_t * _gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context) { @@ -106,12 +93,9 @@ _gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context) stride); cairo_surface_set_device_scale (image_surface, scale, scale); - /* Lock the buffer so we can modify it safely */ - _gdk_macos_buffer_lock (buffer); - cairo_surface_set_user_data (image_surface, - &buffer_key, - g_object_ref (buffer), - unlock_buffer); + /* The buffer should already be locked at this point, and will + * be unlocked as part of end_frame. + */ if (!(cr = cairo_create (image_surface))) goto failure; @@ -158,6 +142,52 @@ failure: return cr; } +static void +copy_surface_data (GdkMacosBuffer *from, + GdkMacosBuffer *to, + const cairo_region_t *region, + int scale) +{ + const guint8 *from_base; + guint8 *to_base; + guint from_stride; + guint to_stride; + guint n_rects; + + g_assert (GDK_IS_MACOS_BUFFER (from)); + g_assert (GDK_IS_MACOS_BUFFER (to)); + g_assert (region != NULL); + g_assert (!cairo_region_is_empty (region)); + + from_base = _gdk_macos_buffer_get_data (from); + from_stride = _gdk_macos_buffer_get_stride (from); + + to_base = _gdk_macos_buffer_get_data (to); + to_stride = _gdk_macos_buffer_get_stride (to); + + n_rects = cairo_region_num_rectangles (region); + + for (guint i = 0; i < n_rects; i++) + { + cairo_rectangle_int_t rect; + int y2; + + cairo_region_get_rectangle (region, i, &rect); + + rect.y *= scale; + rect.height *= scale; + rect.x *= scale; + rect.width *= scale; + + y2 = rect.y + rect.height; + + for (int y = rect.y; y < y2; y++) + memcpy (&to_base[y * to_stride + rect.x * 4], + &from_base[y * from_stride + rect.x * 4], + rect.width * 4); + } +} + static void _gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context, gboolean prefers_high_depth, @@ -165,34 +195,68 @@ _gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context, { GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context; GdkMacosBuffer *buffer; - GdkSurface *surface; + GdkMacosSurface *surface; g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self)); [CATransaction begin]; [CATransaction setDisableActions:YES]; - surface = gdk_draw_context_get_surface (draw_context); - buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); + surface = GDK_MACOS_SURFACE (gdk_draw_context_get_surface (draw_context)); + buffer = _gdk_macos_surface_get_buffer (surface); _gdk_macos_buffer_set_damage (buffer, region); _gdk_macos_buffer_set_flipped (buffer, FALSE); + + _gdk_macos_buffer_lock (buffer); + + /* If there is damage that was on the previous frame that is not on + * this frame, we need to copy that rendered region over to the back + * buffer so that when swapping buffers, we still have that content. + * This is done with a read-only lock on the IOSurface to avoid + * invalidating the buffer contents. + */ + if (surface->front != NULL) + { + const cairo_region_t *previous = _gdk_macos_buffer_get_damage (surface->front); + + if (previous != NULL) + { + cairo_region_t *copy; + + copy = cairo_region_copy (previous); + cairo_region_subtract (copy, region); + + if (!cairo_region_is_empty (copy)) + { + int scale = gdk_surface_get_scale_factor (GDK_SURFACE (surface)); + + _gdk_macos_buffer_read_lock (surface->front); + copy_surface_data (surface->front, buffer, copy, scale); + _gdk_macos_buffer_read_unlock (surface->front); + } + + cairo_region_destroy (copy); + } + } } static void _gdk_macos_cairo_context_end_frame (GdkDrawContext *draw_context, cairo_region_t *painted) { + GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context; + GdkMacosSurface *surface; GdkMacosBuffer *buffer; - GdkSurface *surface; - g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (draw_context)); + g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self)); - surface = gdk_draw_context_get_surface (draw_context); - buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); + surface = GDK_MACOS_SURFACE (gdk_draw_context_get_surface (draw_context)); + buffer = _gdk_macos_surface_get_buffer (surface); - _gdk_macos_surface_swap_buffers (GDK_MACOS_SURFACE (surface), painted); - _gdk_macos_buffer_set_damage (buffer, NULL); + _gdk_macos_buffer_unlock (buffer); + + _gdk_macos_surface_swap_buffers (surface, painted); [CATransaction commit]; } From 75b186eb62800744115c341ee31cbc3e78b4734e Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 02:10:30 -0800 Subject: [PATCH 18/56] macos: fix redisplay of GdkPopup This broke recently during the configure cleanups and also needed to have the tail directions fixed again. --- gdk/macos/gdkmacospopupsurface.c | 7 ++++--- gdk/macos/gdkmacossurface.c | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gdk/macos/gdkmacospopupsurface.c b/gdk/macos/gdkmacospopupsurface.c index 477961503f..5a2d7ed481 100644 --- a/gdk/macos/gdkmacospopupsurface.c +++ b/gdk/macos/gdkmacospopupsurface.c @@ -87,6 +87,9 @@ gdk_macos_popup_surface_layout (GdkMacosPopupSurface *self, gdk_surface_get_origin (GDK_SURFACE (self)->parent, &x, &y); + GDK_SURFACE (self)->x = final_rect.x; + GDK_SURFACE (self)->y = final_rect.y; + x += final_rect.x; y += final_rect.y; @@ -391,9 +394,7 @@ _gdk_macos_popup_surface_reposition (GdkMacosPopupSurface *self) { g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self)); - if (self->layout == NULL || - !gdk_surface_get_mapped (GDK_SURFACE (self)) || - GDK_SURFACE (self)->parent == NULL) + if (self->layout == NULL || GDK_SURFACE (self)->parent == NULL) return; gdk_macos_popup_surface_layout (self, diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index ac2353dd03..e92ba905fc 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -882,6 +882,7 @@ _gdk_macos_surface_show (GdkMacosSurface *self) if (!was_mapped) { gdk_surface_set_is_mapped (GDK_SURFACE (self), TRUE); + gdk_surface_request_layout (GDK_SURFACE (self)); gdk_surface_thaw_updates (GDK_SURFACE (self)); } } From 590a1c2f3adc8228c1e80f0a2264bbf5ad633593 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 11:29:24 -0800 Subject: [PATCH 19/56] macos: external access to display name helpers These can be handy to print debug information when we don't have a GdkMacosMonitor to work with. --- gdk/macos/gdkmacosmonitor-private.h | 2 ++ gdk/macos/gdkmacosmonitor.c | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/gdk/macos/gdkmacosmonitor-private.h b/gdk/macos/gdkmacosmonitor-private.h index fee511d057..88f586813e 100644 --- a/gdk/macos/gdkmacosmonitor-private.h +++ b/gdk/macos/gdkmacosmonitor-private.h @@ -29,6 +29,8 @@ G_BEGIN_DECLS +char *_gdk_macos_monitor_get_localized_name (NSScreen *screen); +char *_gdk_macos_monitor_get_connector_name (CGDirectDisplayID screen_id); GdkMacosMonitor *_gdk_macos_monitor_new (GdkMacosDisplay *display, CGDirectDisplayID screen_id); CGDirectDisplayID _gdk_macos_monitor_get_screen_id (GdkMacosMonitor *self); diff --git a/gdk/macos/gdkmacosmonitor.c b/gdk/macos/gdkmacosmonitor.c index 3773ec5355..bfec76b7ef 100644 --- a/gdk/macos/gdkmacosmonitor.c +++ b/gdk/macos/gdkmacosmonitor.c @@ -160,8 +160,8 @@ GetSubpixelLayout (CGDirectDisplayID screen_id) return GDK_SUBPIXEL_LAYOUT_UNKNOWN; } -static char * -GetLocalizedName (NSScreen *screen) +char * +_gdk_macos_monitor_get_localized_name (NSScreen *screen) { #ifdef AVAILABLE_MAC_OS_X_VERSION_10_15_AND_LATER GDK_BEGIN_MACOS_ALLOC_POOL; @@ -182,8 +182,8 @@ GetLocalizedName (NSScreen *screen) #endif } -static char * -GetConnectorName (CGDirectDisplayID screen_id) +char * +_gdk_macos_monitor_get_connector_name (CGDirectDisplayID screen_id) { guint unit = CGDisplayUnitNumber (screen_id); return g_strdup_printf ("unit-%u", unit); @@ -306,8 +306,8 @@ _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self) pixel_width = CGDisplayModeGetPixelWidth (mode); has_opengl = CGDisplayUsesOpenGLAcceleration (self->screen_id); subpixel_layout = GetSubpixelLayout (self->screen_id); - name = GetLocalizedName (screen); - connector = GetConnectorName (self->screen_id); + name = _gdk_macos_monitor_get_localized_name (screen); + connector = _gdk_macos_monitor_get_connector_name (self->screen_id); if (width != 0 && pixel_width != 0) scale_factor = MAX (1, pixel_width / width); From f2ac5576c23df560f4b70e36f197ace74f67c411 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 11:20:48 -0800 Subject: [PATCH 20/56] macos: leave note about monitor discovery --- gdk/macos/gdkdisplaylinksource.c | 11 +++++++++++ gdk/macos/gdkmacosmonitor-private.h | 1 + 2 files changed, 12 insertions(+) diff --git a/gdk/macos/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c index fe75eb3a55..6a613b40a4 100644 --- a/gdk/macos/gdkdisplaylinksource.c +++ b/gdk/macos/gdkdisplaylinksource.c @@ -26,7 +26,9 @@ #include "gdkdisplaylinksource.h" +#include "gdkdebug.h" #include "gdkmacoseventsource-private.h" +#include "gdkmacosmonitor-private.h" #include "gdk-private.h" static gint64 host_to_frame_clock_time (gint64 val); @@ -173,6 +175,7 @@ gdk_display_link_source_new (CGDirectDisplayID display_id, { GdkDisplayLinkSource *impl; GSource *source; + char *name; source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl); impl = (GdkDisplayLinkSource *)source; @@ -213,6 +216,14 @@ gdk_display_link_source_new (CGDirectDisplayID display_id, impl->refresh_interval = period * 1000000L; } + name = _gdk_macos_monitor_get_connector_name (display_id); + GDK_NOTE (MISC, + g_message ("Monitor \"%s\" discovered with Refresh Rate %d and Interval %"G_GINT64_FORMAT, + name ? name : "unknown", + impl->refresh_rate, + impl->refresh_interval)); + g_free (name); + /* Wire up our callback to be executed within the high-priority thread. */ CVDisplayLinkSetOutputCallback (impl->display_link, gdk_display_link_source_frame_cb, diff --git a/gdk/macos/gdkmacosmonitor-private.h b/gdk/macos/gdkmacosmonitor-private.h index 88f586813e..dfde4142c0 100644 --- a/gdk/macos/gdkmacosmonitor-private.h +++ b/gdk/macos/gdkmacosmonitor-private.h @@ -24,6 +24,7 @@ #include "gdkmacosdisplay.h" #include "gdkmacosmonitor.h" +#include "gdkmacossurface.h" #include "gdkmonitorprivate.h" From 27e9b87fbd9e1f665f4d32d270f275a25f186881 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 11:50:21 -0800 Subject: [PATCH 21/56] macos: add GDK_NOTE for surface sizing and placement This can be useful to debug sizing issues with the surface as well as the "window manager" placement code. --- gdk/macos/gdkmacostoplevelsurface.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/gdk/macos/gdkmacostoplevelsurface.c b/gdk/macos/gdkmacostoplevelsurface.c index c84ebb704b..d89434593b 100644 --- a/gdk/macos/gdkmacostoplevelsurface.c +++ b/gdk/macos/gdkmacostoplevelsurface.c @@ -174,6 +174,14 @@ _gdk_macos_toplevel_surface_present (GdkToplevel *toplevel, _gdk_macos_surface_set_geometry_hints (GDK_MACOS_SURFACE (self), &geometry, mask); gdk_surface_constrain_size (&geometry, mask, width, height, &width, &height); + + GDK_NOTE (MISC, + g_message ("Resizing \"%s\" to %dx%d", + GDK_MACOS_SURFACE (self)->title ? + GDK_MACOS_SURFACE (self)->title : + "untitled", + width, height)); + _gdk_macos_surface_resize (GDK_MACOS_SURFACE (self), width, height); /* Maximized state */ @@ -202,6 +210,13 @@ _gdk_macos_toplevel_surface_present (GdkToplevel *toplevel, GDK_MACOS_SURFACE (self), &x, &y); + GDK_NOTE (MISC, + g_message ("Placing new toplevel \"%s\" at %d,%d", + GDK_MACOS_SURFACE (self)->title ? + GDK_MACOS_SURFACE (self)->title : + "untitled", + x, y)); + _gdk_macos_surface_move (GDK_MACOS_SURFACE (self), x, y); } From 29d424ef91ad5007b588395abedc0d3865ef99bb Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 12:13:29 -0800 Subject: [PATCH 22/56] macos: add GDK_NOTE when surface changes monitor --- gdk/macos/gdkmacossurface.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index e92ba905fc..356dea70e8 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -27,6 +27,7 @@ #include "gdkmacossurface-private.h" +#include "gdkdebug.h" #include "gdkdeviceprivate.h" #include "gdkdisplay.h" #include "gdkeventsprivate.h" @@ -1090,7 +1091,10 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) if (g_set_object (&self->best_monitor, best)) { - /* TODO: change frame clock to new monitor */ + GDK_NOTE (MISC, + g_message ("Surface \"%s\" moved to monitor \"%s\"", + self->title ? self->title : "unknown", + gdk_monitor_get_connector (best))); } _gdk_macos_surface_configure (self); From 17b40ca1487b2427598e8404af22d8b1fe83726d Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 12:20:24 -0800 Subject: [PATCH 23/56] macos: start application in foreground We need to bring the application to the foreground in multiple ways, and this call to [NSApp activateIgnoringOtherApps:YES] ensures that we become foreground before the first window is opened. Otherwise we end up starting applications in the background. Fixes #4736 --- gdk/macos/gdkmacosdisplay.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index cb0331419d..ddcc274372 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -684,6 +684,8 @@ _gdk_macos_display_open (const char *display_name) gdk_display_emit_opened (GDK_DISPLAY (self)); + [NSApp activateIgnoringOtherApps:YES]; + return GDK_DISPLAY (self); } From c7a6d1e8bfc07cc33a61953fcd02c6793b8b9401 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 13:09:57 -0800 Subject: [PATCH 24/56] macos: avoid size/origin changes when possible If _gdk_macos_surface_move_resize() was called with various -1 parameters we really want to avoid changing anything even if we think we know what the value might be. Otherwise, we risk messing up in-flight operations that we have not yet been notified of yet. This improves the chances we place windows in an appropriate location as they don't et screwed up before window-manager placement. --- gdk/macos/gdkmacossurface.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 356dea70e8..83860033c1 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -937,13 +937,17 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self, GdkDisplay *display; NSRect content_rect; NSRect frame_rect; + gboolean ignore_move; + gboolean ignore_size; g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); - if ((x == -1 || (x == self->root_x)) && - (y == -1 || (y == self->root_y)) && - (width == -1 || (width == surface->width)) && - (height == -1 || (height == surface->height))) + ignore_move = (x == -1 || (x == self->root_x)) && + (y == -1 || (y == self->root_y)); + ignore_size = (width == -1 || (width == surface->width)) && + (height == -1 || (height == surface->height)); + + if (ignore_move && ignore_size) return; display = gdk_surface_get_display (surface); @@ -964,7 +968,14 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self, x, y + height, &x, &y); - content_rect = NSMakeRect (x, y, width, height); + content_rect = [self->window contentRectForFrameRect:[self->window frame]]; + + if (!ignore_move) + content_rect.origin = NSMakePoint (x, y); + + if (!ignore_size) + content_rect.size = NSMakeSize (width, height); + frame_rect = [self->window frameRectForContentRect:content_rect]; [self->window setFrame:frame_rect display:YES]; } From 7369ce58da7c5beda6e5f385cf0f7c09a8089b0a Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 13:57:29 -0800 Subject: [PATCH 25/56] macos: check for best_monitor before using Make sure we have a monitor to enqueue/dequeue from before using it. That also allows us to use this from destroy and what-not. --- gdk/macos/gdkmacossurface.c | 41 +++++++++++++++---------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 83860033c1..42ff05385b 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -72,9 +72,12 @@ _gdk_macos_surface_request_frame (GdkMacosSurface *self) if (self->awaiting_frame) return; - self->awaiting_frame = TRUE; - _gdk_macos_monitor_add_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); - gdk_surface_freeze_updates (GDK_SURFACE (self)); + if (self->best_monitor != NULL) + { + self->awaiting_frame = TRUE; + _gdk_macos_monitor_add_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + gdk_surface_freeze_updates (GDK_SURFACE (self)); + } } static void @@ -85,9 +88,12 @@ _gdk_macos_surface_cancel_frame (GdkMacosSurface *self) if (!self->awaiting_frame) return; - self->awaiting_frame = FALSE; - _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); - gdk_surface_thaw_updates (GDK_SURFACE (self)); + if (self->best_monitor != NULL) + { + self->awaiting_frame = FALSE; + _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + gdk_surface_thaw_updates (GDK_SURFACE (self)); + } } void @@ -199,7 +205,9 @@ gdk_macos_surface_hide (GdkSurface *surface) self->show_on_next_swap = FALSE; - was_mapped = GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self)); + _gdk_macos_surface_cancel_frame (self); + + was_mapped = GDK_SURFACE_IS_MAPPED (surface); was_key = [self->window isKeyWindow]; seat = gdk_display_get_default_seat (surface->display); @@ -222,13 +230,6 @@ gdk_macos_surface_hide (GdkSurface *surface) [parentWindow showAndMakeKey:YES]; } } - - if (self->awaiting_frame) - { - self->awaiting_frame = FALSE; - _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); - gdk_surface_freeze_updates (surface); - } } static int @@ -469,16 +470,8 @@ gdk_macos_surface_destroy (GdkSurface *surface, GdkMacosWindow *window = g_steal_pointer (&self->window); GdkFrameClock *frame_clock; - if (self->best_monitor) - { - if (self->awaiting_frame) - { - _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); - self->awaiting_frame = FALSE; - } - - g_clear_object (&self->best_monitor); - } + _gdk_macos_surface_cancel_frame (self); + g_clear_object (&self->best_monitor); if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self)))) { From 2e1e7e72651089163f17b5a39d796612aa0559d9 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:32:50 -0800 Subject: [PATCH 26/56] macos: add re-entrancy check when monitors change --- gdk/macos/gdkmacossurface-private.h | 1 + gdk/macos/gdkmacossurface.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h index 15a7442c17..5f1f551f93 100644 --- a/gdk/macos/gdkmacossurface-private.h +++ b/gdk/macos/gdkmacossurface-private.h @@ -75,6 +75,7 @@ struct _GdkMacosSurface guint geometry_dirty : 1; guint next_frame_set : 1; guint show_on_next_swap : 1; + guint in_change_monitor : 1; guint in_frame : 1; guint awaiting_frame : 1; }; diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 42ff05385b..14cb3de015 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -1035,6 +1035,11 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); + if (self->in_change_monitor) + return; + + self->in_change_monitor = TRUE; + _gdk_macos_surface_cancel_frame (self); rect.x = self->root_x; @@ -1103,6 +1108,8 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) _gdk_macos_surface_configure (self); gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL); + + self->in_change_monitor = FALSE; _gdk_macos_surface_request_frame (self); } From 9b28153571d47b77c5082ece438db52bcab6e409 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:33:13 -0800 Subject: [PATCH 27/56] macos: style cleanup --- gdk/macos/gdkmacospopupsurface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacospopupsurface.c b/gdk/macos/gdkmacospopupsurface.c index 5a2d7ed481..dc6610b398 100644 --- a/gdk/macos/gdkmacospopupsurface.c +++ b/gdk/macos/gdkmacospopupsurface.c @@ -275,7 +275,7 @@ _gdk_macos_popup_surface_class_init (GdkMacosPopupSurfaceClass *klass) object_class->get_property = _gdk_macos_popup_surface_get_property; object_class->set_property = _gdk_macos_popup_surface_set_property; - gdk_popup_install_properties (object_class, 1); + gdk_popup_install_properties (object_class, LAST_PROP); } static void From 42f9ea07e241e69682c5a5c7334b482775e15377 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:34:27 -0800 Subject: [PATCH 28/56] macos: add clamp helper to keep rectangle in workarea This helper is useful to ensure we are consistent with how we keep a window clamped to the workarea of a monitor when placing windows on screen. (This does not affect snap-to-edges). --- gdk/macos/gdkmacosdisplay-wm.c | 41 +++++++---------------------- gdk/macos/gdkmacosmonitor-private.h | 2 ++ gdk/macos/gdkmacosmonitor.c | 26 ++++++++++++++++++ 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-wm.c b/gdk/macos/gdkmacosdisplay-wm.c index 7b508a4a9e..4f0672013c 100644 --- a/gdk/macos/gdkmacosdisplay-wm.c +++ b/gdk/macos/gdkmacosdisplay-wm.c @@ -20,7 +20,7 @@ #include "config.h" #include "gdkmacosdisplay-private.h" -#include "gdkmacosmonitor.h" +#include "gdkmacosmonitor-private.h" #include "gdkmacossurface-private.h" #include "gdkmacostoplevelsurface-private.h" @@ -36,47 +36,28 @@ _gdk_macos_display_position_toplevel_with_parent (GdkMacosDisplay *self, { GdkRectangle surface_rect; GdkRectangle parent_rect; - GdkRectangle workarea; GdkMonitor *monitor; g_assert (GDK_IS_MACOS_DISPLAY (self)); g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (surface)); g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (parent)); - /* If x/y is set, we should place relative to parent */ - if (GDK_SURFACE (surface)->x != 0 || GDK_SURFACE (surface)->y != 0) - { - *x = parent->root_x + GDK_SURFACE (surface)->x; - *y = parent->root_y + GDK_SURFACE (surface)->y; - return; - } + monitor = _gdk_macos_surface_get_best_monitor (parent); /* Try to center on top of the parent but also try to make the whole thing * visible in case that lands us under the topbar/panel/etc. */ - surface_rect.x = surface->root_x + surface->shadow_left; - surface_rect.y = surface->root_y + surface->shadow_top; + parent_rect.x = parent->root_x + parent->shadow_left; + parent_rect.y = parent->root_y + parent->shadow_top; + parent_rect.width = GDK_SURFACE (parent)->width - parent->shadow_left - parent->shadow_right; + parent_rect.height = GDK_SURFACE (parent)->height - parent->shadow_top - parent->shadow_bottom; + surface_rect.width = GDK_SURFACE (surface)->width - surface->shadow_left - surface->shadow_right; surface_rect.height = GDK_SURFACE (surface)->height - surface->shadow_top - surface->shadow_bottom; - - parent_rect.x = parent->root_x + surface->shadow_left; - parent_rect.y = parent->root_y + surface->shadow_top; - parent_rect.width = GDK_SURFACE (parent)->width - surface->shadow_left - surface->shadow_right; - parent_rect.height = GDK_SURFACE (parent)->height - surface->shadow_top - surface->shadow_bottom; - - /* Try to place centered atop parent */ surface_rect.x = parent_rect.x + ((parent_rect.width - surface_rect.width) / 2); surface_rect.y = parent_rect.y + ((parent_rect.height - surface_rect.height) / 2); - /* Now make sure that we don't overlap the top-bar */ - monitor = _gdk_macos_surface_get_best_monitor (parent); - gdk_macos_monitor_get_workarea (monitor, &workarea); - - if (surface_rect.x < workarea.x) - surface_rect.x = workarea.x; - - if (surface_rect.y < workarea.y) - surface_rect.y = workarea.y; + _gdk_macos_monitor_clamp (GDK_MACOS_MONITOR (monitor), &surface_rect); *x = surface_rect.x - surface->shadow_left; *y = surface_rect.y - surface->shadow_top; @@ -123,11 +104,7 @@ _gdk_macos_display_position_toplevel (GdkMacosDisplay *self, surface_rect.x = workarea.x + ((workarea.width - surface_rect.width) / 2); surface_rect.y = workarea.y + ((workarea.height - surface_rect.height) / 2); - if (surface_rect.x < workarea.x) - surface_rect.x = workarea.x; - - if (surface_rect.y < workarea.y) - surface_rect.y = workarea.y; + _gdk_macos_monitor_clamp (GDK_MACOS_MONITOR (surface->best_monitor), &surface_rect); *x = surface_rect.x - surface->shadow_left; *y = surface_rect.y - surface->shadow_top; diff --git a/gdk/macos/gdkmacosmonitor-private.h b/gdk/macos/gdkmacosmonitor-private.h index dfde4142c0..1a4e197f76 100644 --- a/gdk/macos/gdkmacosmonitor-private.h +++ b/gdk/macos/gdkmacosmonitor-private.h @@ -41,6 +41,8 @@ void _gdk_macos_monitor_add_frame_callback (GdkMacosMonitor * GdkMacosSurface *surface); void _gdk_macos_monitor_remove_frame_callback (GdkMacosMonitor *self, GdkMacosSurface *surface); +void _gdk_macos_monitor_clamp (GdkMacosMonitor *self, + GdkRectangle *area); G_END_DECLS diff --git a/gdk/macos/gdkmacosmonitor.c b/gdk/macos/gdkmacosmonitor.c index bfec76b7ef..a9aba3634b 100644 --- a/gdk/macos/gdkmacosmonitor.c +++ b/gdk/macos/gdkmacosmonitor.c @@ -431,3 +431,29 @@ _gdk_macos_monitor_remove_frame_callback (GdkMacosMonitor *self, gdk_display_link_source_pause (self->display_link); } } + +void +_gdk_macos_monitor_clamp (GdkMacosMonitor *self, + GdkRectangle *area) +{ + GdkRectangle workarea; + GdkRectangle geom; + + g_return_if_fail (GDK_IS_MACOS_MONITOR (self)); + g_return_if_fail (area != NULL); + + gdk_macos_monitor_get_workarea (GDK_MONITOR (self), &workarea); + gdk_monitor_get_geometry (GDK_MONITOR (self), &geom); + + if (area->x + area->width > workarea.x + workarea.width) + area->x = workarea.x + workarea.width - area->width; + + if (area->x < workarea.x) + area->x = workarea.x; + + if (area->y + area->height > workarea.y + workarea.height) + area->y = workarea.y + workarea.height - area->height; + + if (area->y < workarea.y) + area->y = workarea.y; +} From 50e2a8239b100a26b3d91245249e8b27083481c2 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:36:17 -0800 Subject: [PATCH 29/56] macos: use GdkMacosBuffer for storing damage region The GdkMacosBuffer object already has storage for tracking the damage region as it is used in GdkMacosCairoContext to manually copy regions from the front buffer to the back buffer. This makes the GdkMacosGLContext also use that field so that we can easily drop old damage regions when the buffer is lost. This happens during resizes, monitor changes, etc. --- gdk/macos/gdkmacosglcontext-private.h | 2 -- gdk/macos/gdkmacosglcontext.c | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/gdk/macos/gdkmacosglcontext-private.h b/gdk/macos/gdkmacosglcontext-private.h index 8b3eac2ca6..7355ffef90 100644 --- a/gdk/macos/gdkmacosglcontext-private.h +++ b/gdk/macos/gdkmacosglcontext-private.h @@ -38,8 +38,6 @@ struct _GdkMacosGLContext { GdkGLContext parent_instance; - cairo_region_t *damage; - G_GNUC_BEGIN_IGNORE_DEPRECATIONS CGLContextObj cgl_context; G_GNUC_END_IGNORE_DEPRECATIONS diff --git a/gdk/macos/gdkmacosglcontext.c b/gdk/macos/gdkmacosglcontext.c index 5baff95a9b..ff7ae975c8 100644 --- a/gdk/macos/gdkmacosglcontext.c +++ b/gdk/macos/gdkmacosglcontext.c @@ -469,6 +469,7 @@ gdk_macos_gl_context_begin_frame (GdkDrawContext *context, buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); _gdk_macos_buffer_set_flipped (buffer, TRUE); + _gdk_macos_buffer_set_damage (buffer, region); /* Create our render target and bind it */ gdk_gl_context_make_current (GDK_GL_CONTEXT (self)); @@ -476,9 +477,6 @@ gdk_macos_gl_context_begin_frame (GdkDrawContext *context, GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->begin_frame (context, prefers_high_depth, region); - g_clear_pointer (&self->damage, cairo_region_destroy); - self->damage = g_steal_pointer (©); - gdk_gl_context_make_current (GDK_GL_CONTEXT (self)); CHECK_GL (NULL, glBindFramebuffer (GL_FRAMEBUFFER, self->fbo)); } @@ -531,8 +529,6 @@ gdk_macos_gl_context_surface_resized (GdkDrawContext *draw_context) g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); - g_clear_pointer (&self->damage, cairo_region_destroy); - if (self->cgl_context != NULL) CGLUpdateContext (self->cgl_context); } @@ -587,9 +583,16 @@ static cairo_region_t * gdk_macos_gl_context_get_damage (GdkGLContext *context) { GdkMacosGLContext *self = (GdkMacosGLContext *)context; + const cairo_region_t *damage; + GdkMacosBuffer *buffer; + GdkSurface *surface; - if (self->damage) - return cairo_region_copy (self->damage); + g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); + + if ((surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (context))) && + (buffer = GDK_MACOS_SURFACE (surface)->front) && + (damage = _gdk_macos_buffer_get_damage (buffer))) + return cairo_region_copy (damage); return GDK_GL_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->get_damage (context); } @@ -619,8 +622,6 @@ gdk_macos_gl_context_dispose (GObject *gobject) CGLDestroyContext (cgl_context); } - g_clear_pointer (&self->damage, cairo_region_destroy); - G_OBJECT_CLASS (gdk_macos_gl_context_parent_class)->dispose (gobject); } From 867f2de1948d5218708960f7952b6d0e54fd37a8 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:37:07 -0800 Subject: [PATCH 30/56] macos: leave a note about monitor configuration It can be helpful to see what the range of monitor values is when emulating the GDK coordinate system. --- gdk/macos/gdkmacosdisplay.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index ddcc274372..798633f33a 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -156,6 +156,10 @@ gdk_macos_display_update_bounds (GdkMacosDisplay *self) self->width = self->max_x - self->min_x; self->height = self->max_y - self->min_y; + GDK_NOTE (MISC, + g_message ("Displays reconfigured to bounds %d,%d %dx%d", + self->min_x, self->min_y, self->width, self->height)); + GDK_END_MACOS_ALLOC_POOL; } From b0d7889b206c040026b75daa0bf40856513a59ba Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:38:39 -0800 Subject: [PATCH 31/56] macos: improve initial placement of toplevels with parent This doesn't appear to happen much, but if it does it is nice to setup the window placement initially. Generally, transient-for is set after the creation of the toplevel rather than here. --- gdk/macos/gdkmacostoplevelsurface.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/gdk/macos/gdkmacostoplevelsurface.c b/gdk/macos/gdkmacostoplevelsurface.c index d89434593b..1759077563 100644 --- a/gdk/macos/gdkmacostoplevelsurface.c +++ b/gdk/macos/gdkmacostoplevelsurface.c @@ -648,10 +648,10 @@ _gdk_macos_toplevel_surface_new (GdkMacosDisplay *display, GdkMacosWindow *window; GdkMacosSurface *self; - NSScreen *screen; NSUInteger style_mask; NSRect content_rect; - NSRect screen_rect; + NSRect visible_frame; + NSScreen *screen; int nx; int ny; @@ -664,14 +664,17 @@ _gdk_macos_toplevel_surface_new (GdkMacosDisplay *display, NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable); - _gdk_macos_display_to_display_coords (display, x, y, &nx, &ny); + if (parent != NULL) + { + x += GDK_MACOS_SURFACE (parent)->root_x; + y += GDK_MACOS_SURFACE (parent)->root_y; + } + + _gdk_macos_display_to_display_coords (display, x, y + height, &nx, &ny); screen = _gdk_macos_display_get_screen_at_display_coords (display, nx, ny); - screen_rect = [screen visibleFrame]; - nx -= screen_rect.origin.x; - ny -= screen_rect.origin.y; - content_rect = NSMakeRect (nx, ny - height, width, height); - + visible_frame = [screen visibleFrame]; + content_rect = NSMakeRect (nx - visible_frame.origin.x, ny - visible_frame.origin.y, width, height); window = [[GdkMacosWindow alloc] initWithContentRect:content_rect styleMask:style_mask backing:NSBackingStoreBuffered @@ -707,13 +710,21 @@ _gdk_macos_toplevel_surface_attach_to_parent (GdkMacosToplevelSurface *self) { NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->transient_for)); NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self)); + int x, y; [parent addChildWindow:window ordered:NSWindowAbove]; if (GDK_SURFACE (self)->modal_hint) [window setLevel:NSModalPanelWindowLevel]; + surface->x = 0; + surface->y = 0; + _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display)); + _gdk_macos_display_position_surface (GDK_MACOS_DISPLAY (surface->display), + GDK_MACOS_SURFACE (surface), + &x, &y); + _gdk_macos_surface_move (GDK_MACOS_SURFACE (surface), x, y); } } From edc6790fbb32a78bc4f59632202aa94fcd17a088 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:40:21 -0800 Subject: [PATCH 32/56] macos: reduce chances for layout cycles We need to be more careful about when we request a layout because it can cause us to get into a layout cycle at maximum frame rate. --- gdk/macos/gdkmacossurface.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 14cb3de015..385775fe58 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -158,9 +158,6 @@ _gdk_macos_surface_reposition_children (GdkMacosSurface *self) if (GDK_IS_MACOS_POPUP_SURFACE (child)) _gdk_macos_popup_surface_reposition (GDK_MACOS_POPUP_SURFACE (child)); } - - if (GDK_IS_POPUP (self) && self->did_initial_present) - gdk_surface_request_layout (GDK_SURFACE (self)); } static void @@ -851,7 +848,6 @@ _gdk_macos_surface_configure (GdkMacosSurface *self) g_clear_object (&self->front); _gdk_surface_update_size (surface); - gdk_surface_request_layout (surface); gdk_surface_invalidate_rect (surface, NULL); } @@ -1110,7 +1106,6 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL); self->in_change_monitor = FALSE; - _gdk_macos_surface_request_frame (self); } GdkMonitor * From c2d1a21f9c38aa467346629fa11354661449dd77 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:42:09 -0800 Subject: [PATCH 33/56] macos: use parent frame clock again We do actually need the parent frame clock here because it is the way we ensure that we get layout called for our popup surfaces at the same time as the parent surface. --- gdk/macos/gdkmacossurface.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 385775fe58..8a1cf3747f 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -621,7 +621,10 @@ _gdk_macos_surface_new (GdkMacosDisplay *display, g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL); - frame_clock = _gdk_frame_clock_idle_new (); + if (parent != NULL) + frame_clock = g_object_ref (parent->frame_clock); + else + frame_clock = _gdk_frame_clock_idle_new (); switch (surface_type) { From cb99370ce4b2af715c2b6debf3e54a4bf2d9a2fb Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:43:33 -0800 Subject: [PATCH 34/56] macos: handle transient-for from configure We failed to handle the toplevel with transient-for case here which could cause our X/Y calculations to be off in other areas such as best monitor detection. --- gdk/macos/gdkmacossurface.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 8a1cf3747f..92d6cd1c34 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -811,8 +811,9 @@ _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self) void _gdk_macos_surface_configure (GdkMacosSurface *self) { - GdkMacosDisplay *display; GdkSurface *surface = (GdkSurface *)self; + GdkMacosDisplay *display; + GdkMacosSurface *parent; NSRect frame_rect; NSRect content_rect; @@ -821,6 +822,13 @@ _gdk_macos_surface_configure (GdkMacosSurface *self) if (GDK_SURFACE_DESTROYED (self)) return; + if (surface->parent != NULL) + parent = GDK_MACOS_SURFACE (surface->parent); + else if (surface->transient_for != NULL) + parent = GDK_MACOS_SURFACE (surface->transient_for); + else + parent = NULL; + display = GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display); frame_rect = [self->window frame]; content_rect = [self->window contentRectForFrameRect:frame_rect]; @@ -830,10 +838,10 @@ _gdk_macos_surface_configure (GdkMacosSurface *self) content_rect.origin.y + content_rect.size.height, &self->root_x, &self->root_y); - if (surface->parent != NULL) + if (parent != NULL) { - surface->x = self->root_x - GDK_MACOS_SURFACE (surface->parent)->root_x; - surface->y = self->root_y - GDK_MACOS_SURFACE (surface->parent)->root_y; + surface->x = self->root_x - parent->root_x; + surface->y = self->root_y - parent->root_y; } else { From 46da364289b2ce8aabe96b364f721944dbe3c90e Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:45:44 -0800 Subject: [PATCH 35/56] macos: make move_resize possibly idempotent We need to handle the case where we might be racing against an incoming configure event due to how notifications are queued from the display server. Rather than calling configure (and possibly causing other things to move around) this just queries the display server directly for the coordinates that we care about. Additionally, we can display:NO as we are in control of all the display process now using CALayer. --- gdk/macos/gdkmacossurface.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 92d6cd1c34..c3916cdc88 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -939,13 +939,27 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self, NSRect frame_rect; gboolean ignore_move; gboolean ignore_size; + GdkRectangle current; g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); - ignore_move = (x == -1 || (x == self->root_x)) && - (y == -1 || (y == self->root_y)); - ignore_size = (width == -1 || (width == surface->width)) && - (height == -1 || (height == surface->height)); + /* Query for up-to-date values in case we're racing against + * an incoming frame notify which could be queued behind whatever + * we're processing right now. + */ + frame_rect = [self->window frame]; + content_rect = [self->window contentRectForFrameRect:frame_rect]; + _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display), + content_rect.origin.x, content_rect.origin.y, + ¤t.x, ¤t.y); + current.width = content_rect.size.width; + current.height = content_rect.size.height; + + /* Check if we can ignore the operation all together */ + ignore_move = (x == -1 || (x == current.x)) && + (y == -1 || (y == current.y)); + ignore_size = (width == -1 || (width == current.width)) && + (height == -1 || (height == current.height)); if (ignore_move && ignore_size) return; @@ -953,23 +967,21 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self, display = gdk_surface_get_display (surface); if (width == -1) - width = surface->width; + width = current.width; if (height == -1) - height = surface->height; + height = current.height; if (x == -1) - x = self->root_x; + x = current.x; if (y == -1) - y = self->root_y; + y = current.y; _gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display), x, y + height, &x, &y); - content_rect = [self->window contentRectForFrameRect:[self->window frame]]; - if (!ignore_move) content_rect.origin = NSMakePoint (x, y); @@ -977,7 +989,7 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self, content_rect.size = NSMakeSize (width, height); frame_rect = [self->window frameRectForContentRect:content_rect]; - [self->window setFrame:frame_rect display:YES]; + [self->window setFrame:frame_rect display:NO]; } void From 2961cc44c50e431bc56336d6b5492d8a39424b0c Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:47:27 -0800 Subject: [PATCH 36/56] macos: move children when monitor changes We can rely on other code to move monitors, but specifically with children we want to try harder to move them as a group and keep positioning in tact. --- gdk/macos/gdkmacossurface.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index c3916cdc88..bca3c9210a 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -1060,6 +1060,7 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) self->in_change_monitor = TRUE; _gdk_macos_surface_cancel_frame (self); + _gdk_macos_surface_configure (self); rect.x = self->root_x; rect.y = self->root_y; @@ -1123,9 +1124,39 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) g_message ("Surface \"%s\" moved to monitor \"%s\"", self->title ? self->title : "unknown", gdk_monitor_get_connector (best))); + + _gdk_macos_surface_configure (self); + + if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self))) + { + _gdk_macos_surface_request_frame (self); + gdk_surface_request_layout (GDK_SURFACE (self)); + } + + for (const GList *iter = GDK_SURFACE (self)->children; + iter != NULL; + iter = iter->next) + { + GdkMacosSurface *child = iter->data; + GdkRectangle area; + + g_set_object (&child->best_monitor, best); + + area.x = self->root_x + GDK_SURFACE (child)->x + child->shadow_left; + area.y = self->root_y + GDK_SURFACE (child)->y + child->shadow_top; + area.width = GDK_SURFACE (child)->width - child->shadow_left - child->shadow_right; + area.height = GDK_SURFACE (child)->height - child->shadow_top - child->shadow_bottom; + + _gdk_macos_monitor_clamp (GDK_MACOS_MONITOR (best), &area); + + area.x -= child->shadow_left; + area.y -= child->shadow_top; + + _gdk_macos_surface_move (child, area.x, area.y); + gdk_surface_invalidate_rect (GDK_SURFACE (child), NULL); + } } - _gdk_macos_surface_configure (self); gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL); self->in_change_monitor = FALSE; From 0aabf47f095e5545ed29fcf70efc0869b4e37d40 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 00:48:06 -0800 Subject: [PATCH 37/56] macos: invalidate surface contents when mapping --- gdk/macos/gdkmacossurface.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index bca3c9210a..4357fbf1a9 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -884,6 +884,7 @@ _gdk_macos_surface_show (GdkMacosSurface *self) { gdk_surface_set_is_mapped (GDK_SURFACE (self), TRUE); gdk_surface_request_layout (GDK_SURFACE (self)); + gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL); gdk_surface_thaw_updates (GDK_SURFACE (self)); } } From 9dbd79f2d8fc701c02211550b1243775dfe5a97e Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 01:04:53 -0800 Subject: [PATCH 38/56] macos: clear window stack before requesting motion We want to ensure that we recalculate the sort order of windows before processing the motion. Generally this would be done in response from the display server in GdkMacosWindow, but I've seen it possible to race there. --- gdk/macos/gdkmacossurface.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 4357fbf1a9..6c116ed817 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -1244,11 +1244,15 @@ _gdk_macos_surface_get_buffer (GdkMacosSurface *self) static void _gdk_macos_surface_do_delayed_show (GdkMacosSurface *self) { + GdkSurface *surface = (GdkSurface *)self; + g_assert (GDK_IS_MACOS_SURFACE (self)); self->show_on_next_swap = FALSE; [self->window showAndMakeKey:YES]; - gdk_surface_request_motion (GDK_SURFACE (self)); + + _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display)); + gdk_surface_request_motion (surface); } void From 60aceb984f58e044c21d6e4ca1afc7b13df30f08 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 19:59:15 -0800 Subject: [PATCH 39/56] macos: require input region to become key Some things cannot become key windows (like tooltips). We can use the input_region existence to determine if we should allow it as a key window. --- gdk/macos/GdkMacosWindow.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index 3ed60cb049..836ded78e8 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -253,7 +253,8 @@ typedef NSString *CALayerContentsGravity; -(BOOL)canBecomeKeyWindow { - return GDK_IS_TOPLEVEL (gdk_surface) || GDK_IS_POPUP (gdk_surface); + return GDK_IS_TOPLEVEL (gdk_surface) || + (GDK_IS_POPUP (gdk_surface) && GDK_SURFACE (gdk_surface)->input_region != NULL); } -(void)showAndMakeKey:(BOOL)makeKey From 2f873052f9664f7abe50c33b94b6f81646b0b14f Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 2 Mar 2022 20:32:19 -0800 Subject: [PATCH 40/56] macos: fix cursor blink time The value from settings is for the duration of the blink period, not the timeout. This fixes the blink lasting longer than 10 seconds. --- gdk/macos/gdkmacosdisplay-settings.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-settings.c b/gdk/macos/gdkmacosdisplay-settings.c index e6714f0a14..53d6df0672 100644 --- a/gdk/macos/gdkmacosdisplay-settings.c +++ b/gdk/macos/gdkmacosdisplay-settings.c @@ -34,7 +34,7 @@ typedef struct const char *font_name; int xft_dpi; int double_click_time; - int cursor_blink_timeout; + int cursor_blink_time; guint enable_animations : 1; guint shell_shows_desktop : 1; guint shell_shows_menubar : 1; @@ -65,9 +65,9 @@ _gdk_macos_settings_load (GdkMacosSettings *settings) ival = [defaults integerForKey:@"NSTextInsertionPointBlinkPeriod"]; if (ival > 0) - settings->cursor_blink_timeout = ival; + settings->cursor_blink_time = ival; else - settings->cursor_blink_timeout = 1000; + settings->cursor_blink_time = 1000; settings->primary_button_warps_slider = [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollerPagingBehavior"] == YES; @@ -124,9 +124,9 @@ _gdk_macos_display_get_setting (GdkMacosDisplay *self, g_value_set_int (value, current_settings.xft_dpi); ret = TRUE; } - else if (strcmp (setting, "gtk-cursor-blink-timeout") == 0) + else if (strcmp (setting, "gtk-cursor-blink-time") == 0) { - g_value_set_int (value, current_settings.cursor_blink_timeout); + g_value_set_int (value, current_settings.cursor_blink_time); ret = TRUE; } else if (strcmp (setting, "gtk-double-click-time") == 0) From cb386ec2a26de106822c24fd3cc9a276c978d9ad Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 7 Mar 2022 14:31:23 -0800 Subject: [PATCH 41/56] macos: do not focus new window when resigning main This can get in the way of how we track changes while events are actively processing. Instead, we may want to delay this until the next main loop idle and then check to see if we have a main window as the NSNotification may have come in right after this. --- gdk/macos/gdkmacosdisplay.c | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index 798633f33a..bc58c2a6f3 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -491,8 +491,6 @@ void _gdk_macos_display_surface_resigned_main (GdkMacosDisplay *self, GdkMacosSurface *surface) { - GdkMacosSurface *new_surface = NULL; - g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); g_return_if_fail (GDK_IS_MACOS_SURFACE (surface)); @@ -500,40 +498,6 @@ _gdk_macos_display_surface_resigned_main (GdkMacosDisplay *self, g_queue_unlink (&self->main_surfaces, &surface->main); _gdk_macos_display_clear_sorting (self); - - if (GDK_SURFACE (surface)->transient_for && - gdk_surface_get_mapped (GDK_SURFACE (surface)->transient_for)) - { - new_surface = GDK_MACOS_SURFACE (GDK_SURFACE (surface)->transient_for); - } - else - { - const GList *surfaces = _gdk_macos_display_get_surfaces (self); - - for (const GList *iter = surfaces; iter; iter = iter->next) - { - GdkMacosSurface *item = iter->data; - - g_assert (GDK_IS_MACOS_SURFACE (item)); - - if (item == surface) - continue; - - if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (item))) - { - new_surface = item; - break; - } - } - } - - if (new_surface != NULL) - { - NSWindow *nswindow = _gdk_macos_surface_get_native (new_surface); - [nswindow makeKeyAndOrderFront:nswindow]; - } - - _gdk_macos_display_clear_sorting (self); } static GdkSurface * From 913f6d4a4fc63f2ba19c00b8db6d9c3ab268adc9 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 9 Mar 2022 13:19:22 -0800 Subject: [PATCH 42/56] macos: allow dropping NSEvent without propagation There are cases we might want to consume a NSEvent without creating a GdkEvent or passing it along to the NSApplication for processing. This creates a new value we can use and check against to propagate that without having to do out parameters at the slightly odd invalid pointer value for a GdkEvent (similar to how MMAP_FAILED is done). --- gdk/macos/gdkmacosdisplay-private.h | 2 ++ gdk/macos/gdkmacosdisplay.c | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h index ef17618d61..72b5f5cd57 100644 --- a/gdk/macos/gdkmacosdisplay-private.h +++ b/gdk/macos/gdkmacosdisplay-private.h @@ -43,6 +43,8 @@ G_BEGIN_DECLS #define GIC_FILTER_PASSTHRU 0 #define GIC_FILTER_FILTERED 1 +#define GDK_MACOS_EVENT_DROP (GdkEvent *)GSIZE_TO_POINTER(1) + struct _GdkMacosDisplay { GdkDisplay parent_instance; diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index bc58c2a6f3..9fbfac49ed 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -314,7 +314,11 @@ gdk_macos_display_queue_events (GdkDisplay *display) { GdkEvent *event = _gdk_macos_display_translate (self, nsevent); - if (event != NULL) + if (event == GDK_MACOS_EVENT_DROP) + { + [nsevent release]; + } + else if (event != NULL) { push_nsevent (event, nsevent); _gdk_windowing_got_event (GDK_DISPLAY (self), From 659832ccab1bfab984807454f50e927aeda41666 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 9 Mar 2022 13:20:53 -0800 Subject: [PATCH 43/56] macos: drop enter/exit when in manual drag/resize If we are in a manual resize/drag then we don't want to generate crossing events as they can just confuse things. --- gdk/macos/gdkmacosdisplay-translate.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index e3fb03fea5..fe4701c39d 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -1200,7 +1200,11 @@ _gdk_macos_display_translate (GdkMacosDisplay *self, GdkDevice *pointer = gdk_seat_get_pointer (seat); GdkDeviceGrabInfo *grab = _gdk_display_get_last_device_grab (GDK_DISPLAY (self), pointer); - if (grab == NULL) + if ([(GdkMacosWindow *)window isInManualResizeOrMove]) + { + ret = GDK_MACOS_EVENT_DROP; + } + else if (grab == NULL) { if (event_type == NSEventTypeMouseExited) [[NSCursor arrowCursor] set]; From 5efa8071d6f372e29f3df9b43a4a8bf990b7bb53 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 10 Mar 2022 03:20:07 -0800 Subject: [PATCH 44/56] macos: queue all pending events Rather than process these a single event at a time, queue all of the outstanding events from the NSEvent queue. --- gdk/macos/gdkmacosdisplay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index 9fbfac49ed..d1398335fd 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -310,7 +310,7 @@ gdk_macos_display_queue_events (GdkDisplay *display) g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); - if ((nsevent = _gdk_macos_event_source_get_pending ())) + while ((nsevent = _gdk_macos_event_source_get_pending ())) { GdkEvent *event = _gdk_macos_display_translate (self, nsevent); From 5c2d9d6f19eaaa03ce9d45a72aded8f3e527ecd9 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 10 Mar 2022 03:21:12 -0800 Subject: [PATCH 45/56] macos: actually drop unnecessary momentum events These would get passed along to the NSApplication which we don't really need to have happen. Denote it as such. --- gdk/macos/gdkmacosdisplay-translate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index fe4701c39d..4ad12ba778 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -628,7 +628,7 @@ fill_scroll_event (GdkMacosDisplay *self, * handle those internally. */ if (phase == 0 && momentumPhase != 0) - return NULL; + return GDK_MACOS_EVENT_DROP; seat = gdk_display_get_default_seat (GDK_DISPLAY (self)); pointer = gdk_seat_get_pointer (seat); From f47e6dda785b2c83031bead9163517d6fe51e793 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 10 Mar 2022 08:04:14 -0800 Subject: [PATCH 46/56] macos: make transient-for key window when hiding surface This only handled the popover case before and not the transient-for case. --- gdk/macos/gdkmacossurface.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 6c116ed817..449a3b35a5 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -219,10 +219,17 @@ gdk_macos_surface_hide (GdkSurface *surface) if (was_key) { + GdkSurface *parent; + + if (GDK_IS_TOPLEVEL (surface)) + parent = surface->transient_for; + else + parent = surface->parent; + /* Return key input to the parent window if necessary */ - if (surface->parent != NULL && GDK_SURFACE_IS_MAPPED (surface->parent)) + if (parent != NULL && GDK_SURFACE_IS_MAPPED (parent)) { - GdkMacosWindow *parentWindow = GDK_MACOS_SURFACE (surface->parent)->window; + GdkMacosWindow *parentWindow = GDK_MACOS_SURFACE (parent)->window; [parentWindow showAndMakeKey:YES]; } From 4bc593c761224c533a924468f59819b863c103ae Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 10 Mar 2022 23:49:48 -0800 Subject: [PATCH 47/56] macos: set main window in addition to key If we are showing the window, we might also want to make it the main window for the application when shown. --- gdk/macos/GdkMacosWindow.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index 836ded78e8..048c00c407 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -266,6 +266,9 @@ typedef NSString *CALayerContentsGravity; else [self orderFront:nil]; + if (makeKey && [self canBecomeMainWindow]) + [self makeMainWindow]; + inShowOrHide = NO; [self checkSendEnterNotify]; From 023bf9aea71f1f9a0d06d9eeb274a100cf36f405 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 10 Mar 2022 18:58:10 -0800 Subject: [PATCH 48/56] macos: fix resize when using server-side decorations If we are using NSWindow titled windows, we don't end up waking up the frame clock when the window is resized on the display server. This ensures that we do that after getting a notification of resize. --- gdk/macos/GdkMacosWindow.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index 048c00c407..7aee71f630 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -377,9 +377,10 @@ typedef NSString *CALayerContentsGravity; _gdk_macos_surface_configure ([self gdkSurface]); } -- (void)windowDidResize:(NSNotification *)notification +-(void)windowDidResize:(NSNotification *)notification { _gdk_macos_surface_configure ([self gdkSurface]); + gdk_surface_request_layout (GDK_SURFACE (gdk_surface)); } /* Used by gdkmacosdisplay-translate.c to decide if our sendEvent() handler From d62313e75ce528cadbea00b148e35d7e62dfef61 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 10 Mar 2022 23:16:38 -0800 Subject: [PATCH 49/56] macos: request layout with server-side decoration If we have server-side decorations we might need to request a layout in response to the resize notification. We don't need to do this in other cases because we already handle that in the process of doing the resize (and that code is that way because of delayed delivery of NSNotification). --- gdk/macos/GdkMacosWindow.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index 7aee71f630..c93be7f49e 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -379,8 +379,15 @@ typedef NSString *CALayerContentsGravity; -(void)windowDidResize:(NSNotification *)notification { - _gdk_macos_surface_configure ([self gdkSurface]); - gdk_surface_request_layout (GDK_SURFACE (gdk_surface)); + _gdk_macos_surface_configure (gdk_surface); + + /* If we're using server-side decorations, this notification is coming + * in from a display-side change. We need to request a layout in + * addition to the configure event. + */ + if (GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface) && + GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface)->decorated) + gdk_surface_request_layout (GDK_SURFACE (gdk_surface)); } /* Used by gdkmacosdisplay-translate.c to decide if our sendEvent() handler From 9462b3fea2afb3b13d5168046f199f0f485b5700 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 11 Mar 2022 00:50:10 -0800 Subject: [PATCH 50/56] macos: select new key window after processing events If we closed a key window in response to events, we need to denote another window as the new key window. This is easiest to do from an idle so that we don't clobber notification pairs of "did resign"/"did become" key window. We have a sorted set of surfaces by display server stacking, so we can take the first one we come across that is already mapped and re-show it to become key/main. --- gdk/macos/gdkmacosdisplay-private.h | 3 +++ gdk/macos/gdkmacosdisplay.c | 32 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h index 72b5f5cd57..1edff58d04 100644 --- a/gdk/macos/gdkmacosdisplay-private.h +++ b/gdk/macos/gdkmacosdisplay-private.h @@ -84,6 +84,9 @@ struct _GdkMacosDisplay int min_y; int max_x; int max_y; + + /* A GSource to select a new main/key window */ + guint select_key_in_idle; }; struct _GdkMacosDisplayClass diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index d1398335fd..6c51ef901e 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -413,6 +413,34 @@ _gdk_macos_display_surface_became_key (GdkMacosDisplay *self, gdk_surface_request_motion (GDK_SURFACE (surface)); } +static gboolean +select_key_in_idle_cb (gpointer data) +{ + GdkMacosDisplay *self = data; + + g_assert (GDK_IS_MACOS_DISPLAY (self)); + + self->select_key_in_idle = 0; + + if (self->keyboard_surface == NULL) + { + const GList *surfaces = _gdk_macos_display_get_surfaces (self); + + for (const GList *iter = surfaces; iter; iter = iter->next) + { + GdkMacosSurface *surface = iter->data; + + if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (surface))) + { + [surface->window showAndMakeKey:YES]; + break; + } + } + } + + return G_SOURCE_REMOVE; +} + void _gdk_macos_display_surface_resigned_key (GdkMacosDisplay *self, GdkMacosSurface *surface) @@ -457,6 +485,9 @@ _gdk_macos_display_surface_resigned_key (GdkMacosDisplay *self, } _gdk_macos_display_clear_sorting (self); + + if (self->select_key_in_idle == 0) + self->select_key_in_idle = g_idle_add (select_key_in_idle_cb, self); } /* Raises a transient window. @@ -564,6 +595,7 @@ gdk_macos_display_finalize (GObject *object) _gdk_macos_display_feedback_destroy (self); + g_clear_handle_id (&self->select_key_in_idle, g_source_remove); g_clear_pointer (&self->active_drags, g_hash_table_unref); g_clear_pointer (&self->active_drops, g_hash_table_unref); g_clear_object (&GDK_DISPLAY (self)->clipboard); From 2c630a74cd3ce405363c1288a103dac60adcbc2c Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 10 Mar 2022 17:26:56 -0800 Subject: [PATCH 51/56] macos: fix window activation during shadow click-through If we are clicking through the shadow of a window, we need to take special care to not raise the old window on mouseUp. This is normally done by the display server for us, so we need to use the proper API that is public to handle this (rather than CGSSetWindowTags()). Doing so requires us to dispatch the event to the NSView and then cancel the activcation from the mouseDown: event there. --- gdk/macos/GdkMacosView.c | 14 ++++++++++++++ gdk/macos/GdkMacosWindow.c | 10 ++++++++-- gdk/macos/GdkMacosWindow.h | 1 + gdk/macos/gdkmacosdisplay-translate.c | 24 ++++++++++++++++++++---- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/gdk/macos/GdkMacosView.c b/gdk/macos/GdkMacosView.c index 52a55a7cef..18de78aa19 100644 --- a/gdk/macos/GdkMacosView.c +++ b/gdk/macos/GdkMacosView.c @@ -24,6 +24,7 @@ #import "GdkMacosLayer.h" #import "GdkMacosView.h" +#import "GdkMacosWindow.h" @implementation GdkMacosView @@ -56,6 +57,19 @@ return NO; } +-(void)mouseDown:(NSEvent *)nsevent +{ + if ([(GdkMacosWindow *)[self window] needsMouseDownQuirk]) + /* We should only hit this when we are trying to click through + * the shadow of a window into another window. Just request + * that the application not activate this window on mouseUp. + * See gdkmacosdisplay-translate.c for the other half of this. + */ + [NSApp preventWindowOrdering]; + else + [super mouseDown:nsevent]; +} + -(void)setFrame:(NSRect)rect { [super setFrame:rect]; diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index c93be7f49e..23b3f5a3f3 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -262,9 +262,9 @@ typedef NSString *CALayerContentsGravity; inShowOrHide = YES; if (makeKey && [self canBecomeKeyWindow]) - [self makeKeyAndOrderFront:nil]; + [self makeKeyAndOrderFront:self]; else - [self orderFront:nil]; + [self orderFront:self]; if (makeKey && [self canBecomeMainWindow]) [self makeMainWindow]; @@ -830,4 +830,10 @@ typedef NSString *CALayerContentsGravity; [(GdkMacosView *)[self contentView] swapBuffer:buffer withDamage:damage]; } +-(BOOL)needsMouseDownQuirk +{ + return GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface) && + !GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface)->decorated; +} + @end diff --git a/gdk/macos/GdkMacosWindow.h b/gdk/macos/GdkMacosWindow.h index cb8b2efad1..3a514ea857 100644 --- a/gdk/macos/GdkMacosWindow.h +++ b/gdk/macos/GdkMacosWindow.h @@ -69,5 +69,6 @@ -(BOOL)trackManualResize; -(void)setDecorated:(BOOL)decorated; -(void)swapBuffer:(GdkMacosBuffer *)buffer withDamage:(const cairo_region_t *)damage; +-(BOOL)needsMouseDownQuirk; @end diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index 4ad12ba778..9caddca811 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -1159,15 +1159,31 @@ _gdk_macos_display_translate (GdkMacosDisplay *self, if (test_resize (nsevent, surface, x, y)) return NULL; - if ((event_type == NSEventTypeRightMouseDown || - event_type == NSEventTypeOtherMouseDown || - event_type == NSEventTypeLeftMouseDown)) + if (event_type == NSEventTypeRightMouseDown || + event_type == NSEventTypeOtherMouseDown || + event_type == NSEventTypeLeftMouseDown) { if (![NSApp isActive]) [NSApp activateIgnoringOtherApps:YES]; if (![window isKeyWindow]) - [window makeKeyWindow]; + { + NSWindow *orig_window = [nsevent window]; + + /* To get NSApp to supress activating the window we might + * have clicked through the shadow of, we need to dispatch + * the event and handle it in GdkMacosView:mouseDown to call + * [NSApp preventWindowOrdering]. Calling it here will not + * do anything as the event is not registered. + */ + if (orig_window && + GDK_IS_MACOS_WINDOW (orig_window) && + [(GdkMacosWindow *)orig_window needsMouseDownQuirk]) + [NSApp sendEvent:nsevent]; + + [window showAndMakeKey:YES]; + _gdk_macos_display_clear_sorting (self); + } } switch ((int)event_type) From 9456f6eea1bbdb3ce847c97e9ba7cb67c1aa526f Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 11 Mar 2022 17:53:42 -0800 Subject: [PATCH 52/56] macos: fix attachment of popups to parents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We had code to do it and it never actually got used correctly. This ensures that the popup services are attached to the parents so that they get proper stacking orders when displayed. Additionally, it fixes popups from being shown as their own windows in Exposé. --- gdk/macos/gdkmacospopupsurface.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/gdk/macos/gdkmacospopupsurface.c b/gdk/macos/gdkmacospopupsurface.c index dc6610b398..d489c671ed 100644 --- a/gdk/macos/gdkmacospopupsurface.c +++ b/gdk/macos/gdkmacospopupsurface.c @@ -34,6 +34,7 @@ struct _GdkMacosPopupSurface { GdkMacosSurface parent_instance; GdkPopupLayout *layout; + guint attached : 1; }; struct _GdkMacosPopupSurfaceClass @@ -138,6 +139,9 @@ gdk_macos_popup_surface_present (GdkPopup *popup, if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self))) return TRUE; + if (!self->attached && GDK_SURFACE (self)->parent != NULL) + _gdk_macos_popup_surface_attach_to_parent (self); + if (GDK_SURFACE (self)->autohide) { GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (popup)); @@ -203,6 +207,19 @@ enum { LAST_PROP, }; +static void +_gdk_macos_popup_surface_hide (GdkSurface *surface) +{ + GdkMacosPopupSurface *self = (GdkMacosPopupSurface *)surface; + + g_assert (GDK_IS_MACOS_POPUP_SURFACE (self)); + + if (self->attached) + _gdk_macos_popup_surface_detach_from_parent (self); + + GDK_SURFACE_CLASS (_gdk_macos_popup_surface_parent_class)->hide (surface); +} + static void _gdk_macos_popup_surface_finalize (GObject *object) { @@ -270,11 +287,14 @@ static void _gdk_macos_popup_surface_class_init (GdkMacosPopupSurfaceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + GdkSurfaceClass *surface_class = GDK_SURFACE_CLASS (klass); object_class->finalize = _gdk_macos_popup_surface_finalize; object_class->get_property = _gdk_macos_popup_surface_get_property; object_class->set_property = _gdk_macos_popup_surface_set_property; + surface_class->hide = _gdk_macos_popup_surface_hide; + gdk_popup_install_properties (object_class, LAST_PROP); } @@ -364,6 +384,8 @@ _gdk_macos_popup_surface_attach_to_parent (GdkMacosPopupSurface *self) [parent addChildWindow:window ordered:NSWindowAbove]; + self->attached = TRUE; + _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display)); } } @@ -385,6 +407,8 @@ _gdk_macos_popup_surface_detach_from_parent (GdkMacosPopupSurface *self) [parent removeChildWindow:window]; + self->attached = FALSE; + _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display)); } } From 36730537cc9de22f712829b8ed10b8efc57c8be5 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 11 Mar 2022 18:01:05 -0800 Subject: [PATCH 53/56] macos: fix window level for popups This comment isn't really accurate anymore it seems, so we can start setting the proper stacking order for popups now. --- gdk/macos/gdkmacospopupsurface.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/gdk/macos/gdkmacospopupsurface.c b/gdk/macos/gdkmacospopupsurface.c index d489c671ed..797fe872da 100644 --- a/gdk/macos/gdkmacospopupsurface.c +++ b/gdk/macos/gdkmacospopupsurface.c @@ -347,13 +347,7 @@ _gdk_macos_popup_surface_new (GdkMacosDisplay *display, [window setBackgroundColor:[NSColor clearColor]]; [window setDecorated:NO]; -#if 0 - /* NOTE: We could set these to be popup level, but then - * [NSApp orderedWindows] would not give us the windows - * back with the stacking order applied. - */ [window setLevel:NSPopUpMenuWindowLevel]; -#endif self = g_object_new (GDK_TYPE_MACOS_POPUP_SURFACE, "display", display, From a9c89fa23e883823e7967555ba905a005b5351e8 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 11 Mar 2022 18:07:34 -0800 Subject: [PATCH 54/56] macos: dont steal key window from NSPanel Or we risk making it really difficult to use native file choosers. --- gdk/macos/gdkmacosdisplay-private.h | 5 +++++ gdk/macos/gdkmacosdisplay.c | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h index 1edff58d04..83ae435e49 100644 --- a/gdk/macos/gdkmacosdisplay-private.h +++ b/gdk/macos/gdkmacosdisplay-private.h @@ -87,6 +87,11 @@ struct _GdkMacosDisplay /* A GSource to select a new main/key window */ guint select_key_in_idle; + + /* Note if we have a key window that is not a GdkMacosWindow + * such as a NSPanel used for native dialogs. + */ + guint key_window_is_foregin : 1; }; struct _GdkMacosDisplayClass diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index 6c51ef901e..d85a744563 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -422,6 +422,10 @@ select_key_in_idle_cb (gpointer data) self->select_key_in_idle = 0; + /* Don't steal focus from NSPanel, etc */ + if (self->key_window_is_foregin) + return G_SOURCE_REMOVE; + if (self->keyboard_surface == NULL) { const GList *surfaces = _gdk_macos_display_get_surfaces (self); @@ -960,11 +964,16 @@ _gdk_macos_display_get_surfaces (GdkMacosDisplay *self) NSArray *array = [NSApp orderedWindows]; GQueue sorted = G_QUEUE_INIT; + self->key_window_is_foregin = FALSE; + for (id obj in array) { NSWindow *nswindow = (NSWindow *)obj; GdkMacosSurface *surface; + if ([nswindow isKeyWindow]) + self->key_window_is_foregin = !GDK_IS_MACOS_WINDOW (nswindow); + if (!GDK_IS_MACOS_WINDOW (nswindow)) continue; From ccbaa020ff61876a5326427b950194972c8677d0 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 11 Mar 2022 18:25:47 -0800 Subject: [PATCH 55/56] macos: pass events to foreign windows --- gdk/macos/gdkmacosdisplay-translate.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index 9caddca811..7a0b84ccdb 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -1086,6 +1086,7 @@ _gdk_macos_display_translate (GdkMacosDisplay *self, GdkMacosSurface *surface; GdkMacosWindow *window; NSEventType event_type; + NSWindow *event_window; GdkEvent *ret = NULL; int x; int y; @@ -1128,6 +1129,15 @@ _gdk_macos_display_translate (GdkMacosDisplay *self, return NULL; } + /* If the event was delivered to NSWindow that is foreign (or rather, + * Cocoa native), then we should pass the event along to that window. + */ + if ((event_window = [nsevent window]) && !GDK_IS_MACOS_WINDOW (event_window)) + return NULL; + + /* If we can't find a GdkSurface to deliver the event to, then we + * should pass it along to the NSApp. + */ if (!(surface = find_surface_for_ns_event (self, nsevent, &x, &y))) return NULL; From 73d5f7061b3021ccf0c66807ed33630b4fc6ac2e Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 11 Mar 2022 22:36:26 -0800 Subject: [PATCH 56/56] macos: exclude popups from window list This probably only matters if you do window list integration for the global menu on macOS. --- gdk/macos/gdkmacospopupsurface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacospopupsurface.c b/gdk/macos/gdkmacospopupsurface.c index 797fe872da..061af9d85d 100644 --- a/gdk/macos/gdkmacospopupsurface.c +++ b/gdk/macos/gdkmacospopupsurface.c @@ -346,7 +346,7 @@ _gdk_macos_popup_surface_new (GdkMacosDisplay *display, [window setOpaque:NO]; [window setBackgroundColor:[NSColor clearColor]]; [window setDecorated:NO]; - + [window setExcludedFromWindowsMenu:YES]; [window setLevel:NSPopUpMenuWindowLevel]; self = g_object_new (GDK_TYPE_MACOS_POPUP_SURFACE,