gtk+ r21783 - in trunk: . gdk gdk/quartz
- From: rhult svn gnome org
- To: svn-commits-list gnome org
- Subject: gtk+ r21783 - in trunk: . gdk gdk/quartz
- Date: Wed, 12 Nov 2008 13:30:22 +0000 (UTC)
Author: rhult
Date: Wed Nov 12 13:30:22 2008
New Revision: 21783
URL: http://svn.gnome.org/viewvc/gtk+?rev=21783&view=rev
Log:
2008-11-12 Richard Hult <richard imendio com>
Bug 550942 â [patch] Rework of gdkeventloop-quartz.c
* gdk/gdk.c:
* gdk/gdkinternals.h: Add eventloop debug facility.
* gdk/quartz/gdkeventloop-quartz.c: Big rework of the quartz
mainloop integration, patch from Owen Taylor. See bug #550942 for
the details.
Modified:
trunk/ChangeLog
trunk/gdk/gdk.c
trunk/gdk/gdkinternals.h
trunk/gdk/quartz/gdkeventloop-quartz.c
Modified: trunk/gdk/gdk.c
==============================================================================
--- trunk/gdk/gdk.c (original)
+++ trunk/gdk/gdk.c Wed Nov 12 13:30:22 2008
@@ -80,7 +80,8 @@
{"cursor", GDK_DEBUG_CURSOR},
{"multihead", GDK_DEBUG_MULTIHEAD},
{"xinerama", GDK_DEBUG_XINERAMA},
- {"draw", GDK_DEBUG_DRAW}
+ {"draw", GDK_DEBUG_DRAW},
+ {"eventloop", GDK_DEBUG_EVENTLOOP}
};
static const int gdk_ndebug_keys = G_N_ELEMENTS (gdk_debug_keys);
Modified: trunk/gdk/gdkinternals.h
==============================================================================
--- trunk/gdk/gdkinternals.h (original)
+++ trunk/gdk/gdkinternals.h Wed Nov 12 13:30:22 2008
@@ -85,7 +85,8 @@
GDK_DEBUG_CURSOR = 1 <<11,
GDK_DEBUG_MULTIHEAD = 1 <<12,
GDK_DEBUG_XINERAMA = 1 <<13,
- GDK_DEBUG_DRAW = 1 <<14
+ GDK_DEBUG_DRAW = 1 <<14,
+ GDK_DEBUG_EVENTLOOP = 1 <<15
} GdkDebugFlag;
#ifndef GDK_DISABLE_DEPRECATED
Modified: trunk/gdk/quartz/gdkeventloop-quartz.c
==============================================================================
--- trunk/gdk/quartz/gdkeventloop-quartz.c (original)
+++ trunk/gdk/quartz/gdkeventloop-quartz.c Wed Nov 12 13:30:22 2008
@@ -8,22 +8,582 @@
#include "gdkprivate-quartz.h"
+/*
+ * 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;
-static gboolean select_fd_waiting = FALSE, ready_for_poll = FALSE;
-static pthread_t select_thread = 0;
-static int wakeup_pipe[2];
-static pthread_mutex_t pollfd_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_cond_t ready_cond = PTHREAD_COND_INITIALIZER;
-static GPollFD *pollfds;
-static guint n_pollfds;
-static CFRunLoopSourceRef select_main_thread_source;
+/* 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 gboolean getting_events;
+
+/************************************************************
+ ********* 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_print ("EventLoop: Select thread state: %s => %s\n", 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_print ("EventLoop: Waking up main thread\n"));
+
+ /* 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);
+
+ if (CFRunLoopIsWaiting (main_thread_run_loop))
+ 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
+ */
+ if (current_pollfds)
+ 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: NSApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: 0
+ windowNumber: 0
+ context: nil
+ subtype: GDK_QUARTZ_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)
+{
+ gint i;
+
+ for (i = 0; i < nfds; i++)
+ {
+ if (ufds[i].fd >= 0 && ufds[i].revents)
+ {
+ g_print (" %d:", ufds[i].fd);
+ if (ufds[i].revents & G_IO_IN)
+ g_print (" in");
+ if (ufds[i].revents & G_IO_OUT)
+ g_print (" out");
+ if (ufds[i].revents & G_IO_PRI)
+ g_print (" pri");
+ g_print ("\n");
+ }
+ }
+}
+#endif
+
+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.
+ *
+ * Return value:
+ * -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_print ("EventLoop: Nothing to poll\n"));
+ 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_print ("EventLoop: Found ready file descriptors before waiting\n");
+ 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_print ("EventLoop: Submitting a new set of file descriptor to the select thread\n"));
+
+ 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.
+ *
+ * Return Value: number of file descriptors ready
+ */
+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_print ("EventLoop: Found ready file descriptors after waiting\n");
+ dump_poll_result (ufds, nfds);
+ }
+#endif
+ }
+
+ SELECT_THREAD_UNLOCK ();
+
+ return n_ready;
+}
+
+/************************************************************
+ ********* Main Loop Source *********
+ ************************************************************/
+
gboolean
_gdk_quartz_event_loop_check_pending (void)
{
@@ -51,21 +611,14 @@
gdk_event_prepare (GSource *source,
gint *timeout)
{
- NSEvent *event;
gboolean retval;
GDK_THREADS_ENTER ();
*timeout = -1;
- event = [NSApp nextEventMatchingMask: NSAnyEventMask
- untilDate: [NSDate distantPast]
- inMode: NSDefaultRunLoopMode
- dequeue: NO];
-
retval = (_gdk_event_queue_find_first (_gdk_display) != NULL ||
- (current_events && current_events->head) ||
- event != NULL);
+ _gdk_quartz_event_loop_check_pending ());
GDK_THREADS_LEAVE ();
@@ -79,15 +632,23 @@
GDK_THREADS_ENTER ();
- if (autorelease_pool)
- [autorelease_pool release];
- autorelease_pool = [[NSAutoreleasePool alloc] init];
-
- if (_gdk_event_queue_find_first (_gdk_display) != NULL ||
- _gdk_quartz_event_loop_check_pending ())
- retval = TRUE;
- else
- retval = FALSE;
+ /* XXX: This check isn't right it won't handle a recursive GLib main
+ * loop run within an outer CFRunLoop run. Such loops will pile up
+ * memory. Fixing this requires setting a flag *only* when we call
+ * g_main_context_check() from within the run loop iteraton code,
+ * and also maintaining our own stack of run loops... allocating and
+ * releasing NSAutoReleasePools not properly nested with CFRunLoop
+ * runs seems to cause problems.
+ */
+ if (current_loop_level == 0)
+ {
+ if (autorelease_pool)
+ [autorelease_pool release];
+ autorelease_pool = [[NSAutoreleasePool alloc] init];
+ }
+
+ retval = (_gdk_event_queue_find_first (_gdk_display) != NULL ||
+ _gdk_quartz_event_loop_check_pending ());
GDK_THREADS_LEAVE ();
@@ -127,212 +688,300 @@
NULL
};
-static void
-got_fd_activity (void *info)
+/************************************************************
+ ********* Our Poll Function *********
+ ************************************************************/
+
+static gint
+poll_func (GPollFD *ufds,
+ guint nfds,
+ gint timeout_)
{
NSEvent *event;
+ NSDate *limit_date;
+ gint n_ready;
- /* Post a message so we'll break out of the message loop */
- event = [NSEvent otherEventWithType: NSApplicationDefined
- location: NSZeroPoint
- modifierFlags: 0
- timestamp: 0
- windowNumber: 0
- context: nil
- subtype: GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP
- data1: 0
- data2: 0];
+ n_ready = select_thread_start_poll (ufds, nfds, timeout_);
+ if (n_ready > 0)
+ timeout_ = 0;
- [NSApp postEvent:event atStart:YES];
+ 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 = TRUE;
+ event = [NSApp nextEventMatchingMask: NSAnyEventMask
+ untilDate: limit_date
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ getting_events = FALSE;
+
+ if (n_ready < 0)
+ n_ready = select_thread_collect_poll (ufds, nfds);
+
+ if (event &&
+ [event type] == NSApplicationDefined &&
+ [event subtype] == GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP)
+ {
+ /* 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;
+ }
+
+ if (event)
+ {
+ if (!current_events)
+ current_events = g_queue_new ();
+ g_queue_push_head (current_events, [event retain]);
+ }
+
+ return n_ready;
}
-static void *
-select_thread_func (void *arg)
+/************************************************************
+ ********* Running the main loop out of CFRunLoop *********
+ ************************************************************/
+
+/* 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)
{
- int n_active_fds;
-
- pthread_mutex_lock (&pollfd_mutex);
+ 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 (1)
+ while ((nfds = g_main_context_query (context, max_priority, timeout,
+ run_loop_pollfds,
+ run_loop_pollfds_size)) > run_loop_pollfds_size)
{
- char c;
- int n;
+ g_free (run_loop_pollfds);
+ run_loop_pollfds_size = nfds;
+ run_loop_pollfds = g_new (GPollFD, nfds);
+ }
- ready_for_poll = TRUE;
- pthread_cond_signal (&ready_cond);
- pthread_cond_wait (&ready_cond, &pollfd_mutex);
- ready_for_poll = FALSE;
-
- select_fd_waiting = TRUE;
- pthread_cond_signal (&ready_cond);
- pthread_mutex_unlock (&pollfd_mutex);
- n_active_fds = old_poll_func (pollfds, n_pollfds, -1);
- pthread_mutex_lock (&pollfd_mutex);
- select_fd_waiting = FALSE;
- n = read (wakeup_pipe[0], &c, 1);
- if (n == 1)
- {
- g_assert (c == 'A');
+ return nfds;
+}
- n_active_fds --;
- }
- pthread_mutex_unlock (&pollfd_mutex);
+static void
+run_loop_entry (void)
+{
+ current_loop_level++;
- if (n_active_fds)
+ if (acquired_loop_level == -1)
+ {
+ if (g_main_context_acquire (NULL))
{
- /* We have active fds, signal the main thread */
- CFRunLoopSourceSignal (select_main_thread_source);
- if (CFRunLoopIsWaiting (main_thread_run_loop))
- CFRunLoopWakeUp (main_thread_run_loop);
+ GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Beginning tracking run loop activity\n"));
+ 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_print ("EventLoop: Can't acquire main loop; skipping tracking run loop activity\n"));
}
-
- pthread_mutex_lock (&pollfd_mutex);
}
}
-static gint
-poll_func (GPollFD *ufds, guint nfds, gint timeout_)
+static void
+run_loop_before_timers (void)
{
- NSEvent *event;
- NSDate *limit_date;
- gboolean poll_event_fd = FALSE;
- int n_active = 0;
- int i;
-
- if (nfds > 1 || ufds[0].fd != -1)
- {
- if (!select_thread) {
- /* Create source used for signalling the main thread */
- main_thread_run_loop = CFRunLoopGetCurrent ();
- 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, kCFRunLoopDefaultMode);
-
- pipe (wakeup_pipe);
- fcntl(wakeup_pipe[0], F_SETFL, O_NONBLOCK);
-
- pthread_mutex_lock (&pollfd_mutex);
- pthread_create (&select_thread, NULL, select_thread_func, NULL);
- } else
- pthread_mutex_lock (&pollfd_mutex);
+}
- while (!ready_for_poll)
- pthread_cond_wait (&ready_cond, &pollfd_mutex);
+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);
- /* We cheat and use the fake fd (if it's polled) for our pipe */
+ /* 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);
- for (i = 0; i < nfds; i++)
- if (ufds[i].fd == -1)
- {
- poll_event_fd = TRUE;
- break;
- }
-
- g_free (pollfds);
-
- if (i == nfds)
- {
- n_pollfds = nfds + 1;
- pollfds = g_new (GPollFD, nfds + 1);
- memcpy (pollfds, ufds, nfds * sizeof (GPollFD));
- }
- else
- {
- pollfds = g_memdup (ufds, nfds * sizeof (GPollFD));
- n_pollfds = nfds;
- }
-
- pollfds[i].fd = wakeup_pipe[0];
- pollfds[i].events = G_IO_IN;
-
- /* Start our thread */
- pthread_cond_signal (&ready_cond);
- pthread_cond_wait (&ready_cond, &pollfd_mutex);
- pthread_mutex_unlock (&pollfd_mutex);
+ 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_print ("EventLoop: Dispatching high priority sources\n"));
+ g_main_context_dispatch (context);
}
+}
- if (timeout_ == -1)
- limit_date = [NSDate distantFuture];
- else if (timeout_ == 0)
- limit_date = [NSDate distantPast];
- else
- limit_date = [NSDate dateWithTimeIntervalSinceNow:timeout_/1000.0];
+static void
+dummy_timer_callback (CFRunLoopTimerRef timer,
+ void *info)
+{
+ /* Nothing; won't normally even be called */
+}
- event = [NSApp nextEventMatchingMask: NSAnyEventMask
- untilDate: limit_date
- inMode: NSDefaultRunLoopMode
- dequeue: YES];
+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.
+ */
- if (event)
+ 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)
{
- if ([event type] == NSApplicationDefined &&
- [event subtype] == GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP)
- {
- pthread_mutex_lock (&pollfd_mutex);
-
- for (i = 0; i < nfds; i++)
- {
- if (ufds[i].fd == -1)
- continue;
-
- g_assert (ufds[i].fd == pollfds[i].fd);
- g_assert (ufds[i].events == pollfds[i].events);
-
- if (pollfds[i].revents)
- {
- ufds[i].revents = pollfds[i].revents;
- n_active ++;
- }
- }
-
- pthread_mutex_unlock (&pollfd_mutex);
-
- /* Try to get a Cocoa event too, if requested */
- if (poll_event_fd)
- event = [NSApp nextEventMatchingMask: NSAnyEventMask
- untilDate: [NSDate distantPast]
- inMode: NSDefaultRunLoopMode
- dequeue: YES];
- else
- event = NULL;
- }
- }
-
- /* There were no active fds, break out of the other thread's poll() */
- pthread_mutex_lock (&pollfd_mutex);
- if (select_fd_waiting && wakeup_pipe[1])
+ /* We have stuff to do, no sleeping allowed! */
+ CFRunLoopWakeUp (main_thread_run_loop);
+ }
+ else if (timeout > 0)
{
- char c = 'A';
+ /* We need to get the run loop to break out of it's 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_print ("EventLoop: Adding timer to wake us up in %d milliseconds\n", 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);
- write (wakeup_pipe[1], &c, 1);
+ CFRunLoopAddTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
}
- pthread_mutex_unlock (&pollfd_mutex);
+
+ run_loop_polling_async = n_ready < 0;
+}
- if (event)
+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)
{
- for (i = 0; i < nfds; i++)
- {
- if (ufds[i].fd == -1)
- {
- ufds[i].revents = G_IO_IN;
- break;
- }
- }
+ 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_print ("EventLoop: Dispatching after waiting\n"));
+ g_main_context_dispatch (context);
+ }
+}
- if (!current_events)
- current_events = g_queue_new ();
- g_queue_push_head (current_events, [event retain]);
+static void
+run_loop_exit (void)
+{
+ g_return_if_fail (current_loop_level > 0);
- n_active ++;
+ if (current_loop_level == acquired_loop_level)
+ {
+ g_main_context_release (NULL);
+ acquired_loop_level = -1;
+ GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Ended tracking run loop activity\n"));
}
+
+ current_loop_level--;
+}
- return n_active;
+static void
+run_loop_observer_callback (CFRunLoopObserverRef observer,
+ CFRunLoopActivity activity,
+ void *info)
+{
+ if (getting_events) /* 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;
+ default:
+ break;
+ }
}
+/************************************************************/
+
void
_gdk_quartz_event_loop_init (void)
{
GSource *source;
+ CFRunLoopObserverRef observer;
+
+ /* Hook into the GLib main loop */
event_poll_fd.events = G_IO_IN;
event_poll_fd.fd = -1;
@@ -344,8 +993,22 @@
g_source_attach (source, NULL);
old_poll_func = g_main_context_get_poll_func (NULL);
- g_main_context_set_poll_func (NULL, poll_func);
+ g_main_context_set_poll_func (NULL, poll_func);
+
+ /* 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];
}
-
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]