From 70292672c4381c3039bd88255c4f57d45e142599 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Thu, 29 Aug 2013 17:10:56 +0100 Subject: [PATCH] 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 --- clutter/clutter-event-private.h | 2 + clutter/clutter-event.c | 106 +++++++++++++++++++++ clutter/clutter-event.h | 26 +++++ clutter/clutter-main.c | 22 ++++- clutter/clutter-private.h | 4 + doc/reference/clutter/clutter-sections.txt | 3 + 6 files changed, 162 insertions(+), 1 deletion(-) diff --git a/clutter/clutter-event-private.h b/clutter/clutter-event-private.h index 955db260e..00d627d12 100644 --- a/clutter/clutter-event-private.h +++ b/clutter/clutter-event-private.h @@ -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); diff --git a/clutter/clutter-event.c b/clutter/clutter-event.c index 03655f6c3..06870c61d 100644 --- a/clutter/clutter-event.c +++ b/clutter/clutter-event.c @@ -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); +} diff --git a/clutter/clutter-event.h b/clutter/clutter-event.h index 30e84d988..f41375c78 100644 --- a/clutter/clutter-event.h +++ b/clutter/clutter-event.h @@ -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); diff --git a/clutter/clutter-main.c b/clutter/clutter-main.c index ef83665d9..6363ad9e2 100644 --- a/clutter/clutter-main.c +++ b/clutter/clutter-main.c @@ -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: diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h index 2136efe75..65154926f 100644 --- a/clutter/clutter-private.h +++ b/clutter/clutter-private.h @@ -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 */ diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index 6029cef15..d3c7f5bd1 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -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 CLUTTER_BUTTON_PRIMARY