[gtk/wip/chergert/quartz4u] start bringing over old style event loop
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/chergert/quartz4u] start bringing over old style event loop
- Date: Thu, 30 Apr 2020 00:25:24 +0000 (UTC)
commit c91c3de21b2b9b70fa81f824a977713fe4126800
Author: Christian Hergert <chergert redhat com>
Date: Wed Apr 29 17:24:45 2020 -0700
start bringing over old style event loop
this still needs a bunch of work to create events (and to bring them
back from the actual GdkMacosWindow). But at least we can get a widnow
displayed on the screen.
gdk/macos/gdkmacosdisplay-private.h | 1 +
gdk/macos/gdkmacosdisplay.c | 41 +-
gdk/macos/gdkmacoseventloop-private.h | 31 -
gdk/macos/gdkmacoseventloop.c | 1044 -----------------------------
gdk/macos/gdkmacoseventsource-private.h | 12 +-
gdk/macos/gdkmacoseventsource.c | 1095 +++++++++++++++++++++++++++++--
6 files changed, 1081 insertions(+), 1143 deletions(-)
---
diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h
index f203925764..b7503eed33 100644
--- a/gdk/macos/gdkmacosdisplay-private.h
+++ b/gdk/macos/gdkmacosdisplay-private.h
@@ -28,6 +28,7 @@ G_BEGIN_DECLS
GdkDisplay *_gdk_macos_display_open (const gchar *display_name);
int _gdk_macos_display_get_fd (GdkMacosDisplay *self);
+void _gdk_macos_display_queue_events (GdkMacosDisplay *self);
void _gdk_macos_display_to_display_coords (GdkMacosDisplay *self,
int x,
int y,
diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c
index 0c09155a7d..9171a490f0 100644
--- a/gdk/macos/gdkmacosdisplay.c
+++ b/gdk/macos/gdkmacosdisplay.c
@@ -20,7 +20,8 @@
#include "config.h"
#include <AppKit/AppKit.h>
-#include <gdk/gdk.h>
+
+#include "gdkeventsprivate.h"
#include "gdkmacoscairocontext-private.h"
#include "gdkdisplayprivate.h"
@@ -415,7 +416,7 @@ _gdk_macos_display_open (const gchar *display_name)
if (event_source == NULL)
{
- event_source = _gdk_macos_event_source_new ();
+ event_source = _gdk_macos_event_source_new (self);
g_source_attach (event_source, NULL);
}
@@ -470,3 +471,39 @@ _gdk_macos_display_get_screen_at_display_coords (GdkMacosDisplay *self,
return screen;
}
+
+static GdkEvent *
+gdk_macos_display_translate (GdkMacosDisplay *self,
+ NSEvent *event)
+{
+ g_assert (GDK_IS_MACOS_DISPLAY (self));
+ g_assert (event != NULL);
+
+ /* TODO: Event translation */
+
+ return NULL;
+}
+
+void
+_gdk_macos_display_queue_events (GdkMacosDisplay *self)
+{
+ NSEvent *nsevent;
+ GdkEvent *event;
+ GList *node;
+
+ g_return_if_fail (GDK_IS_MACOS_DISPLAY (self));
+
+ if (!(nsevent = _gdk_macos_event_source_get_pending ()))
+ return;
+
+ if (!(event = gdk_macos_display_translate (self, nsevent)))
+ {
+ [NSApp sendEvent:nsevent];
+ _gdk_macos_event_source_release_event (nsevent);
+ return;
+ }
+
+ node = _gdk_event_queue_append (GDK_DISPLAY (self), event);
+ _gdk_windowing_got_event (GDK_DISPLAY (self), node, event, 0);
+ _gdk_macos_event_source_release_event (nsevent);
+}
diff --git a/gdk/macos/gdkmacoseventsource-private.h b/gdk/macos/gdkmacoseventsource-private.h
index 63811ecd38..9de9d0ef08 100644
--- a/gdk/macos/gdkmacoseventsource-private.h
+++ b/gdk/macos/gdkmacoseventsource-private.h
@@ -20,11 +20,21 @@
#ifndef __GDK_MACOS_EVENT_SOURCE_PRIVATE_H__
#define __GDK_MACOS_EVENT_SOURCE_PRIVATE_H__
+#include <AppKit/AppKit.h>
+
#include "gdkmacosdisplay.h"
G_BEGIN_DECLS
-GSource *_gdk_macos_event_source_new (void);
+typedef enum
+{
+ GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP,
+} GdkMacosEventSubType;
+
+GSource *_gdk_macos_event_source_new (GdkMacosDisplay *display);
+void _gdk_macos_event_source_release_event (NSEvent *event);
+NSEvent *_gdk_macos_event_source_get_pending (void);
+gboolean _gdk_macos_event_source_check_pending (void);
G_END_DECLS
diff --git a/gdk/macos/gdkmacoseventsource.c b/gdk/macos/gdkmacoseventsource.c
index b5ded48ae2..21cf081316 100644
--- a/gdk/macos/gdkmacoseventsource.c
+++ b/gdk/macos/gdkmacoseventsource.c
@@ -1,10 +1,11 @@
-/*
- * Copyright © 2020 Red Hat, Inc.
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright (C) 2005-2007 Imendio AB
*
* 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.
+ * version 2 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
@@ -19,116 +20,1080 @@
#include "config.h"
-#include <errno.h>
-#include <fcntl.h>
-#include <mach/mach.h>
-#include <mach/mach.h>
-#include <mach/port.h>
-#include <mach/port.h>
-#include <sys/event.h>
-#include <sys/time.h>
+#include <glib.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/uio.h>
#include <unistd.h>
+#include "gdkdisplayprivate.h"
#include "gdkinternals.h"
#include "gdkmacoseventsource-private.h"
#include "gdkmacosdisplay-private.h"
-/* See https://daurnimator.com/post/147024385399/using-your-own-main-loop-on-osx
- * for more information on integrating Cocoa's CFRunLoop using an FD.
+/*
+ * This file implementations integration between the GLib main loop and
+ * the native system of the Core Foundation run loop and Cocoa event
+ * handling. There are basically two different cases that we need to
+ * handle: either the GLib main loop is in control (the application
+ * has called gtk_main(), or is otherwise iterating the main loop), or
+ * CFRunLoop is in control (we are in a modal operation such as window
+ * resizing or drag-and-drop.)
+ *
+ * When the GLib main loop is in control we integrate in native event
+ * handling in two ways: first we add a GSource that handles checking
+ * whether there are native events available, translating native events
+ * to GDK events, and dispatching GDK events. Second we replace the
+ * "poll function" of the GLib main loop with our own version that knows
+ * how to wait for both the file descriptors and timeouts that GLib is
+ * interested in and also for incoming native events.
+ *
+ * When CFRunLoop is in control, we integrate in GLib main loop handling
+ * by adding a "run loop observer" that gives us notification at various
+ * points in the run loop cycle. We map these points onto the corresponding
+ * stages of the GLib main loop (prepare, check, dispatch), and make the
+ * appropriate calls into GLib.
+ *
+ * Both cases share a single problem: the OS X API’s don’t allow us to
+ * wait simultaneously for file descriptors and for events. So when we
+ * need to do a blocking wait that includes file descriptor activity, we
+ * push the actual work of calling select() to a helper thread (the
+ * "select thread") and wait for native events in the main thread.
+ *
+ * The main known limitation of this code is that if a callback is triggered
+ * via the OS X run loop while we are "polling" (in either case described
+ * above), iteration of the GLib main loop is not possible from within
+ * that callback. If the programmer tries to do so explicitly, then they
+ * will get a warning from GLib "main loop already active in another thread".
+ */
+
+/******* State for run loop iteration *******/
+
+/* Count of number of times we've gotten an "Entry" notification for
+ * our run loop observer.
+ */
+static int current_loop_level = 0;
+
+/* Run loop level at which we acquired ownership of the GLib main
+ * loop. See note in run_loop_entry(). -1 means that we don’t have
+ * ownership
+ */
+static int acquired_loop_level = -1;
+
+/* Between run_loop_before_waiting() and run_loop_after_waiting();
+ * whether we we need to call select_thread_collect_poll()
+ */
+static gboolean run_loop_polling_async = FALSE;
+
+/* Between run_loop_before_waiting() and run_loop_after_waiting();
+ * max_prioritiy to pass to g_main_loop_check()
+ */
+static gint run_loop_max_priority;
+
+/* Timer that we've added to wake up the run loop when a GLib timeout
+ */
+static CFRunLoopTimerRef run_loop_timer = NULL;
+
+/* These are the file descriptors that are we are polling out of
+ * the run loop. (We keep the array around and reuse it to avoid
+ * constant allocations.)
+ */
+#define RUN_LOOP_POLLFDS_INITIAL_SIZE 16
+static GPollFD *run_loop_pollfds;
+static guint run_loop_pollfds_size; /* Allocated size of the array */
+static guint run_loop_n_pollfds; /* Number of file descriptors in the array */
+
+/******* Other global variables *******/
+
+/* Since we count on replacing the GLib main loop poll function as our
+ * method of integrating Cocoa event handling into the GLib main loop
+ * we need to make sure that the poll function is always called even
+ * when there are no file descriptors that need to be polled. To do
+ * this, we add a dummy GPollFD to our event source with a file
+ * descriptor of “-1”. Then any time that GLib is polling the event
+ * source, it will call our poll function.
+ */
+static GPollFD event_poll_fd;
+
+/* Current NSEvents that we've gotten from Cocoa but haven't yet converted
+ * to GdkEvents. We wait until our dispatch() function to do the conversion
+ * since the conversion can conceivably cause signals to be emmitted
+ * or other things that shouldn’t happen inside a poll function.
+ */
+static GQueue *current_events;
+
+/* The default poll function for GLib; we replace this with our own
+ * Cocoa-aware version and then call the old version to do actual
+ * file descriptor polling. There’s no actual need to chain to the
+ * old one; we could reimplement the same functionality from scratch,
+ * but since the default implementation does the right thing, why
+ * bother.
+ */
+static GPollFunc old_poll_func;
+
+/* Reference to the run loop of the main thread. (There is a unique
+ * CFRunLoop per thread.)
+ */
+static CFRunLoopRef main_thread_run_loop;
+
+/* Normally the Cocoa main loop maintains an NSAutoReleasePool and frees
+ * it on every iteration. Since we are replacing the main loop we have
+ * to provide this functionality ourself. We free and replace the
+ * auto-release pool in our sources prepare() function.
+ */
+static NSAutoreleasePool *autorelease_pool;
+
+/* Flag when we've called nextEventMatchingMask ourself; this triggers
+ * a run loop iteration, so we need to detect that and avoid triggering
+ * our "run the GLib main looop while the run loop is active machinery.
+ */
+static gint getting_events = 0;
+
+/************************************************************
+ ********* Select Thread *********
+ ************************************************************/
+
+/* The states in our state machine, see comments in select_thread_func()
+ * for descriptiions of each state
+ */
+typedef enum {
+ BEFORE_START,
+ WAITING,
+ POLLING_QUEUED,
+ POLLING_RESTART,
+ POLLING_DESCRIPTORS,
+} SelectThreadState;
+
+#ifdef G_ENABLE_DEBUG
+static const char *const state_names[] = {
+ "BEFORE_START",
+ "WAITING",
+ "POLLING_QUEUED",
+ "POLLING_RESTART",
+ "POLLING_DESCRIPTORS"
+};
+#endif
+
+static SelectThreadState select_thread_state = BEFORE_START;
+
+static pthread_t select_thread;
+static pthread_mutex_t select_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t select_thread_cond = PTHREAD_COND_INITIALIZER;
+
+#define SELECT_THREAD_LOCK() pthread_mutex_lock (&select_thread_mutex)
+#define SELECT_THREAD_UNLOCK() pthread_mutex_unlock (&select_thread_mutex)
+#define SELECT_THREAD_SIGNAL() pthread_cond_signal (&select_thread_cond)
+#define SELECT_THREAD_WAIT() pthread_cond_wait (&select_thread_cond, &select_thread_mutex)
+
+/* These are the file descriptors that the select thread is currently
+ * polling.
+ */
+static GPollFD *current_pollfds;
+static guint current_n_pollfds;
+
+/* These are the file descriptors that the select thread should pick
+ * up and start polling when it has a chance.
+ */
+static GPollFD *next_pollfds;
+static guint next_n_pollfds;
+
+/* Pipe used to wake up the select thread */
+static gint select_thread_wakeup_pipe[2];
+
+/* Run loop source used to wake up the main thread */
+static CFRunLoopSourceRef select_main_thread_source;
+
+static void
+select_thread_set_state (SelectThreadState new_state)
+{
+ gboolean old_state;
+
+ if (select_thread_state == new_state)
+ return;
+
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Select thread state: %s => %s",
state_names[select_thread_state], state_names[new_state]));
+
+ old_state = select_thread_state;
+ select_thread_state = new_state;
+ if (old_state == WAITING && new_state != WAITING)
+ SELECT_THREAD_SIGNAL ();
+}
+
+static void
+signal_main_thread (void)
+{
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Waking up main thread"));
+
+ /* If we are in nextEventMatchingMask, then we need to make sure an
+ * event gets queued, otherwise it's enough to simply wake up the
+ * main thread run loop
+ */
+ if (!run_loop_polling_async)
+ CFRunLoopSourceSignal (select_main_thread_source);
+
+ /* Don't check for CFRunLoopIsWaiting() here because it causes a
+ * race condition (the loop could go into waiting state right after
+ * we checked).
+ */
+ CFRunLoopWakeUp (main_thread_run_loop);
+}
+
+static void *
+select_thread_func (void *arg)
+{
+ char c;
+
+ SELECT_THREAD_LOCK ();
+
+ while (TRUE)
+ {
+ switch (select_thread_state)
+ {
+ case BEFORE_START:
+ /* The select thread has not been started yet
+ */
+ g_assert_not_reached ();
+
+ case WAITING:
+ /* Waiting for a set of file descriptors to be submitted by the main thread
+ *
+ * => POLLING_QUEUED: main thread thread submits a set of file descriptors
+ */
+ SELECT_THREAD_WAIT ();
+ break;
+
+ case POLLING_QUEUED:
+ /* Waiting for a set of file descriptors to be submitted by the main thread
+ *
+ * => POLLING_DESCRIPTORS: select thread picks up the file descriptors to begin polling
+ */
+ g_free (current_pollfds);
+
+ current_pollfds = next_pollfds;
+ current_n_pollfds = next_n_pollfds;
+
+ next_pollfds = NULL;
+ next_n_pollfds = 0;
+
+ select_thread_set_state (POLLING_DESCRIPTORS);
+ break;
+
+ case POLLING_RESTART:
+ /* Select thread is currently polling a set of file descriptors, main thread has
+ * began a new iteration with the same set of file descriptors. We don't want to
+ * wake the select thread up and wait for it to restart immediately, but to avoid
+ * a race (described below in select_thread_start_polling()) we need to recheck after
+ * polling completes.
+ *
+ * => POLLING_DESCRIPTORS: select completes, main thread rechecks by polling again
+ * => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
+ */
+ select_thread_set_state (POLLING_DESCRIPTORS);
+ break;
+
+ case POLLING_DESCRIPTORS:
+ /* In the process of polling the file descriptors
+ *
+ * => WAITING: polling completes when a file descriptor becomes active
+ * => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
+ * => POLLING_RESTART: main thread begins a new iteration with the same set file descriptors
+ */
+ SELECT_THREAD_UNLOCK ();
+ old_poll_func (current_pollfds, current_n_pollfds, -1);
+ SELECT_THREAD_LOCK ();
+
+ read (select_thread_wakeup_pipe[0], &c, 1);
+
+ if (select_thread_state == POLLING_DESCRIPTORS)
+ {
+ signal_main_thread ();
+ select_thread_set_state (WAITING);
+ }
+ break;
+ }
+ }
+}
+
+static void
+got_fd_activity (void *info)
+{
+ NSEvent *event;
+
+ /* Post a message so we'll break out of the message loop */
+ event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: 0
+ windowNumber: 0
+ context: nil
+ subtype: GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP
+ data1: 0
+ data2: 0];
+
+ [NSApp postEvent:event atStart:YES];
+}
+
+static void
+select_thread_start (void)
+{
+ g_return_if_fail (select_thread_state == BEFORE_START);
+
+ pipe (select_thread_wakeup_pipe);
+ fcntl (select_thread_wakeup_pipe[0], F_SETFL, O_NONBLOCK);
+
+ CFRunLoopSourceContext source_context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
got_fd_activity };
+ select_main_thread_source = CFRunLoopSourceCreate (NULL, 0, &source_context);
+
+ CFRunLoopAddSource (main_thread_run_loop, select_main_thread_source, kCFRunLoopCommonModes);
+
+ select_thread_state = WAITING;
+
+ while (TRUE)
+ {
+ if (pthread_create (&select_thread, NULL, select_thread_func, NULL) == 0)
+ break;
+
+ g_warning ("Failed to create select thread, sleeping and trying again");
+ sleep (1);
+ }
+}
+
+#ifdef G_ENABLE_DEBUG
+static void
+dump_poll_result (GPollFD *ufds,
+ guint nfds)
+{
+ GString *s;
+ gint i;
+
+ s = g_string_new ("");
+ for (i = 0; i < nfds; i++)
+ {
+ if (ufds[i].fd >= 0 && ufds[i].revents)
+ {
+ g_string_append_printf (s, " %d:", ufds[i].fd);
+ if (ufds[i].revents & G_IO_IN)
+ g_string_append (s, " in");
+ if (ufds[i].revents & G_IO_OUT)
+ g_string_append (s, " out");
+ if (ufds[i].revents & G_IO_PRI)
+ g_string_append (s, " pri");
+ g_string_append (s, "\n");
+ }
+ }
+ g_message ("%s", s->str);
+ g_string_free (s, TRUE);
+}
+#endif
+
+static gboolean
+pollfds_equal (GPollFD *old_pollfds,
+ guint old_n_pollfds,
+ GPollFD *new_pollfds,
+ guint new_n_pollfds)
+{
+ gint i;
+
+ if (old_n_pollfds != new_n_pollfds)
+ return FALSE;
+
+ for (i = 0; i < old_n_pollfds; i++)
+ {
+ if (old_pollfds[i].fd != new_pollfds[i].fd ||
+ old_pollfds[i].events != new_pollfds[i].events)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Begins a polling operation with the specified GPollFD array; the
+ * timeout is used only to tell if the polling operation is blocking
+ * or non-blocking.
+ *
+ * Returns:
+ * -1: No file descriptors ready, began asynchronous poll
+ * 0: No file descriptors ready, asynchronous poll not needed
+ * > 0: Number of file descriptors ready
+ */
+static gint
+select_thread_start_poll (GPollFD *ufds,
+ guint nfds, gint timeout)
+{
+ gint n_ready;
+ gboolean have_new_pollfds = FALSE;
+ gint poll_fd_index = -1;
+ gint i;
+
+ for (i = 0; i < nfds; i++)
+ if (ufds[i].fd == -1)
+ {
+ poll_fd_index = i;
+ break;
+ }
+
+ if (nfds == 0 ||
+ (nfds == 1 && poll_fd_index >= 0))
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Nothing to poll"));
+ return 0;
+ }
+
+ /* If we went immediately to an async poll, then we might decide to
+ * dispatch idle functions when higher priority file descriptor sources
+ * are ready to be dispatched. So we always need to first check
+ * check synchronously with a timeout of zero, and only when no
+ * sources are immediately ready, go to the asynchronous poll.
+ *
+ * Of course, if the timeout passed in is 0, then the synchronous
+ * check is sufficient and we never need to do the asynchronous poll.
+ */
+ n_ready = old_poll_func (ufds, nfds, 0);
+ if (n_ready > 0 || timeout == 0)
+ {
+#ifdef G_ENABLE_DEBUG
+ if ((_gdk_debug_flags & GDK_DEBUG_EVENTLOOP) && n_ready > 0)
+ {
+ g_message ("EventLoop: Found ready file descriptors before waiting");
+ dump_poll_result (ufds, nfds);
+ }
+#endif
+
+ return n_ready;
+ }
+
+ SELECT_THREAD_LOCK ();
+
+ if (select_thread_state == BEFORE_START)
+ {
+ select_thread_start ();
+ }
+
+ if (select_thread_state == POLLING_QUEUED)
+ {
+ /* If the select thread hasn't picked up the set of file descriptors yet
+ * then we can simply replace an old stale set with a new set.
+ */
+ if (!pollfds_equal (ufds, nfds, next_pollfds, next_n_pollfds - 1))
+ {
+ g_free (next_pollfds);
+ next_pollfds = NULL;
+ next_n_pollfds = 0;
+
+ have_new_pollfds = TRUE;
+ }
+ }
+ else if (select_thread_state == POLLING_RESTART || select_thread_state == POLLING_DESCRIPTORS)
+ {
+ /* If we are already in the process of polling the right set of file descriptors,
+ * there's no need for us to immediately force the select thread to stop polling
+ * and then restart again. And avoiding doing so increases the efficiency considerably
+ * in the common case where we have a set of basically inactive file descriptors that
+ * stay unchanged present as we process many events.
+ *
+ * However, we have to be careful that we don't hit the following race condition
+ * Select Thread Main Thread
+ * ----------------- ---------------
+ * Polling Completes
+ * Reads data or otherwise changes file descriptor state
+ * Checks if polling is current
+ * Does nothing (*)
+ * Releases lock
+ * Acquires lock
+ * Marks polling as complete
+ * Wakes main thread
+ * Receives old stale file descriptor state
+ *
+ * To avoid this, when the new set of poll descriptors is the same as the current
+ * one, we transition to the POLLING_RESTART stage at the point marked (*). When
+ * the select thread wakes up from the poll because a file descriptor is active, if
+ * the state is POLLING_RESTART it immediately begins polling same the file descriptor
+ * set again. This normally will just return the same set of active file descriptors
+ * as the first time, but in sequence described above will properly update the
+ * file descriptor state.
+ *
+ * Special case: this RESTART logic is not needed if the only FD is the internal GLib
+ * "wakeup pipe" that is presented when threads are initialized.
+ *
+ * P.S.: The harm in the above sequence is mostly that sources can be signalled
+ * as ready when they are no longer ready. This may prompt a blocking read
+ * from a file descriptor that hangs.
+ */
+ if (!pollfds_equal (ufds, nfds, current_pollfds, current_n_pollfds - 1))
+ have_new_pollfds = TRUE;
+ else
+ {
+ if (!((nfds == 1 && poll_fd_index < 0 && g_thread_supported ()) ||
+ (nfds == 2 && poll_fd_index >= 0 && g_thread_supported ())))
+ select_thread_set_state (POLLING_RESTART);
+ }
+ }
+ else
+ have_new_pollfds = TRUE;
+
+ if (have_new_pollfds)
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Submitting a new set of file descriptor to the select
thread"));
+
+ g_assert (next_pollfds == NULL);
+
+ next_n_pollfds = nfds + 1;
+ next_pollfds = g_new (GPollFD, nfds + 1);
+ memcpy (next_pollfds, ufds, nfds * sizeof (GPollFD));
+
+ next_pollfds[nfds].fd = select_thread_wakeup_pipe[0];
+ next_pollfds[nfds].events = G_IO_IN;
+
+ if (select_thread_state != POLLING_QUEUED && select_thread_state != WAITING)
+ {
+ if (select_thread_wakeup_pipe[1])
+ {
+ char c = 'A';
+ write (select_thread_wakeup_pipe[1], &c, 1);
+ }
+ }
+
+ select_thread_set_state (POLLING_QUEUED);
+ }
+
+ SELECT_THREAD_UNLOCK ();
+
+ return -1;
+}
+
+/* End an asynchronous polling operation started with
+ * select_thread_collect_poll(). This must be called if and only if
+ * select_thread_start_poll() return -1. The GPollFD array passed
+ * in must be identical to the one passed to select_thread_start_poll().
+ *
+ * The results of the poll are written into the GPollFD array passed in.
+ *
+ * Returns: number of file descriptors ready
*/
-extern mach_port_t _dispatch_get_main_queue_port_4CF (void);
-extern void _dispatch_main_queue_callback_4CF (void);
+static int
+select_thread_collect_poll (GPollFD *ufds, guint nfds)
+{
+ gint i;
+ gint n_ready = 0;
+
+ SELECT_THREAD_LOCK ();
+
+ if (select_thread_state == WAITING) /* The poll completed */
+ {
+ for (i = 0; i < nfds; i++)
+ {
+ if (ufds[i].fd == -1)
+ continue;
+
+ g_assert (ufds[i].fd == current_pollfds[i].fd);
+ g_assert (ufds[i].events == current_pollfds[i].events);
+
+ if (current_pollfds[i].revents)
+ {
+ ufds[i].revents = current_pollfds[i].revents;
+ n_ready++;
+ }
+ }
+
+#ifdef G_ENABLE_DEBUG
+ if (_gdk_debug_flags & GDK_DEBUG_EVENTLOOP)
+ {
+ g_message ("EventLoop: Found ready file descriptors after waiting");
+ dump_poll_result (ufds, nfds);
+ }
+#endif
+ }
+
+ SELECT_THREAD_UNLOCK ();
+
+ return n_ready;
+}
+
+/************************************************************
+ ********* Main Loop Source *********
+ ************************************************************/
typedef struct _GdkMacosEventSource
{
- GSource source;
- GPollFD pfd;
+ GSource source;
+ GdkDisplay *display;
} GdkMacosEventSource;
+gboolean
+_gdk_macos_event_source_check_pending (void)
+{
+ return current_events && current_events->head;
+}
+
+NSEvent *
+_gdk_macos_event_source_get_pending (void)
+{
+ NSEvent *event = NULL;
+
+ if (current_events)
+ event = g_queue_pop_tail (current_events);
+
+ return event;
+}
+
+void
+_gdk_macos_event_source_release_event (NSEvent *event)
+{
+ [event release];
+}
+
static gboolean
-gdk_macos_event_source_check (GSource *base)
+gdk_macos_event_source_prepare (GSource *source,
+ gint *timeout)
{
- GdkMacosEventSource *source = (GdkMacosEventSource *)base;
- return (source->pfd.revents & G_IO_IN) != 0;
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+ gboolean retval;
+
+ /* The prepare stage is the stage before the main loop starts polling
+ * and dispatching events. The autorelease poll is drained here for
+ * the preceding main loop iteration or, in case of the first iteration,
+ * for the operations carried out between event loop initialization and
+ * this first iteration.
+ *
+ * The autorelease poll must only be drained when the following conditions
+ * apply:
+ * - We are at the base CFRunLoop level (indicated by current_loop_level),
+ * - We are at the base g_main_loop level (indicated by
+ * g_main_depth())
+ * - We are at the base poll_func level (indicated by getting events).
+ *
+ * Messing with the autorelease pool at any level of nesting can cause access
+ * to deallocated memory because autorelease_pool is static and releasing a
+ * pool will cause all pools allocated inside of it to be released as well.
+ */
+ if (current_loop_level == 0 && g_main_depth() == 0 && getting_events == 0)
+ {
+ if (autorelease_pool)
+ [autorelease_pool drain];
+
+ autorelease_pool = [[NSAutoreleasePool alloc] init];
+ }
+
+ *timeout = -1;
+
+ if (event_source->display->event_pause_count > 0)
+ retval = _gdk_event_queue_find_first (event_source->display) != NULL;
+ else
+ retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
+ _gdk_macos_event_source_check_pending ());
+
+ return retval;
+}
+
+static gboolean
+gdk_macos_event_source_check (GSource *source)
+{
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+ gboolean retval;
+
+ if (event_source->display->event_pause_count > 0)
+ retval = _gdk_event_queue_find_first (event_source->display) != NULL;
+ else
+ retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
+ _gdk_macos_event_source_check_pending ());
+
+ return retval;
}
static gboolean
-gdk_macos_event_source_dispatch (GSource *base,
+gdk_macos_event_source_dispatch (GSource *source,
GSourceFunc callback,
- gpointer data)
+ gpointer user_data)
{
- GdkMacosEventSource *source = (GdkMacosEventSource *)base;
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+ GdkEvent *event;
- g_assert (source != NULL);
+ _gdk_macos_display_queue_events (GDK_MACOS_DISPLAY (event_source->display));
- source->pfd.revents = 0;
- _dispatch_main_queue_callback_4CF ();
+ event = _gdk_event_unqueue (event_source->display);
- return G_SOURCE_CONTINUE;
+ if (event)
+ {
+ _gdk_event_emit (event);
+
+ gdk_event_unref (event);
+ }
+
+ return TRUE;
}
-static GSourceFuncs macos_event_source_funcs = {
- .prepare = NULL,
- .check = gdk_macos_event_source_check,
- .dispatch = gdk_macos_event_source_dispatch,
- .finalize = NULL,
+static void
+gdk_macos_event_source_finalize (GSource *source)
+{
+ GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
+
+ g_clear_object (&event_source->display);
+}
+
+static GSourceFuncs event_funcs = {
+ gdk_macos_event_source_prepare,
+ gdk_macos_event_source_check,
+ gdk_macos_event_source_dispatch,
+ gdk_macos_event_source_finalize,
};
-static int
-gdk_macos_event_source_get_fd (void)
+/************************************************************
+ ********* Our Poll Function *********
+ ************************************************************/
+
+static gint
+poll_func (GPollFD *ufds,
+ guint nfds,
+ gint timeout_)
{
- int fd = kqueue ();
+ NSEvent *event;
+ NSDate *limit_date;
+ gint n_ready;
- if (fd != -1)
+ static GPollFD *last_ufds;
+
+ last_ufds = ufds;
+
+ n_ready = select_thread_start_poll (ufds, nfds, timeout_);
+ if (n_ready > 0)
+ timeout_ = 0;
+
+ if (timeout_ == -1)
+ limit_date = [NSDate distantFuture];
+ else if (timeout_ == 0)
+ limit_date = [NSDate distantPast];
+ else
+ limit_date = [NSDate dateWithTimeIntervalSinceNow:timeout_/1000.0];
+
+ getting_events++;
+ event = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: limit_date
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ getting_events--;
+
+ /* We check if last_ufds did not change since the time this function was
+ * called. It is possible that a recursive main loop (and thus recursive
+ * invocation of this poll function) is triggered while in
+ * nextEventMatchingMask:. If during that time new fds are added,
+ * the cached fds array might be replaced in g_main_context_iterate().
+ * So, we should avoid accessing the old fd array (still pointed at by
+ * ufds) here in that case, since it might have been freed. We avoid this
+ * by not calling the collect stage.
+ */
+ if (last_ufds == ufds && n_ready < 0)
+ n_ready = select_thread_collect_poll (ufds, nfds);
+
+ if (event &&
+ [event type] == NSEventTypeApplicationDefined &&
+ [event subtype] == GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP)
{
- mach_port_t port;
- mach_port_t portset;
+ /* Just used to wake us up; if an event and a FD arrived at the same
+ * time; could have come from a previous iteration in some cases,
+ * but the spurious wake up is harmless if a little inefficient.
+ */
+ event = NULL;
+ }
- fcntl (fd, F_SETFD, FD_CLOEXEC);
- port = _dispatch_get_main_queue_port_4CF ();
+ if (event)
+ {
+ if (!current_events)
+ current_events = g_queue_new ();
+ g_queue_push_head (current_events, [event retain]);
+ }
- if (KERN_SUCCESS == mach_port_allocate (mach_task_self (),
- MACH_PORT_RIGHT_PORT_SET,
- &portset))
- {
- struct kevent64_s event;
+ return n_ready;
+}
- EV_SET64 (&event, portset, EVFILT_MACHPORT, EV_ADD|EV_CLEAR, MACH_RCV_MSG, 0, 0, 0, 0);
+/************************************************************
+ ********* Running the main loop out of CFRunLoop *********
+ ************************************************************/
- if (kevent64 (fd, &event, 1, NULL, 0, 0, &(struct timespec){0,0}) == 0)
- {
- if (KERN_SUCCESS == mach_port_insert_member (mach_task_self (), port, portset))
- return fd;
- }
+/* Wrapper around g_main_context_query() that handles reallocating
+ * run_loop_pollfds up to the proper size
+ */
+static gint
+query_main_context (GMainContext *context,
+ int max_priority,
+ int *timeout)
+{
+ gint nfds;
+
+ if (!run_loop_pollfds)
+ {
+ run_loop_pollfds_size = RUN_LOOP_POLLFDS_INITIAL_SIZE;
+ run_loop_pollfds = g_new (GPollFD, run_loop_pollfds_size);
+ }
+
+ while ((nfds = g_main_context_query (context, max_priority, timeout,
+ run_loop_pollfds,
+ run_loop_pollfds_size)) > run_loop_pollfds_size)
+ {
+ g_free (run_loop_pollfds);
+ run_loop_pollfds_size = nfds;
+ run_loop_pollfds = g_new (GPollFD, nfds);
+ }
+
+ return nfds;
+}
+
+static void
+run_loop_entry (void)
+{
+ if (acquired_loop_level == -1)
+ {
+ if (g_main_context_acquire (NULL))
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Beginning tracking run loop activity"));
+ acquired_loop_level = current_loop_level;
+ }
+ else
+ {
+ /* If we fail to acquire the main context, that means someone is iterating
+ * the main context in a different thread; we simply wait until this loop
+ * exits and then try again at next entry. In general, iterating the loop
+ * from a different thread is rare: it is only possible when GDK threading
+ * is initialized and is not frequently used even then. So, we hope that
+ * having GLib main loop iteration blocked in the combination of that and
+ * a native modal operation is a minimal problem. We could imagine using a
+ * thread that does g_main_context_wait() and then wakes us back up, but
+ * the gain doesn't seem worth the complexity.
+ */
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Can't acquire main loop; skipping tracking run loop
activity"));
}
+ }
+}
+
+static void
+run_loop_before_timers (void)
+{
+}
+
+static void
+run_loop_before_sources (void)
+{
+ GMainContext *context = g_main_context_default ();
+ gint max_priority;
+ gint nfds;
+
+ /* Before we let the CFRunLoop process sources, we want to check if there
+ * are any pending GLib main loop sources more urgent than
+ * G_PRIORITY_DEFAULT that need to be dispatched. (We consider all activity
+ * from the CFRunLoop to have a priority of G_PRIORITY_DEFAULT.) If no
+ * sources are processed by the CFRunLoop, then processing will continue
+ * on to the BeforeWaiting stage where we check for lower priority sources.
+ */
+
+ g_main_context_prepare (context, &max_priority);
+ max_priority = MIN (max_priority, G_PRIORITY_DEFAULT);
- close (fd);
+ /* We ignore the timeout that query_main_context () returns since we'll
+ * always query again before waiting.
+ */
+ nfds = query_main_context (context, max_priority, NULL);
+
+ if (nfds)
+ old_poll_func (run_loop_pollfds, nfds, 0);
+
+ if (g_main_context_check (context, max_priority, run_loop_pollfds, nfds))
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Dispatching high priority sources"));
+ g_main_context_dispatch (context);
}
+}
- return -1;
+static void
+dummy_timer_callback (CFRunLoopTimerRef timer,
+ void *info)
+{
+ /* Nothing; won't normally even be called */
+}
+
+static void
+run_loop_before_waiting (void)
+{
+ GMainContext *context = g_main_context_default ();
+ gint timeout;
+ gint n_ready;
+
+ /* At this point, the CFRunLoop is ready to wait. We start a GMain loop
+ * iteration by calling the check() and query() stages. We start a
+ * poll, and if it doesn't complete immediately we let the run loop
+ * go ahead and sleep. Before doing that, if there was a timeout from
+ * GLib, we set up a CFRunLoopTimer to wake us up.
+ */
+
+ g_main_context_prepare (context, &run_loop_max_priority);
+
+ run_loop_n_pollfds = query_main_context (context, run_loop_max_priority, &timeout);
+
+ n_ready = select_thread_start_poll (run_loop_pollfds, run_loop_n_pollfds, timeout);
+
+ if (n_ready > 0 || timeout == 0)
+ {
+ /* We have stuff to do, no sleeping allowed! */
+ CFRunLoopWakeUp (main_thread_run_loop);
+ }
+ else if (timeout > 0)
+ {
+ /* We need to get the run loop to break out of its wait when our timeout
+ * expires. We do this by adding a dummy timer that we'll remove immediately
+ * after the wait wakes up.
+ */
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Adding timer to wake us up in %d milliseconds", timeout));
+
+ run_loop_timer = CFRunLoopTimerCreate (NULL, /* allocator */
+ CFAbsoluteTimeGetCurrent () + timeout / 1000.,
+ 0, /* interval (0=does not repeat) */
+ 0, /* flags */
+ 0, /* order (priority) */
+ dummy_timer_callback,
+ NULL);
+
+ CFRunLoopAddTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
+ }
+
+ run_loop_polling_async = n_ready < 0;
+}
+
+static void
+run_loop_after_waiting (void)
+{
+ GMainContext *context = g_main_context_default ();
+
+ /* After sleeping, we finish of the GMain loop iteratin started in before_waiting()
+ * by doing the check() and dispatch() stages.
+ */
+
+ if (run_loop_timer)
+ {
+ CFRunLoopRemoveTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
+ CFRelease (run_loop_timer);
+ run_loop_timer = NULL;
+ }
+
+ if (run_loop_polling_async)
+ {
+ select_thread_collect_poll (run_loop_pollfds, run_loop_n_pollfds);
+ run_loop_polling_async = FALSE;
+ }
+
+ if (g_main_context_check (context, run_loop_max_priority, run_loop_pollfds, run_loop_n_pollfds))
+ {
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Dispatching after waiting"));
+ g_main_context_dispatch (context);
+ }
+}
+
+static void
+run_loop_exit (void)
+{
+ /* + 1 because we decrement current_loop_level separately in observer_callback() */
+ if ((current_loop_level + 1) == acquired_loop_level)
+ {
+ g_main_context_release (NULL);
+ acquired_loop_level = -1;
+ GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Ended tracking run loop activity"));
+ }
+}
+
+static void
+run_loop_observer_callback (CFRunLoopObserverRef observer,
+ CFRunLoopActivity activity,
+ void *info)
+{
+ switch (activity)
+ {
+ case kCFRunLoopEntry:
+ current_loop_level++;
+ break;
+ case kCFRunLoopExit:
+ g_return_if_fail (current_loop_level > 0);
+ current_loop_level--;
+ break;
+ case kCFRunLoopBeforeTimers:
+ case kCFRunLoopBeforeSources:
+ case kCFRunLoopBeforeWaiting:
+ case kCFRunLoopAfterWaiting:
+ case kCFRunLoopAllActivities:
+ default:
+ break;
+ }
+
+ if (getting_events > 0) /* Activity we triggered */
+ return;
+
+ switch (activity)
+ {
+ case kCFRunLoopEntry:
+ run_loop_entry ();
+ break;
+ case kCFRunLoopBeforeTimers:
+ run_loop_before_timers ();
+ break;
+ case kCFRunLoopBeforeSources:
+ run_loop_before_sources ();
+ break;
+ case kCFRunLoopBeforeWaiting:
+ run_loop_before_waiting ();
+ break;
+ case kCFRunLoopAfterWaiting:
+ run_loop_after_waiting ();
+ break;
+ case kCFRunLoopExit:
+ run_loop_exit ();
+ break;
+ case kCFRunLoopAllActivities:
+ /* TODO: Do most of the above? */
+ default:
+ break;
+ }
}
+/************************************************************/
+
GSource *
-_gdk_macos_event_source_new (void)
+_gdk_macos_event_source_new (GdkMacosDisplay *display)
{
- GdkMacosEventSource *macos_source;
+ CFRunLoopObserverRef observer;
+ GdkMacosEventSource *event_source;
GSource *source;
- int fd;
- if ((fd = gdk_macos_event_source_get_fd ()) == -1)
- return NULL;
+ g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
+
+ /* Hook into the GLib main loop */
+
+ event_poll_fd.events = G_IO_IN;
+ event_poll_fd.fd = -1;
- source = g_source_new (&macos_event_source_funcs, sizeof (GdkMacosEventSource));
- g_source_set_name (source, "GDK macOS Event Source");
+ source = g_source_new (&event_funcs, sizeof (GdkMacosEventSource));
+ g_source_set_name (source, "GDK Quartz event source");
+ g_source_add_poll (source, &event_poll_fd);
g_source_set_priority (source, GDK_PRIORITY_EVENTS);
g_source_set_can_recurse (source, TRUE);
- macos_source = (GdkMacosEventSource *)source;
- macos_source->pfd.fd = fd;
- macos_source->pfd.events = G_IO_IN;
- macos_source->pfd.revents = 0;
- g_source_add_poll (source, &macos_source->pfd);
+ old_poll_func = g_main_context_get_poll_func (NULL);
+ g_main_context_set_poll_func (NULL, poll_func);
+
+ event_source = (GdkMacosEventSource *)source;
+ event_source->display = g_object_ref (GDK_DISPLAY (display));
+
+ /* Hook into the the CFRunLoop for the main thread */
+
+ main_thread_run_loop = CFRunLoopGetCurrent ();
+
+ observer = CFRunLoopObserverCreate (NULL, /* default allocator */
+ kCFRunLoopAllActivities,
+ true, /* repeats: not one-shot */
+ 0, /* order (priority) */
+ run_loop_observer_callback,
+ NULL);
+
+ CFRunLoopAddObserver (main_thread_run_loop, observer, kCFRunLoopCommonModes);
+
+ /* Initialize our autorelease pool */
+ autorelease_pool = [[NSAutoreleasePool alloc] init];
return source;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]