[mutter/wip/gestures: 7/16] core: Add MetaGestureTracker
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [mutter/wip/gestures: 7/16] core: Add MetaGestureTracker
- Date: Wed, 25 Jun 2014 16:20:24 +0000 (UTC)
commit b5c605df5e9e1f0c1371efc20b4253d27d516400
Author: Carlos Garnacho <carlosg gnome org>
Date: Thu Jun 19 22:29:10 2014 +0200
core: Add MetaGestureTracker
This object tracks both touch sequences happening on the stage and
gestures attached to the stage actor. When a gesture emits
::gesture-begin, All triggering sequences and future ones will be
marked as "accepted" by the compositor, and events will be listened
for meanwhile there are active gestures.
If a sequence goes unclaimed for a short time, it will be
automatically "denied", and punted to the client or shell element
below.
src/Makefile.am | 2 +
src/core/gesture-tracker-private.h | 83 ++++++
src/core/gesture-tracker.c | 536 ++++++++++++++++++++++++++++++++++++
3 files changed, 621 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 6f7cf5f..be8bb95 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -152,6 +152,8 @@ libmutter_la_SOURCES = \
core/frame.h \
ui/gradient.c \
meta/gradient.h \
+ core/gesture-tracker.c \
+ core/gesture-tracker-private.h \
core/keybindings.c \
core/keybindings-private.h \
core/main.c \
diff --git a/src/core/gesture-tracker-private.h b/src/core/gesture-tracker-private.h
new file mode 100644
index 0000000..296866e
--- /dev/null
+++ b/src/core/gesture-tracker-private.h
@@ -0,0 +1,83 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file gesture-tracker-private.h Manages gestures on windows/desktop
+ *
+ * Forwards touch events to clutter actors, and accepts/rejects touch sequences
+ * based on the outcome of those.
+ */
+
+/*
+ * Copyright (C) 2014 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Carlos Garnacho <carlosg gnome org>
+ */
+
+#ifndef META_GESTURE_TRACKER_PRIVATE_H
+#define META_GESTURE_TRACKER_PRIVATE_H
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+#include <meta/window.h>
+
+#define META_TYPE_GESTURE_TRACKER (meta_gesture_tracker_get_type ())
+#define META_GESTURE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_GESTURE_TRACKER,
MetaGestureTracker))
+#define META_GESTURE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_GESTURE_TRACKER,
MetaGestureTrackerClass))
+#define META_IS_GESTURE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_GESTURE_TRACKER))
+#define META_IS_GESTURE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_GESTURE_TRACKER))
+#define META_GESTURE_TRACKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_GESTURE_TRACKER,
MetaGestureTrackerClass))
+
+typedef struct _MetaGestureTracker MetaGestureTracker;
+typedef struct _MetaGestureTrackerClass MetaGestureTrackerClass;
+
+typedef enum {
+ META_SEQUENCE_NONE,
+ META_SEQUENCE_ACCEPTED,
+ META_SEQUENCE_REJECTED,
+ META_SEQUENCE_PENDING_END
+} MetaSequenceState;
+
+struct _MetaGestureTracker
+{
+ GObject parent_instance;
+};
+
+struct _MetaGestureTrackerClass
+{
+ GObjectClass parent_class;
+
+ void (* state_changed) (MetaGestureTracker *tracker,
+ ClutterEventSequence *sequence,
+ MetaSequenceState state);
+};
+
+GType meta_gesture_tracker_get_type (void) G_GNUC_CONST;
+
+MetaGestureTracker * meta_gesture_tracker_new (guint autodeny_timeout);
+
+gboolean meta_gesture_tracker_handle_event (MetaGestureTracker *tracker,
+ const ClutterEvent *event);
+gboolean meta_gesture_tracker_set_sequence_state (MetaGestureTracker *tracker,
+ ClutterEventSequence *sequence,
+ MetaSequenceState state);
+MetaSequenceState meta_gesture_tracker_get_sequence_state (MetaGestureTracker *tracker,
+ ClutterEventSequence *sequence);
+gboolean meta_gesture_tracker_consumes_event (MetaGestureTracker *tracker,
+ const ClutterEvent *event);
+
+#endif /* META_GESTURE_TRACKER_PRIVATE_H */
diff --git a/src/core/gesture-tracker.c b/src/core/gesture-tracker.c
new file mode 100644
index 0000000..5fd6579
--- /dev/null
+++ b/src/core/gesture-tracker.c
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2014 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Carlos Garnacho <carlosg gnome org>
+ */
+
+#include "config.h"
+#include "gesture-tracker-private.h"
+#include "meta-surface-actor.h"
+
+typedef struct _MetaGestureTrackerPrivate MetaGestureTrackerPrivate;
+typedef struct _GestureActionData GestureActionData;
+typedef struct _MetaSequenceInfo MetaSequenceInfo;
+
+struct _MetaSequenceInfo
+{
+ MetaGestureTracker *tracker;
+ ClutterEventSequence *sequence;
+ MetaSequenceState state;
+ guint autodeny_timeout_id;
+};
+
+struct _GestureActionData
+{
+ ClutterGestureAction *gesture;
+ MetaSequenceState state;
+ guint gesture_begin_id;
+ guint gesture_end_id;
+ guint gesture_cancel_id;
+};
+
+struct _MetaGestureTrackerPrivate
+{
+ GHashTable *sequences; /* Hashtable of ClutterEventSequence->MetaSequenceInfo */
+
+ MetaSequenceState stage_state;
+ GArray *stage_gestures; /* Array of GestureActionData */
+ GList *listeners; /* List of ClutterGestureAction */
+ guint autodeny_timeout;
+};
+
+enum {
+ PROP_AUTODENY_TIMEOUT = 1
+};
+
+enum {
+ STATE_CHANGED,
+ N_SIGNALS
+};
+
+#define DEFAULT_AUTODENY_TIMEOUT 150
+
+static guint signals[N_SIGNALS] = { 0 };
+
+static void meta_gesture_tracker_untrack_stage (MetaGestureTracker *tracker);
+
+G_DEFINE_TYPE_WITH_PRIVATE (MetaGestureTracker, meta_gesture_tracker, G_TYPE_OBJECT)
+
+static void
+meta_gesture_tracker_finalize (GObject *object)
+{
+ MetaGestureTrackerPrivate *priv;
+
+ priv = meta_gesture_tracker_get_instance_private (META_GESTURE_TRACKER (object));
+
+ g_hash_table_destroy (priv->sequences);
+ g_array_free (priv->stage_gestures, TRUE);
+ g_list_free (priv->listeners);
+
+ G_OBJECT_CLASS (meta_gesture_tracker_parent_class)->finalize (object);
+}
+
+static void
+meta_gesture_tracker_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MetaGestureTrackerPrivate *priv;
+
+ priv = meta_gesture_tracker_get_instance_private (META_GESTURE_TRACKER (object));
+
+ switch (prop_id)
+ {
+ case PROP_AUTODENY_TIMEOUT:
+ priv->autodeny_timeout = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+meta_gesture_tracker_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MetaGestureTrackerPrivate *priv;
+
+ priv = meta_gesture_tracker_get_instance_private (META_GESTURE_TRACKER (object));
+
+ switch (prop_id)
+ {
+ case PROP_AUTODENY_TIMEOUT:
+ g_value_set_uint (value, priv->autodeny_timeout);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+meta_gesture_tracker_class_init (MetaGestureTrackerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = meta_gesture_tracker_finalize;
+ object_class->set_property = meta_gesture_tracker_set_property;
+ object_class->get_property = meta_gesture_tracker_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_AUTODENY_TIMEOUT,
+ g_param_spec_uint ("autodeny-timeout",
+ "Auto-deny timeout",
+ "Auto-deny timeout",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+ signals[STATE_CHANGED] =
+ g_signal_new ("state-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (MetaGestureTrackerClass, state_changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+}
+
+static gboolean
+autodeny_sequence (gpointer user_data)
+{
+ MetaSequenceInfo *info = user_data;
+
+ /* Deny the sequence automatically after the given timeout */
+ if (info->state == META_SEQUENCE_NONE)
+ meta_gesture_tracker_set_sequence_state (info->tracker, info->sequence,
+ META_SEQUENCE_REJECTED);
+
+ info->autodeny_timeout_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static MetaSequenceInfo *
+meta_sequence_info_new (MetaGestureTracker *tracker,
+ const ClutterEvent *event)
+{
+ MetaGestureTrackerPrivate *priv;
+ MetaSequenceInfo *info;
+ guint ms;
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+ ms = (priv->autodeny_timeout) ?
+ priv->autodeny_timeout : DEFAULT_AUTODENY_TIMEOUT;
+
+ info = g_slice_new0 (MetaSequenceInfo);
+ info->tracker = tracker;
+ info->sequence = event->touch.sequence;
+ info->state = META_SEQUENCE_NONE;
+ info->autodeny_timeout_id = g_timeout_add (ms, autodeny_sequence, info);
+
+ return info;
+}
+
+static void
+meta_sequence_info_free (MetaSequenceInfo *info)
+{
+ if (info->autodeny_timeout_id)
+ g_source_remove (info->autodeny_timeout_id);
+
+ if (info->state == META_SEQUENCE_NONE)
+ meta_gesture_tracker_set_sequence_state (info->tracker, info->sequence,
+ META_SEQUENCE_REJECTED);
+ g_slice_free (MetaSequenceInfo, info);
+}
+
+static gboolean
+state_is_applicable (MetaSequenceState prev_state,
+ MetaSequenceState state)
+{
+ if (prev_state == META_SEQUENCE_PENDING_END)
+ return FALSE;
+
+ /* Don't allow reverting to none */
+ if (state == META_SEQUENCE_NONE)
+ return FALSE;
+
+ /* PENDING_END state is final */
+ if (prev_state == META_SEQUENCE_PENDING_END)
+ return FALSE;
+
+ /* Sequences must be accepted/denied before PENDING_END */
+ if (prev_state == META_SEQUENCE_NONE &&
+ state == META_SEQUENCE_PENDING_END)
+ return FALSE;
+
+ /* Make sequences stick to their accepted/denied state */
+ if (state != META_SEQUENCE_PENDING_END &&
+ prev_state != META_SEQUENCE_NONE)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+meta_gesture_tracker_set_state (MetaGestureTracker *tracker,
+ MetaSequenceState state)
+{
+ MetaGestureTrackerPrivate *priv;
+ ClutterEventSequence *sequence;
+ GHashTableIter iter;
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+
+ if (priv->stage_state != state &&
+ !state_is_applicable (priv->stage_state, state))
+ return FALSE;
+
+ g_hash_table_iter_init (&iter, priv->sequences);
+ priv->stage_state = state;
+
+ while (g_hash_table_iter_next (&iter, (gpointer*) &sequence, NULL))
+ meta_gesture_tracker_set_sequence_state (tracker, sequence, state);
+
+ return TRUE;
+}
+
+static gboolean
+gesture_begin_cb (ClutterGestureAction *gesture,
+ ClutterActor *actor,
+ MetaGestureTracker *tracker)
+{
+ MetaGestureTrackerPrivate *priv;
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+
+ if (!g_list_find (priv->listeners, gesture) &&
+ meta_gesture_tracker_set_state (tracker, META_SEQUENCE_ACCEPTED))
+ priv->listeners = g_list_prepend (priv->listeners, gesture);
+
+ return TRUE;
+}
+
+static void
+gesture_end_cb (ClutterGestureAction *gesture,
+ ClutterActor *actor,
+ MetaGestureTracker *tracker)
+{
+ MetaGestureTrackerPrivate *priv;
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+ priv->listeners = g_list_remove (priv->listeners, gesture);
+
+ if (!priv->listeners)
+ meta_gesture_tracker_untrack_stage (tracker);
+}
+
+static void
+gesture_cancel_cb (ClutterGestureAction *gesture,
+ ClutterActor *actor,
+ MetaGestureTracker *tracker)
+{
+ MetaGestureTrackerPrivate *priv;
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+
+ if (g_list_find (priv->listeners, gesture))
+ {
+ priv->listeners = g_list_remove (priv->listeners, gesture);
+
+ if (!priv->listeners)
+ meta_gesture_tracker_set_state (tracker, META_SEQUENCE_PENDING_END);
+ }
+}
+
+static gboolean
+cancel_and_unref_gesture_cb (ClutterGestureAction *action)
+{
+ clutter_gesture_action_cancel (action);
+ g_object_unref (action);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+clear_gesture_data (GestureActionData *data)
+{
+ g_signal_handler_disconnect (data->gesture, data->gesture_begin_id);
+ g_signal_handler_disconnect (data->gesture, data->gesture_end_id);
+ g_signal_handler_disconnect (data->gesture, data->gesture_cancel_id);
+
+ /* Defer cancellation to an idle, as it may happen within event handling */
+ g_idle_add ((GSourceFunc) cancel_and_unref_gesture_cb, data->gesture);
+}
+
+static void
+meta_gesture_tracker_init (MetaGestureTracker *tracker)
+{
+ MetaGestureTrackerPrivate *priv;
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+ priv->sequences = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) meta_sequence_info_free);
+ priv->stage_gestures = g_array_new (FALSE, FALSE, sizeof (GestureActionData));
+ g_array_set_clear_func (priv->stage_gestures, (GDestroyNotify) clear_gesture_data);
+}
+
+MetaGestureTracker *
+meta_gesture_tracker_new (guint autodeny_timeout)
+{
+ return g_object_new (META_TYPE_GESTURE_TRACKER,
+ "autodeny-timeout", autodeny_timeout,
+ NULL);
+}
+
+static void
+meta_gesture_tracker_track_stage (MetaGestureTracker *tracker,
+ ClutterActor *stage)
+{
+ MetaGestureTrackerPrivate *priv;
+ GList *actions, *l;
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+ actions = clutter_actor_get_actions (stage);
+
+ for (l = actions; l; l = l->next)
+ {
+ GestureActionData data;
+
+ if (!CLUTTER_IS_GESTURE_ACTION (l->data))
+ continue;
+
+ data.gesture = g_object_ref (l->data);
+ data.state = META_SEQUENCE_NONE;
+ data.gesture_begin_id =
+ g_signal_connect (data.gesture, "gesture-begin",
+ G_CALLBACK (gesture_begin_cb), tracker);
+ data.gesture_end_id =
+ g_signal_connect (data.gesture, "gesture-end",
+ G_CALLBACK (gesture_end_cb), tracker);
+ data.gesture_cancel_id =
+ g_signal_connect (data.gesture, "gesture-cancel",
+ G_CALLBACK (gesture_cancel_cb), tracker);
+ g_array_append_val (priv->stage_gestures, data);
+ }
+
+ g_list_free (actions);
+}
+
+static void
+meta_gesture_tracker_untrack_stage (MetaGestureTracker *tracker)
+{
+ MetaGestureTrackerPrivate *priv;
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+ priv->stage_state = META_SEQUENCE_NONE;
+
+ g_hash_table_remove_all (priv->sequences);
+
+ if (priv->stage_gestures->len > 0)
+ g_array_remove_range (priv->stage_gestures, 0, priv->stage_gestures->len);
+
+ g_list_free (priv->listeners);
+ priv->listeners = NULL;
+}
+
+gboolean
+meta_gesture_tracker_handle_event (MetaGestureTracker *tracker,
+ const ClutterEvent *event)
+{
+ MetaGestureTrackerPrivate *priv;
+ ClutterEventSequence *sequence;
+ MetaSequenceInfo *info;
+ ClutterActor *stage;
+
+ sequence = clutter_event_get_event_sequence (event);
+
+ if (!sequence)
+ return FALSE;
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+ stage = CLUTTER_ACTOR (clutter_event_get_stage (event));
+
+ switch (event->type)
+ {
+ case CLUTTER_TOUCH_BEGIN:
+ if (g_hash_table_size (priv->sequences) == 0)
+ meta_gesture_tracker_track_stage (tracker, stage);
+
+ info = meta_sequence_info_new (tracker, event);
+ g_hash_table_insert (priv->sequences, sequence, info);
+
+ if (priv->stage_gestures->len == 0)
+ {
+ /* If no gestures are attached, reject the sequence right away */
+ meta_gesture_tracker_set_sequence_state (tracker, sequence,
+ META_SEQUENCE_REJECTED);
+ }
+ else if (priv->stage_state != META_SEQUENCE_NONE)
+ {
+ /* Make the sequence state match the general state */
+ meta_gesture_tracker_set_sequence_state (tracker, sequence,
+ priv->stage_state);
+ }
+ break;
+ case CLUTTER_TOUCH_END:
+ info = g_hash_table_lookup (priv->sequences, sequence);
+
+ if (!info)
+ return FALSE;
+
+ /* If nothing was done yet about the sequence, reject it so X11
+ * clients may see it
+ */
+ if (info->state == META_SEQUENCE_NONE)
+ meta_gesture_tracker_set_sequence_state (tracker, sequence,
+ META_SEQUENCE_REJECTED);
+
+ g_hash_table_remove (priv->sequences, sequence);
+
+ if (g_hash_table_size (priv->sequences) == 0)
+ meta_gesture_tracker_untrack_stage (tracker);
+ break;
+ case CLUTTER_TOUCH_UPDATE:
+ break;
+ default:
+ return FALSE;
+ break;
+ }
+
+ return TRUE;
+}
+
+gboolean
+meta_gesture_tracker_set_sequence_state (MetaGestureTracker *tracker,
+ ClutterEventSequence *sequence,
+ MetaSequenceState state)
+{
+ MetaGestureTrackerPrivate *priv;
+ MetaSequenceInfo *info;
+
+ g_return_val_if_fail (META_IS_GESTURE_TRACKER (tracker), FALSE);
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+ info = g_hash_table_lookup (priv->sequences, sequence);
+
+ if (!info)
+ return FALSE;
+ else if (state == info->state)
+ return TRUE;
+
+ if (!state_is_applicable (info->state, state))
+ return FALSE;
+
+ /* Unset autodeny timeout */
+ if (info->autodeny_timeout_id)
+ {
+ g_source_remove (info->autodeny_timeout_id);
+ info->autodeny_timeout_id = 0;
+ }
+
+ info->state = state;
+ g_signal_emit (tracker, signals[STATE_CHANGED], 0, sequence, info->state);
+
+ /* If the sequence was denied, set immediately to PENDING_END after emission */
+ if (state == META_SEQUENCE_REJECTED)
+ {
+ info->state = META_SEQUENCE_PENDING_END;
+ g_signal_emit (tracker, signals[STATE_CHANGED], 0, sequence, info->state);
+ }
+
+ return TRUE;
+}
+
+MetaSequenceState
+meta_gesture_tracker_get_sequence_state (MetaGestureTracker *tracker,
+ ClutterEventSequence *sequence)
+{
+ MetaGestureTrackerPrivate *priv;
+ MetaSequenceInfo *info;
+
+ g_return_val_if_fail (META_IS_GESTURE_TRACKER (tracker), META_SEQUENCE_PENDING_END);
+
+ priv = meta_gesture_tracker_get_instance_private (tracker);
+ info = g_hash_table_lookup (priv->sequences, sequence);
+
+ if (!info)
+ return META_SEQUENCE_PENDING_END;
+
+ return info->state;
+}
+
+gboolean
+meta_gesture_tracker_consumes_event (MetaGestureTracker *tracker,
+ const ClutterEvent *event)
+{
+ ClutterEventSequence *sequence;
+ MetaSequenceState state;
+
+ g_return_val_if_fail (META_IS_GESTURE_TRACKER (tracker), FALSE);
+
+ sequence = clutter_event_get_event_sequence (event);
+
+ if (!sequence)
+ return FALSE;
+
+ state = meta_gesture_tracker_get_sequence_state (tracker, sequence);
+
+ return (event->type != CLUTTER_TOUCH_END &&
+ (state == META_SEQUENCE_REJECTED || state == META_SEQUENCE_PENDING_END));
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]