Add API to install an event filter

This adds clutter_event_add/remove_filter which adds a callback
function which will receive all Clutter events just before the event
signal is emitted for them. The event filter will be invoked
regardless of any grabs or captures. This will be used by Mutter which
wants to access the events at a lower level then the event bubbling
mechanism. It needs to see all mouse motion events even if there is a
grab in place.

https://bugzilla.gnome.org/show_bug.cgi?id=707560
This commit is contained in:
Neil Roberts 2013-08-29 17:10:56 +01:00 committed by Jasper St. Pierre
parent c2b0b9aace
commit 70292672c4
6 changed files with 162 additions and 1 deletions

View File

@ -11,6 +11,8 @@ void _clutter_event_set_pointer_emulated (ClutterEvent *eve
/* Reinjecting queued events for processing */
void _clutter_process_event (ClutterEvent *event);
gboolean _clutter_event_process_filters (ClutterEvent *event);
/* clears the event queue inside the main context */
void _clutter_clear_events_queue (void);
void _clutter_clear_events_queue_for_stage (ClutterStage *stage);

View File

@ -64,6 +64,15 @@ typedef struct _ClutterEventPrivate {
guint is_pointer_emulated : 1;
} ClutterEventPrivate;
typedef struct _ClutterEventFilter {
int id;
ClutterStage *stage;
ClutterEventFilterFunc func;
GDestroyNotify notify;
gpointer user_data;
} ClutterEventFilter;
static GHashTable *all_events = NULL;
G_DEFINE_BOXED_TYPE (ClutterEvent, clutter_event,
@ -1720,3 +1729,100 @@ clutter_event_is_pointer_emulated (const ClutterEvent *event)
return ((ClutterEventPrivate *) event)->is_pointer_emulated;
}
gboolean
_clutter_event_process_filters (ClutterEvent *event)
{
ClutterMainContext *context = _clutter_context_get_default ();
GList *l, *next;
/* Event filters are handled in order from least recently added to
* most recently added */
for (l = context->event_filters; l; l = next)
{
ClutterEventFilter *event_filter = l->data;
next = l->next;
if (event_filter->stage && event_filter->stage != event->any.stage)
continue;
if (event_filter->func (event, event_filter->user_data) == CLUTTER_EVENT_STOP)
return CLUTTER_EVENT_STOP;
}
return CLUTTER_EVENT_PROPAGATE;
}
/**
* clutter_event_add_filter:
* @stage: (allow-none): The #ClutterStage to capture events for
* @func: The callback function which will be passed all events.
* @notify: A #GDestroyNotify
* @user_data: A data pointer to pass to the function.
*
* Adds a function which will be called for all events that Clutter
* processes. The function will be called before any signals are
* emitted for the event and it will take precedence over any grabs.
*
* Return value: an identifier for the event filter, to be used
* with clutter_event_remove_filter().
*
* Since: 1.18
*/
guint
clutter_event_add_filter (ClutterStage *stage,
ClutterEventFilterFunc func,
GDestroyNotify notify,
gpointer user_data)
{
ClutterMainContext *context = _clutter_context_get_default ();
ClutterEventFilter *event_filter = g_slice_new (ClutterEventFilter);
static guint event_filter_id = 0;
event_filter->stage = stage;
event_filter->id = ++event_filter_id;
event_filter->func = func;
event_filter->notify = notify;
event_filter->user_data = user_data;
/* The event filters are kept in order from least recently added to
* most recently added so we must add it to the end */
context->event_filters = g_list_append (context->event_filters, event_filter);
return event_filter->id;
}
/**
* clutter_event_remove_filter:
* @id: The ID of the event filter, as returned from clutter_event_add_filter()
*
* Removes an event filter that was previously added with
* clutter_event_add_filter().
*
* Since: 1.18
*/
void
clutter_event_remove_filter (guint id)
{
ClutterMainContext *context = _clutter_context_get_default ();
GList *l;
for (l = context->event_filters; l; l = l->next)
{
ClutterEventFilter *event_filter = l->data;
if (event_filter->id == id)
{
if (event_filter->notify)
event_filter->notify (event_filter->user_data);
context->event_filters = g_list_delete_link (context->event_filters, l);
g_slice_free (ClutterEventFilter, event_filter);
return;
}
}
g_warning ("No event filter found for id: %d\n", id);
}

View File

@ -405,6 +405,24 @@ union _ClutterEvent
ClutterTouchEvent touch;
};
/**
* ClutterEventFilterFunc:
* @event: the event that is going to be emitted
* @user_data: the data pointer passed to clutter_event_add_filter()
*
* A function pointer type used by event filters that are added with
* clutter_event_add_filter().
*
* Return value: %CLUTTER_EVENT_STOP to indicate that the event
* has been handled or %CLUTTER_EVENT_PROPAGATE otherwise.
* Returning %CLUTTER_EVENT_STOP skips any further filter
* functions and prevents the signal emission for the event.
*
* Since: 1.18
*/
typedef gboolean (* ClutterEventFilterFunc) (const ClutterEvent *event,
gpointer user_data);
GType clutter_event_get_type (void) G_GNUC_CONST;
gboolean clutter_events_pending (void);
@ -412,6 +430,14 @@ ClutterEvent * clutter_event_get (void);
ClutterEvent * clutter_event_peek (void);
void clutter_event_put (const ClutterEvent *event);
CLUTTER_AVAILABLE_IN_1_18
guint clutter_event_add_filter (ClutterStage *stage,
ClutterEventFilterFunc func,
GDestroyNotify notify,
gpointer user_data);
CLUTTER_AVAILABLE_IN_1_18
void clutter_event_remove_filter (guint id);
ClutterEvent * clutter_event_new (ClutterEventType type);
ClutterEvent * clutter_event_copy (const ClutterEvent *event);
void clutter_event_free (ClutterEvent *event);

