diff options
-rw-r--r-- | ChangeLog | 10 | ||||
-rw-r--r-- | gdk/gdk.c | 3 | ||||
-rw-r--r-- | gdk/gdkinternals.h | 3 | ||||
-rw-r--r-- | gdk/quartz/gdkeventloop-quartz.c | 1047 |
4 files changed, 869 insertions, 194 deletions
@@ -1,5 +1,15 @@ 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. + +2008-11-12 Richard Hult <richard@imendio.com> + Bug 558586 – handling of keyboard under darwin (quartz) * gdk/quartz/gdkkeys-quartz.c: Follow up on this bug, only use the @@ -80,7 +80,8 @@ static const GDebugKey gdk_debug_keys[] = { {"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); diff --git a/gdk/gdkinternals.h b/gdk/gdkinternals.h index 09ae12dd50..61a5433e2a 100644 --- a/gdk/gdkinternals.h +++ b/gdk/gdkinternals.h @@ -85,7 +85,8 @@ typedef enum { 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 diff --git a/gdk/quartz/gdkeventloop-quartz.c b/gdk/quartz/gdkeventloop-quartz.c index 50b2c7e90c..9f87371a20 100644 --- a/gdk/quartz/gdkeventloop-quartz.c +++ b/gdk/quartz/gdkeventloop-quartz.c @@ -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 @@ static gboolean 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_event_check (GSource *source) 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 @@ static GSourceFuncs event_funcs = { 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; + 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); + } - pthread_mutex_lock (&pollfd_mutex); + 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; +} - while (1) +static void +run_loop_entry (void) +{ + current_loop_level++; + + if (acquired_loop_level == -1) { - char c; - int n; - - 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'); - - n_active_fds --; + if (g_main_context_acquire (NULL)) + { + GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Beginning tracking run loop activity\n")); + acquired_loop_level = current_loop_level; } - pthread_mutex_unlock (&pollfd_mutex); - - if (n_active_fds) + else { - /* We have active fds, signal the main thread */ - CFRunLoopSourceSignal (select_main_thread_source); - if (CFRunLoopIsWaiting (main_thread_run_loop)) - CFRunLoopWakeUp (main_thread_run_loop); + /* 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); +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); - pipe (wakeup_pipe); - fcntl(wakeup_pipe[0], F_SETFL, O_NONBLOCK); + /* 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); - pthread_mutex_lock (&pollfd_mutex); - pthread_create (&select_thread, NULL, select_thread_func, NULL); - } else - pthread_mutex_lock (&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); + } +} - while (!ready_for_poll) - pthread_cond_wait (&ready_cond, &pollfd_mutex); +static void +dummy_timer_callback (CFRunLoopTimerRef timer, + void *info) +{ + /* Nothing; won't normally even be called */ +} - /* We cheat and use the fake fd (if it's polled) for our pipe */ +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); - 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); + 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 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); + + CFRunLoopAddTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes); } + + run_loop_polling_async = n_ready < 0; +} - 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 +run_loop_after_waiting (void) +{ + GMainContext *context = g_main_context_default (); - event = [NSApp nextEventMatchingMask: NSAnyEventMask - untilDate: limit_date - inMode: NSDefaultRunLoopMode - dequeue: YES]; - - if (event) + /* After sleeping, we finish of the GMain loop iteratin started in before_waiting() + * by doing the check() and dispatch() stages. + */ + + if (run_loop_timer) { - 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; - } + CFRunLoopRemoveTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes); + CFRelease (run_loop_timer); + run_loop_timer = 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]) + + if (run_loop_polling_async) { - char c = 'A'; - - write (wakeup_pipe[1], &c, 1); + select_thread_collect_poll (run_loop_pollfds, run_loop_n_pollfds); + run_loop_polling_async = FALSE; } - pthread_mutex_unlock (&pollfd_mutex); - - if (event) + + if (g_main_context_check (context, run_loop_max_priority, run_loop_pollfds, run_loop_n_pollfds)) { - for (i = 0; i < nfds; i++) - { - if (ufds[i].fd == -1) - { - ufds[i].revents = G_IO_IN; - break; - } - } + 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 @@ _gdk_quartz_event_loop_init (void) 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]; } - |