Commit b81b9094 authored by Richard Hult's avatar Richard Hult Committed by Richard Hult

Bug 550942 – [patch] Rework of gdkeventloop-quartz.c

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.

svn path=/trunk/; revision=21783
parent 2c3590b4
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> 2008-11-12 Richard Hult <richard@imendio.com>
Bug 558586 – handling of keyboard under darwin (quartz) Bug 558586 – handling of keyboard under darwin (quartz)
......
...@@ -80,7 +80,8 @@ static const GDebugKey gdk_debug_keys[] = { ...@@ -80,7 +80,8 @@ static const GDebugKey gdk_debug_keys[] = {
{"cursor", GDK_DEBUG_CURSOR}, {"cursor", GDK_DEBUG_CURSOR},
{"multihead", GDK_DEBUG_MULTIHEAD}, {"multihead", GDK_DEBUG_MULTIHEAD},
{"xinerama", GDK_DEBUG_XINERAMA}, {"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); static const int gdk_ndebug_keys = G_N_ELEMENTS (gdk_debug_keys);
......
...@@ -85,7 +85,8 @@ typedef enum { ...@@ -85,7 +85,8 @@ typedef enum {
GDK_DEBUG_CURSOR = 1 <<11, GDK_DEBUG_CURSOR = 1 <<11,
GDK_DEBUG_MULTIHEAD = 1 <<12, GDK_DEBUG_MULTIHEAD = 1 <<12,
GDK_DEBUG_XINERAMA = 1 <<13, GDK_DEBUG_XINERAMA = 1 <<13,
GDK_DEBUG_DRAW = 1 <<14 GDK_DEBUG_DRAW = 1 <<14,
GDK_DEBUG_EVENTLOOP = 1 <<15
} GdkDebugFlag; } GdkDebugFlag;
#ifndef GDK_DISABLE_DEPRECATED #ifndef GDK_DISABLE_DEPRECATED
......
...@@ -8,22 +8,582 @@ ...@@ -8,22 +8,582 @@
#include "gdkprivate-quartz.h" #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; 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; 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 GPollFunc old_poll_func;
static gboolean select_fd_waiting = FALSE, ready_for_poll = FALSE; /* Reference to the run loop of the main thread. (There is a unique
static pthread_t select_thread = 0; * CFRunLoop per thread.)
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;
static CFRunLoopRef main_thread_run_loop; 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; 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 gboolean
_gdk_quartz_event_loop_check_pending (void) _gdk_quartz_event_loop_check_pending (void)
{ {
...@@ -51,21 +611,14 @@ static gboolean ...@@ -51,21 +611,14 @@ static gboolean
gdk_event_prepare (GSource *source, gdk_event_prepare (GSource *source,
gint *timeout) gint *timeout)
{ {
NSEvent *event;
gboolean retval; gboolean retval;
GDK_THREADS_ENTER (); GDK_THREADS_ENTER ();