View File

@ -2279,6 +2279,9 @@ emit_pointer_event (ClutterEvent *event,
{
ClutterMainContext *context = _clutter_context_get_default ();
if (_clutter_event_process_filters (event))
return;
if (context->pointer_grab_actor == NULL &&
(device == NULL || device->pointer_grab_actor == NULL))
{
@ -2306,6 +2309,9 @@ emit_touch_event (ClutterEvent *event,
{
ClutterActor *grab_actor = NULL;
if (_clutter_event_process_filters (event))
return;
if (device->sequence_grab_actors != NULL)
{
grab_actor = g_hash_table_lookup (device->sequence_grab_actors,
@ -2330,6 +2336,9 @@ emit_keyboard_event (ClutterEvent *event,
{
ClutterMainContext *context = _clutter_context_get_default ();
if (_clutter_event_process_filters (event))
return;
if (context->keyboard_grab_actor == NULL &&
(device == NULL || device->keyboard_grab_actor == NULL))
{
@ -2493,6 +2502,10 @@ _clutter_process_event_details (ClutterActor *stage,
case CLUTTER_DESTROY_NOTIFY:
case CLUTTER_DELETE:
event->any.source = stage;
if (_clutter_event_process_filters (event))
break;
/* the stage did not handle the event, so we just quit */
clutter_stage_event (CLUTTER_STAGE (stage), event);
break;
@ -2505,6 +2518,9 @@ _clutter_process_event_details (ClutterActor *stage,
/* Only stage gets motion events */
event->any.source = stage;
if (_clutter_event_process_filters (event))
break;
/* global grabs */
if (context->pointer_grab_actor != NULL)
{
@ -2632,6 +2648,9 @@ _clutter_process_event_details (ClutterActor *stage,
/* Only stage gets motion events */
event->any.source = stage;
if (_clutter_event_process_filters (event))
break;
/* global grabs */
if (device->sequence_grab_actors != NULL)
{
@ -2735,7 +2754,8 @@ _clutter_process_event_details (ClutterActor *stage,
case CLUTTER_STAGE_STATE:
/* fullscreen / focus - forward to stage */
event->any.source = stage;
clutter_stage_event (CLUTTER_STAGE (stage), event);
if (!_clutter_event_process_filters (event))
clutter_stage_event (CLUTTER_STAGE (stage), event);
break;
case CLUTTER_CLIENT_MESSAGE:

View File

@ -137,6 +137,10 @@ struct _ClutterMainContext
/* the main event queue */
GQueue *events_queue;
/* the event filters added via clutter_event_add_filter. these are
* ordered from least recently added to most recently added */
GList *event_filters;
ClutterPickMode pick_mode;
/* mapping between reused integer ids and actors */

View File

@ -1132,6 +1132,9 @@ clutter_event_get
clutter_event_peek
clutter_event_put
clutter_events_pending
ClutterEventFilterFunc
clutter_event_add_filter
clutter_event_remove_filter
<SUBSECTION>
CLUTTER_BUTTON_PRIMARY