/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
* Copyright (C) 2005-2007 Imendio AB
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 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 .
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include
#include
#include
#include
#include
#include "gdkdisplayprivate.h"
#include "gdkinternals.h"
#include "gdkmacoseventsource-private.h"
#include "gdkmacosdisplay-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 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 int 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 emitted
* or other things that shouldn’t happen inside a poll function.
*/
static GQueue *current_events;
/* The default poll function for GLib; we replace this with our own
* Cocoa-aware version and then call the old version to do actual
* file descriptor polling. There’s no actual need to chain to the
* old one; we could reimplement the same functionality from scratch,
* but since the default implementation does the right thing, why
* bother.
*/
static GPollFunc old_poll_func;
/* Reference to the run loop of the main thread. (There is a unique
* CFRunLoop per thread.)
*/
static CFRunLoopRef main_thread_run_loop;
/* Normally the Cocoa main loop maintains an NSAutoReleasePool and frees
* it on every iteration. Since we are replacing the main loop we have
* to provide this functionality ourself. We free and replace the
* auto-release pool in our sources prepare() function.
*/
static NSAutoreleasePool *autorelease_pool;
/* Flag when we've called nextEventMatchingMask ourself; this triggers
* a run loop iteration, so we need to detect that and avoid triggering
* our "run the GLib main loop while the run loop is active machinery.
*/
static int getting_events = 0;
/************************************************************
********* Select Thread *********
************************************************************/
/* The states in our state machine, see comments in select_thread_func()
* for descriptiions of each state
*/
typedef enum {
BEFORE_START,
WAITING,
POLLING_QUEUED,
POLLING_RESTART,
POLLING_DESCRIPTORS,
} SelectThreadState;
#ifdef G_ENABLE_DEBUG
static const char *const state_names[] = {
"BEFORE_START",
"WAITING",
"POLLING_QUEUED",
"POLLING_RESTART",
"POLLING_DESCRIPTORS"
};
#endif
static SelectThreadState select_thread_state = BEFORE_START;
static pthread_t select_thread;
static pthread_mutex_t select_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t select_thread_cond = PTHREAD_COND_INITIALIZER;
#define SELECT_THREAD_LOCK() pthread_mutex_lock (&select_thread_mutex)
#define SELECT_THREAD_UNLOCK() pthread_mutex_unlock (&select_thread_mutex)
#define SELECT_THREAD_SIGNAL() pthread_cond_signal (&select_thread_cond)
#define SELECT_THREAD_WAIT() pthread_cond_wait (&select_thread_cond, &select_thread_mutex)
/* These are the file descriptors that the select thread is currently
* polling.
*/
static GPollFD *current_pollfds;
static guint current_n_pollfds;
/* These are the file descriptors that the select thread should pick
* up and start polling when it has a chance.
*/
static GPollFD *next_pollfds;
static guint next_n_pollfds;
/* Pipe used to wake up the select thread */
static int select_thread_wakeup_pipe[2];
/* Run loop source used to wake up the main thread */
static CFRunLoopSourceRef select_main_thread_source;
static void
select_thread_set_state (SelectThreadState new_state)
{
gboolean old_state;
if (select_thread_state == new_state)
return;
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Select thread state: %s => %s", state_names[select_thread_state], state_names[new_state]));
old_state = select_thread_state;
select_thread_state = new_state;
if (old_state == WAITING && new_state != WAITING)
SELECT_THREAD_SIGNAL ();
}
static void
signal_main_thread (void)
{
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Waking up main thread"));
/* If we are in nextEventMatchingMask, then we need to make sure an
* event gets queued, otherwise it's enough to simply wake up the
* main thread run loop
*/
if (!run_loop_polling_async)
CFRunLoopSourceSignal (select_main_thread_source);
/* Don't check for CFRunLoopIsWaiting() here because it causes a
* race condition (the loop could go into waiting state right after
* we checked).
*/
CFRunLoopWakeUp (main_thread_run_loop);
}
static void *
select_thread_func (void *arg)
{
char c;
SELECT_THREAD_LOCK ();
while (TRUE)
{
switch (select_thread_state)
{
case BEFORE_START:
/* The select thread has not been started yet
*/
g_assert_not_reached ();
case WAITING:
/* Waiting for a set of file descriptors to be submitted by the main thread
*
* => POLLING_QUEUED: main thread thread submits a set of file descriptors
*/
SELECT_THREAD_WAIT ();
break;
case POLLING_QUEUED:
/* Waiting for a set of file descriptors to be submitted by the main thread
*
* => POLLING_DESCRIPTORS: select thread picks up the file descriptors to begin polling
*/
g_free (current_pollfds);
current_pollfds = next_pollfds;
current_n_pollfds = next_n_pollfds;
next_pollfds = NULL;
next_n_pollfds = 0;
select_thread_set_state (POLLING_DESCRIPTORS);
break;
case POLLING_RESTART:
/* Select thread is currently polling a set of file descriptors, main thread has
* began a new iteration with the same set of file descriptors. We don't want to
* wake the select thread up and wait for it to restart immediately, but to avoid
* a race (described below in select_thread_start_polling()) we need to recheck after
* polling completes.
*
* => POLLING_DESCRIPTORS: select completes, main thread rechecks by polling again
* => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
*/
select_thread_set_state (POLLING_DESCRIPTORS);
break;
case POLLING_DESCRIPTORS:
/* In the process of polling the file descriptors
*
* => WAITING: polling completes when a file descriptor becomes active
* => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
* => POLLING_RESTART: main thread begins a new iteration with the same set file descriptors
*/
SELECT_THREAD_UNLOCK ();
old_poll_func (current_pollfds, current_n_pollfds, -1);
SELECT_THREAD_LOCK ();
read (select_thread_wakeup_pipe[0], &c, 1);
if (select_thread_state == POLLING_DESCRIPTORS)
{
signal_main_thread ();
select_thread_set_state (WAITING);
}
break;
}
}
}
static void
got_fd_activity (void *info)
{
NSEvent *event;
/* Post a message so we'll break out of the message loop */
event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
location: NSZeroPoint
modifierFlags: 0
timestamp: 0
windowNumber: 0
context: nil
subtype: GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP
data1: 0
data2: 0];
[NSApp postEvent:event atStart:YES];
}
static void
select_thread_start (void)
{
g_return_if_fail (select_thread_state == BEFORE_START);
pipe (select_thread_wakeup_pipe);
fcntl (select_thread_wakeup_pipe[0], F_SETFL, O_NONBLOCK);
CFRunLoopSourceContext source_context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, got_fd_activity };
select_main_thread_source = CFRunLoopSourceCreate (NULL, 0, &source_context);
CFRunLoopAddSource (main_thread_run_loop, select_main_thread_source, kCFRunLoopCommonModes);
select_thread_state = WAITING;
while (TRUE)
{
if (pthread_create (&select_thread, NULL, select_thread_func, NULL) == 0)
break;
g_warning ("Failed to create select thread, sleeping and trying again");
sleep (1);
}
}
#ifdef G_ENABLE_DEBUG
static void
dump_poll_result (GPollFD *ufds,
guint nfds)
{
GString *s;
int i;
s = g_string_new ("");
for (i = 0; i < nfds; i++)
{
if (ufds[i].fd >= 0 && ufds[i].revents)
{
g_string_append_printf (s, " %d:", ufds[i].fd);
if (ufds[i].revents & G_IO_IN)
g_string_append (s, " in");
if (ufds[i].revents & G_IO_OUT)
g_string_append (s, " out");
if (ufds[i].revents & G_IO_PRI)
g_string_append (s, " pri");
g_string_append (s, "\n");
}
}
g_message ("%s", s->str);
g_string_free (s, TRUE);
}
#endif
static gboolean
pollfds_equal (GPollFD *old_pollfds,
guint old_n_pollfds,
GPollFD *new_pollfds,
guint new_n_pollfds)
{
int i;
if (old_n_pollfds != new_n_pollfds)
return FALSE;
for (i = 0; i < old_n_pollfds; i++)
{
if (old_pollfds[i].fd != new_pollfds[i].fd ||
old_pollfds[i].events != new_pollfds[i].events)
return FALSE;
}
return TRUE;
}
/* Begins a polling operation with the specified GPollFD array; the
* timeout is used only to tell if the polling operation is blocking
* or non-blocking.
*
* Returns:
* -1: No file descriptors ready, began asynchronous poll
* 0: No file descriptors ready, asynchronous poll not needed
* > 0: Number of file descriptors ready
*/
static int
select_thread_start_poll (GPollFD *ufds,
guint nfds,
int timeout)
{
int n_ready;
gboolean have_new_pollfds = FALSE;
int poll_fd_index = -1;
int i;
for (i = 0; i < nfds; i++)
if (ufds[i].fd == -1)
{
poll_fd_index = i;
break;
}
if (nfds == 0 ||
(nfds == 1 && poll_fd_index >= 0))
{
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Nothing to poll"));
return 0;
}
/* If we went immediately to an async poll, then we might decide to
* dispatch idle functions when higher priority file descriptor sources
* are ready to be dispatched. So we always need to first check
* check synchronously with a timeout of zero, and only when no
* sources are immediately ready, go to the asynchronous poll.
*
* Of course, if the timeout passed in is 0, then the synchronous
* check is sufficient and we never need to do the asynchronous poll.
*/
n_ready = old_poll_func (ufds, nfds, 0);
if (n_ready > 0 || timeout == 0)
{
#ifdef G_ENABLE_DEBUG
if ((_gdk_debug_flags & GDK_DEBUG_EVENTLOOP) && n_ready > 0)
{
g_message ("EventLoop: Found ready file descriptors before waiting");
dump_poll_result (ufds, nfds);
}
#endif
return n_ready;
}
SELECT_THREAD_LOCK ();
if (select_thread_state == BEFORE_START)
{
select_thread_start ();
}
if (select_thread_state == POLLING_QUEUED)
{
/* If the select thread hasn't picked up the set of file descriptors yet
* then we can simply replace an old stale set with a new set.
*/
if (!pollfds_equal (ufds, nfds, next_pollfds, next_n_pollfds - 1))
{
g_free (next_pollfds);
next_pollfds = NULL;
next_n_pollfds = 0;
have_new_pollfds = TRUE;
}
}
else if (select_thread_state == POLLING_RESTART || select_thread_state == POLLING_DESCRIPTORS)
{
/* If we are already in the process of polling the right set of file descriptors,
* there's no need for us to immediately force the select thread to stop polling
* and then restart again. And avoiding doing so increases the efficiency considerably
* in the common case where we have a set of basically inactive file descriptors that
* stay unchanged present as we process many events.
*
* However, we have to be careful that we don't hit the following race condition
* Select Thread Main Thread
* ----------------- ---------------
* Polling Completes
* Reads data or otherwise changes file descriptor state
* Checks if polling is current
* Does nothing (*)
* Releases lock
* Acquires lock
* Marks polling as complete
* Wakes main thread
* Receives old stale file descriptor state
*
* To avoid this, when the new set of poll descriptors is the same as the current
* one, we transition to the POLLING_RESTART stage at the point marked (*). When
* the select thread wakes up from the poll because a file descriptor is active, if
* the state is POLLING_RESTART it immediately begins polling same the file descriptor
* set again. This normally will just return the same set of active file descriptors
* as the first time, but in sequence described above will properly update the
* file descriptor state.
*
* Special case: this RESTART logic is not needed if the only FD is the internal GLib
* "wakeup pipe" that is presented when threads are initialized.
*
* P.S.: The harm in the above sequence is mostly that sources can be signalled
* as ready when they are no longer ready. This may prompt a blocking read
* from a file descriptor that hangs.
*/
if (!pollfds_equal (ufds, nfds, current_pollfds, current_n_pollfds - 1))
have_new_pollfds = TRUE;
else
{
if (!((nfds == 1 && poll_fd_index < 0) ||
(nfds == 2 && poll_fd_index >= 0)))
select_thread_set_state (POLLING_RESTART);
}
}
else
have_new_pollfds = TRUE;
if (have_new_pollfds)
{
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Submitting a new set of file descriptor to the select thread"));
g_assert (next_pollfds == NULL);
next_n_pollfds = nfds + 1;
next_pollfds = g_new (GPollFD, nfds + 1);
memcpy (next_pollfds, ufds, nfds * sizeof (GPollFD));
next_pollfds[nfds].fd = select_thread_wakeup_pipe[0];
next_pollfds[nfds].events = G_IO_IN;
if (select_thread_state != POLLING_QUEUED && select_thread_state != WAITING)
{
if (select_thread_wakeup_pipe[1])
{
char c = 'A';
write (select_thread_wakeup_pipe[1], &c, 1);
}
}
select_thread_set_state (POLLING_QUEUED);
}
SELECT_THREAD_UNLOCK ();
return -1;
}
/* End an asynchronous polling operation started with
* select_thread_collect_poll(). This must be called if and only if
* select_thread_start_poll() return -1. The GPollFD array passed
* in must be identical to the one passed to select_thread_start_poll().
*
* The results of the poll are written into the GPollFD array passed in.
*
* Returns: number of file descriptors ready
*/
static int
select_thread_collect_poll (GPollFD *ufds, guint nfds)
{
int i;
int n_ready = 0;
SELECT_THREAD_LOCK ();
if (select_thread_state == WAITING) /* The poll completed */
{
for (i = 0; i < nfds; i++)
{
if (ufds[i].fd == -1)
continue;
g_assert (ufds[i].fd == current_pollfds[i].fd);
g_assert (ufds[i].events == current_pollfds[i].events);
if (current_pollfds[i].revents)
{
ufds[i].revents = current_pollfds[i].revents;
n_ready++;
}
}
#ifdef G_ENABLE_DEBUG
if (_gdk_debug_flags & GDK_DEBUG_EVENTLOOP)
{
g_message ("EventLoop: Found ready file descriptors after waiting");
dump_poll_result (ufds, nfds);
}
#endif
}
SELECT_THREAD_UNLOCK ();
return n_ready;
}
/************************************************************
********* Main Loop Source *********
************************************************************/
typedef struct _GdkMacosEventSource
{
GSource source;
GdkDisplay *display;
} GdkMacosEventSource;
gboolean
_gdk_macos_event_source_check_pending (void)
{
return current_events && current_events->head;
}
NSEvent *
_gdk_macos_event_source_get_pending (void)
{
NSEvent *event = NULL;
if (current_events)
event = g_queue_pop_tail (current_events);
return event;
}
static gboolean
gdk_macos_event_source_prepare (GSource *source,
int *timeout)
{
GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
gboolean retval;
/* The prepare stage is the stage before the main loop starts polling
* and dispatching events. The autorelease poll is drained here for
* the preceding main loop iteration or, in case of the first iteration,
* for the operations carried out between event loop initialization and
* this first iteration.
*
* The autorelease poll must only be drained when the following conditions
* apply:
* - We are at the base CFRunLoop level (indicated by current_loop_level),
* - We are at the base g_main_loop level (indicated by
* g_main_depth())
* - We are at the base poll_func level (indicated by getting events).
*
* Messing with the autorelease pool at any level of nesting can cause access
* to deallocated memory because autorelease_pool is static and releasing a
* pool will cause all pools allocated inside of it to be released as well.
*/
if (current_loop_level == 0 && g_main_depth() == 0 && getting_events == 0)
{
if (autorelease_pool)
[autorelease_pool drain];
autorelease_pool = [[NSAutoreleasePool alloc] init];
}
*timeout = -1;
if (event_source->display->event_pause_count > 0)
retval = _gdk_event_queue_find_first (event_source->display) != NULL;
else
retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
_gdk_macos_event_source_check_pending ());
return retval;
}
static gboolean
gdk_macos_event_source_check (GSource *source)
{
GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
gboolean retval;
if (event_source->display->event_pause_count > 0)
retval = _gdk_event_queue_find_first (event_source->display) != NULL;
else
retval = (_gdk_event_queue_find_first (event_source->display) != NULL ||
_gdk_macos_event_source_check_pending ());
return retval;
}
static gboolean
gdk_macos_event_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
GdkEvent *event;
_gdk_macos_display_queue_events (GDK_MACOS_DISPLAY (event_source->display));
event = _gdk_event_unqueue (event_source->display);
if (event)
{
_gdk_event_emit (event);
gdk_event_unref (event);
}
return TRUE;
}
static void
gdk_macos_event_source_finalize (GSource *source)
{
GdkMacosEventSource *event_source = (GdkMacosEventSource *)source;
g_clear_object (&event_source->display);
}
static GSourceFuncs event_funcs = {
gdk_macos_event_source_prepare,
gdk_macos_event_source_check,
gdk_macos_event_source_dispatch,
gdk_macos_event_source_finalize,
};
/************************************************************
********* Our Poll Function *********
************************************************************/
static int
poll_func (GPollFD *ufds,
guint nfds,
int timeout_)
{
NSEvent *event;
NSDate *limit_date;
int n_ready;
static GPollFD *last_ufds;
last_ufds = ufds;
n_ready = select_thread_start_poll (ufds, nfds, timeout_);
if (n_ready > 0)
timeout_ = 0;
if (timeout_ == -1)
limit_date = [NSDate distantFuture];
else if (timeout_ == 0)
limit_date = [NSDate distantPast];
else
limit_date = [NSDate dateWithTimeIntervalSinceNow:timeout_/1000.0];
getting_events++;
event = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: limit_date
inMode: NSDefaultRunLoopMode
dequeue: YES];
getting_events--;
/* We check if last_ufds did not change since the time this function was
* called. It is possible that a recursive main loop (and thus recursive
* invocation of this poll function) is triggered while in
* nextEventMatchingMask:. If during that time new fds are added,
* the cached fds array might be replaced in g_main_context_iterate().
* So, we should avoid accessing the old fd array (still pointed at by
* ufds) here in that case, since it might have been freed. We avoid this
* by not calling the collect stage.
*/
if (last_ufds == ufds && n_ready < 0)
n_ready = select_thread_collect_poll (ufds, nfds);
if (event &&
[event type] == NSEventTypeApplicationDefined &&
[event subtype] == GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP)
{
/* 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 int
query_main_context (GMainContext *context,
int max_priority,
int *timeout)
{
int nfds;
if (!run_loop_pollfds)
{
run_loop_pollfds_size = RUN_LOOP_POLLFDS_INITIAL_SIZE;
run_loop_pollfds = g_new (GPollFD, run_loop_pollfds_size);
}
while ((nfds = g_main_context_query (context, max_priority, timeout,
run_loop_pollfds,
run_loop_pollfds_size)) > run_loop_pollfds_size)
{
g_free (run_loop_pollfds);
run_loop_pollfds_size = nfds;
run_loop_pollfds = g_new (GPollFD, nfds);
}
return nfds;
}
static void
run_loop_entry (void)
{
if (acquired_loop_level == -1)
{
if (g_main_context_acquire (NULL))
{
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Beginning tracking run loop activity"));
acquired_loop_level = current_loop_level;
}
else
{
/* If we fail to acquire the main context, that means someone is iterating
* the main context in a different thread; we simply wait until this loop
* exits and then try again at next entry. In general, iterating the loop
* from a different thread is rare: it is only possible when GDK threading
* is initialized and is not frequently used even then. So, we hope that
* having GLib main loop iteration blocked in the combination of that and
* a native modal operation is a minimal problem. We could imagine using a
* thread that does g_main_context_wait() and then wakes us back up, but
* the gain doesn't seem worth the complexity.
*/
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Can't acquire main loop; skipping tracking run loop activity"));
}
}
}
static void
run_loop_before_timers (void)
{
}
static void
run_loop_before_sources (void)
{
GMainContext *context = g_main_context_default ();
int max_priority;
int 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))
{
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Dispatching high priority sources"));
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 ();
int timeout;
int n_ready;
/* At this point, the CFRunLoop is ready to wait. We start a GMain loop
* iteration by calling the check() and query() stages. We start a
* poll, and if it doesn't complete immediately we let the run loop
* go ahead and sleep. Before doing that, if there was a timeout from
* GLib, we set up a CFRunLoopTimer to wake us up.
*/
g_main_context_prepare (context, &run_loop_max_priority);
run_loop_n_pollfds = query_main_context (context, run_loop_max_priority, &timeout);
n_ready = select_thread_start_poll (run_loop_pollfds, run_loop_n_pollfds, timeout);
if (n_ready > 0 || timeout == 0)
{
/* We have stuff to do, no sleeping allowed! */
CFRunLoopWakeUp (main_thread_run_loop);
}
else if (timeout > 0)
{
/* We need to get the run loop to break out of its wait when our timeout
* expires. We do this by adding a dummy timer that we'll remove immediately
* after the wait wakes up.
*/
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Adding timer to wake us up in %d milliseconds", timeout));
run_loop_timer = CFRunLoopTimerCreate (NULL, /* allocator */
CFAbsoluteTimeGetCurrent () + timeout / 1000.,
0, /* interval (0=does not repeat) */
0, /* flags */
0, /* order (priority) */
dummy_timer_callback,
NULL);
CFRunLoopAddTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
}
run_loop_polling_async = n_ready < 0;
}
static void
run_loop_after_waiting (void)
{
GMainContext *context = g_main_context_default ();
/* After sleeping, we finish of the GMain loop iteratin started in before_waiting()
* by doing the check() and dispatch() stages.
*/
if (run_loop_timer)
{
CFRunLoopRemoveTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
CFRelease (run_loop_timer);
run_loop_timer = NULL;
}
if (run_loop_polling_async)
{
select_thread_collect_poll (run_loop_pollfds, run_loop_n_pollfds);
run_loop_polling_async = FALSE;
}
if (g_main_context_check (context, run_loop_max_priority, run_loop_pollfds, run_loop_n_pollfds))
{
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Dispatching after waiting"));
g_main_context_dispatch (context);
}
}
static void
run_loop_exit (void)
{
/* + 1 because we decrement current_loop_level separately in observer_callback() */
if ((current_loop_level + 1) == acquired_loop_level)
{
g_main_context_release (NULL);
acquired_loop_level = -1;
GDK_NOTE (EVENTLOOP, g_message ("EventLoop: Ended tracking run loop activity"));
}
}
static void
run_loop_observer_callback (CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void *info)
{
switch (activity)
{
case kCFRunLoopEntry:
current_loop_level++;
break;
case kCFRunLoopExit:
g_return_if_fail (current_loop_level > 0);
current_loop_level--;
break;
case kCFRunLoopBeforeTimers:
case kCFRunLoopBeforeSources:
case kCFRunLoopBeforeWaiting:
case kCFRunLoopAfterWaiting:
case kCFRunLoopAllActivities:
default:
break;
}
if (getting_events > 0) /* Activity we triggered */
return;
switch (activity)
{
case kCFRunLoopEntry:
run_loop_entry ();
break;
case kCFRunLoopBeforeTimers:
run_loop_before_timers ();
break;
case kCFRunLoopBeforeSources:
run_loop_before_sources ();
break;
case kCFRunLoopBeforeWaiting:
run_loop_before_waiting ();
break;
case kCFRunLoopAfterWaiting:
run_loop_after_waiting ();
break;
case kCFRunLoopExit:
run_loop_exit ();
break;
case kCFRunLoopAllActivities:
/* TODO: Do most of the above? */
default:
break;
}
}
/************************************************************/
GSource *
_gdk_macos_event_source_new (GdkMacosDisplay *display)
{
CFRunLoopObserverRef observer;
GdkMacosEventSource *event_source;
GSource *source;
g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
/* Hook into the GLib main loop */
event_poll_fd.events = G_IO_IN;
event_poll_fd.fd = -1;
source = g_source_new (&event_funcs, sizeof (GdkMacosEventSource));
g_source_set_name (source, "GDK Quartz event source");
g_source_add_poll (source, &event_poll_fd);
g_source_set_priority (source, GDK_PRIORITY_EVENTS);
g_source_set_can_recurse (source, TRUE);
old_poll_func = g_main_context_get_poll_func (NULL);
g_main_context_set_poll_func (NULL, poll_func);
event_source = (GdkMacosEventSource *)source;
event_source->display = g_object_ref (GDK_DISPLAY (display));
/* Hook into the the CFRunLoop for the main thread */
main_thread_run_loop = CFRunLoopGetCurrent ();
observer = CFRunLoopObserverCreate (NULL, /* default allocator */
kCFRunLoopAllActivities,
true, /* repeats: not one-shot */
0, /* order (priority) */
run_loop_observer_callback,
NULL);
CFRunLoopAddObserver (main_thread_run_loop, observer, kCFRunLoopCommonModes);
/* Initialize our autorelease pool */
autorelease_pool = [[NSAutoreleasePool alloc] init];
return source;
}