mirror of
https://github.com/brl/mutter.git
synced 2024-11-25 17:40:40 -05:00
e70a0109f2
All backends follow the same pattern of queueing events first in ClutterMainContext, then copying them to a ClutterStage queue and immediately free them. Instead, we can just pass ownership of events directly to ClutterStage thus avoiding the allocation and copy in between. https://bugzilla.gnome.org/show_bug.cgi?id=711857
1057 lines
31 KiB
C
1057 lines
31 KiB
C
/* source code taken from gtk+-2.22.1/gdk/quartz/gdkeventloop-quartz.c */
|
|
/* Clutter - An OpenGL based 'interactive canvas' library.
|
|
* OSX backend - event loop
|
|
*
|
|
* Copyright (C) 2005-2007 Imendio AB
|
|
* Copyright (C) 2011 Crystalnix <vgachkaylo@crystalnix.com>
|
|
*
|
|
* 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 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "clutter-osx.h"
|
|
#include "clutter-stage-osx.h"
|
|
|
|
#import <AppKit/AppKit.h>
|
|
#include <glib.h>
|
|
#include <pthread.h>
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
#include <unistd.h>
|
|
|
|
#include "clutter-debug.h"
|
|
#include "clutter-private.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 clutter 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 = NULL;
|
|
|
|
/* 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;
|
|
|
|
static const char *const state_names[] = {
|
|
"BEFORE_START",
|
|
"WAITING",
|
|
"POLLING_QUEUED",
|
|
"POLLING_RESTART",
|
|
"POLLING_DESCRIPTORS"
|
|
};
|
|
|
|
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;
|
|
|
|
/* Events */
|
|
typedef enum {
|
|
CLUTTER_OSX_EVENT_SUBTYPE_EVENTLOOP
|
|
} ClutterOSXEventSubType;
|
|
|
|
static void
|
|
select_thread_set_state (SelectThreadState new_state)
|
|
{
|
|
gboolean old_state;
|
|
|
|
if (select_thread_state == new_state)
|
|
return;
|
|
|
|
CLUTTER_NOTE (EVENTLOOP, "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)
|
|
{
|
|
CLUTTER_NOTE (EVENTLOOP, "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: CLUTTER_OSX_EVENT_SUBTYPE_EVENTLOOP
|
|
data1: 0
|
|
data2: 0];
|
|
|
|
[NSApp postEvent:event atStart:YES];
|
|
}
|
|
|
|
static void
|
|
select_thread_start (void)
|
|
{
|
|
CFRunLoopSourceContext source_context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, got_fd_activity };
|
|
|
|
g_return_if_fail (select_thread_state == BEFORE_START);
|
|
|
|
pipe (select_thread_wakeup_pipe);
|
|
fcntl (select_thread_wakeup_pipe[0], F_SETFL, O_NONBLOCK);
|
|
|
|
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))
|
|
{
|
|
CLUTTER_NOTE (EVENTLOOP, "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 (CLUTTER_HAS_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) ||
|
|
(nfds == 2 && poll_fd_index >= 0)))
|
|
select_thread_set_state (POLLING_RESTART);
|
|
}
|
|
}
|
|
else
|
|
have_new_pollfds = TRUE;
|
|
|
|
if (have_new_pollfds)
|
|
{
|
|
CLUTTER_NOTE (EVENTLOOP, "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
|
|
_clutter_osx_event_loop_check_pending (void)
|
|
{
|
|
return current_events && current_events->head;
|
|
}
|
|
|
|
NSEvent*
|
|
_clutter_osx_event_loop_get_pending (void)
|
|
{
|
|
NSEvent *event = NULL;
|
|
|
|
if (current_events)
|
|
event = g_queue_pop_tail (current_events);
|
|
|
|
return event;
|
|
}
|
|
|
|
void
|
|
_clutter_osx_event_loop_release_event (NSEvent *event)
|
|
{
|
|
[event release];
|
|
}
|
|
|
|
static gboolean
|
|
clutter_event_prepare (GSource *source,
|
|
gint *timeout)
|
|
{
|
|
gboolean retval;
|
|
|
|
_clutter_threads_acquire_lock ();
|
|
|
|
*timeout = -1;
|
|
|
|
retval = (clutter_events_pending () || _clutter_osx_event_loop_check_pending ());
|
|
|
|
_clutter_threads_release_lock ();
|
|
|
|
return retval;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_event_check (GSource *source)
|
|
{
|
|
gboolean retval;
|
|
|
|
_clutter_threads_acquire_lock ();
|
|
|
|
/* 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 = (clutter_events_pending () || _clutter_osx_event_loop_check_pending ());
|
|
|
|
_clutter_threads_release_lock ();
|
|
|
|
return retval;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_event_dispatch (GSource *source,
|
|
GSourceFunc callback,
|
|
gpointer user_data)
|
|
{
|
|
NSEvent *nsevent;
|
|
ClutterEvent *event;
|
|
|
|
_clutter_threads_acquire_lock ();
|
|
|
|
nsevent = _clutter_osx_event_loop_get_pending ();
|
|
if (nsevent)
|
|
{
|
|
_clutter_threads_release_lock ();
|
|
|
|
[NSApp sendEvent:nsevent];
|
|
|
|
_clutter_threads_acquire_lock ();
|
|
|
|
_clutter_osx_event_loop_release_event (nsevent);
|
|
}
|
|
|
|
event = clutter_event_get ();
|
|
|
|
if (event)
|
|
{
|
|
/* forward the event into clutter for emission etc. */
|
|
_clutter_stage_queue_event (event->any.stage, event, FALSE);
|
|
}
|
|
|
|
_clutter_threads_release_lock ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GSourceFuncs event_funcs = {
|
|
clutter_event_prepare,
|
|
clutter_event_check,
|
|
clutter_event_dispatch,
|
|
NULL
|
|
};
|
|
|
|
/************************************************************
|
|
********* Our Poll Function *********
|
|
************************************************************/
|
|
|
|
static gint
|
|
poll_func (GPollFD *ufds,
|
|
guint nfds,
|
|
gint timeout_)
|
|
{
|
|
NSEvent *event;
|
|
NSDate *limit_date;
|
|
gint n_ready;
|
|
|
|
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 = 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] == CLUTTER_OSX_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;
|
|
}
|
|
|
|
/************************************************************
|
|
********* 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)
|
|
{
|
|
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)
|
|
{
|
|
current_loop_level++;
|
|
|
|
if (acquired_loop_level == -1)
|
|
{
|
|
if (g_main_context_acquire (NULL))
|
|
{
|
|
CLUTTER_NOTE (EVENTLOOP, "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.
|
|
*/
|
|
CLUTTER_NOTE (EVENTLOOP, "EventLoop: Can't acquire main loop; skipping tracking run loop activity\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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))
|
|
{
|
|
CLUTTER_NOTE (EVENTLOOP, "EventLoop: Dispatching high priority sources\n");
|
|
g_main_context_dispatch (context);
|
|
}
|
|
}
|
|
|
|
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 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.
|
|
*/
|
|
CLUTTER_NOTE (EVENTLOOP, "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;
|
|
}
|
|
|
|
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))
|
|
{
|
|
CLUTTER_NOTE (EVENTLOOP, "EventLoop: Dispatching after waiting\n");
|
|
g_main_context_dispatch (context);
|
|
}
|
|
}
|
|
|
|
static void
|
|
run_loop_exit (void)
|
|
{
|
|
g_return_if_fail (current_loop_level > 0);
|
|
|
|
if (current_loop_level == acquired_loop_level)
|
|
{
|
|
g_main_context_release (NULL);
|
|
acquired_loop_level = -1;
|
|
CLUTTER_NOTE (EVENTLOOP, "EventLoop: Ended tracking run loop activity\n");
|
|
}
|
|
|
|
current_loop_level--;
|
|
}
|
|
|
|
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
|
|
_clutter_osx_event_loop_init (void)
|
|
{
|
|
GSource *source;
|
|
CFRunLoopObserverRef observer;
|
|
|
|
g_assert (old_poll_func == NULL);
|
|
|
|
/* Hook into the GLib main loop */
|
|
|
|
event_poll_fd.events = G_IO_IN;
|
|
event_poll_fd.fd = -1;
|
|
|
|
source = g_source_new (&event_funcs, sizeof (GSource));
|
|
g_source_set_name (source, "Clutter OS X event source");
|
|
g_source_add_poll (source, &event_poll_fd);
|
|
g_source_set_priority (source, CLUTTER_PRIORITY_EVENTS);
|
|
g_source_set_can_recurse (source, TRUE);
|
|
g_source_attach (source, NULL);
|
|
|
|
old_poll_func = g_main_context_get_poll_func (NULL);
|
|
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];
|
|
}
|