Compare commits

...

16 Commits

Author SHA1 Message Date
3ed80495e0 compositor: emit grab-op-begin/end signals on plugin grabs
This makes it possible to track there grabs triggered this way, in
addition to the ones handled by mutter.
2014-06-25 16:38:29 +02:00
f13c86d651 gesture-tracker: Implement threshold-based sequence rejection
If a sequence moves past a certain distance without being used by a
gesture, reject it so clients may see and react to it ASAP. This makes
gestures to be began by initially quasi-static touchpoints, in addition to
quasi-simultaneous.
2014-06-25 15:17:13 +02:00
be2ca66735 wayland: Export meta_wayland_touch_cancel()
This will be necessary in order to hook the gesture tracker
to clients.
2014-06-25 15:17:13 +02:00
2df807549e frames: Keep information about the ongoing grab operation, and retry if needed.
When a passive touch grab is rejected over the frame, management is punted to
the frame itself, and pointer events emulated, but the attempt to transfer the
grab from the GDK connection to the Clutter one fails with AlreadyGrabbed, and
will fail until the Clutter connection receives the XI_TouchEnd resulting from
XIRejectTouch, gotten after the XI_ButtonPress on the GDK connection.

In order to bypass this shortcoming, store the current grab operation on the
frame as long as the button is pressed, so it is retried once on the next
motion event happening during frame dragging, that will have a recent enough
timestamp to succeed. If no grabbing succeeded, the current grab operation
data will be reset on GDK_BUTTON_RELEASE.
2014-06-25 15:17:13 +02:00
66cdb1bb71 backend: Use the most recent event time on replayed touch events
When a touch sequence is passively grabbed and later rejected, events
will be replayed on the next client in propagation order, although those
events (either transformed to pointer events or not) will contain the
original timestamps, this will make grabs fail with InvalidTime if triggered
from the replayed ButtonPress/TouchBegin handler.

In order to work around this, store the most recent event time (presumably
gotten from the XI_TouchEnd caused by the passive grab being rejected), and
use that time on the events being replayed afterwards and grabs, so we don't
possibly fail with InvalidTime if those events result in a compositor grab.
2014-06-25 15:17:13 +02:00
8030a2972e display: Set an X11 passive touch grab on the root window
Touch events will be caught first by the compositor this way,
whenever the MetaGestureTracker notifies of the accepted/rejected
state of a sequence, XIAllowTouchEvents() will be called on it
accordingly, so it is handled exclusively by the compositor or
punted to clients.
2014-06-25 15:17:13 +02:00
ba086dea8c events: Hook MetaGestureTracker to display event processing
Events aren't actually consumed by the MetaGestureTracker, but it
rather defines whether the event will reach clients, or the stage.
2014-06-25 15:17:13 +02:00
6a02d7dfa3 backend: Ensure touch event coordinates are translated to the stage's
Now that those events are going to be received, coordinates translation
should also happen on those.
2014-06-25 15:17:13 +02:00
c5db56da5c display: Add a getter for the gesture tracker
The MetaDisplay will contain a global MetaGestureTracker,
shared through this getter.
2014-06-25 15:17:13 +02:00
b5c605df5e 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.
2014-06-25 15:17:13 +02:00
4ad2865cce events: Update current time on CLUTTER_TOUCH_BEGIN events
Just as with key/button press events, update the current interaction
time when a touch begins on a window.
2014-06-25 15:17:13 +02:00
b9687d1a72 ui: Remove unneeded code
There's no way now that GDK will hold a grab, so it is safe to remove
this now.
2014-06-25 15:17:13 +02:00
66d18fcc55 x11/events: Filter out all pointer/touch GDK events
Mutter does its own input event processing, including for the places
where interaction is wanted with the remaining GTK UI elements
(frames), so GDK is largely disposable.

Even though the GDK display connection remains, and events will
be delivered over there due to event selections on that display,
all pointer and touch events will be handled and filtered out by
the GDK filter function.

The dedicated event processing meant for frames in src/ui/ui.c
now hooks to the display connection through
meta_display_events_x11_add_func(), added to let all event filters
run and stop processing at a single point.
2014-06-25 15:17:13 +02:00
321cd5d85f ui: Remove XI_TouchBegin handling from window frame event handlers
This is now unnecessary as only pointer events are selected, so pointer
emulation will take care of sending only pointer events.
2014-06-25 15:17:13 +02:00
ae91de5d03 frame: Only select for ButtonPress/Release/Motion/Crossing
Touch events are largely ignored on GdkEvent emulation, so only
make frames receive pointer events, only the pointer emulating
touch will be reported, and any other further touches will be
ignored, which is about the behavior we want. This makes window
dragging possible again on touch.
2014-06-25 15:17:13 +02:00
7247b8d81b backend-x11: Remove pointless goto
The function is not as complex as needing that, plus it always jumped
anytime bypass_clutter is TRUE, so make all conditional code depend on
that.
2014-06-25 15:17:12 +02:00
16 changed files with 977 additions and 78 deletions

View File

@ -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 \

View File

@ -51,6 +51,7 @@ struct _MetaBackendX11Private
int xinput_opcode;
int xinput_event_base;
int xinput_error_base;
Time latest_evtime;
};
typedef struct _MetaBackendX11Private MetaBackendX11Private;
@ -71,6 +72,7 @@ static void
translate_device_event (MetaBackendX11 *x11,
XIDeviceEvent *device_event)
{
MetaBackendX11Private *priv = meta_backend_x11_get_instance_private (x11);
Window stage_window = meta_backend_x11_get_xwindow (x11);
if (device_event->event != stage_window)
@ -88,6 +90,21 @@ translate_device_event (MetaBackendX11 *x11,
device_event->event_x = device_event->root_x;
device_event->event_y = device_event->root_y;
}
if (!device_event->send_event && device_event->time != CurrentTime)
{
if (device_event->time < priv->latest_evtime)
{
/* Emulated pointer events received after XIRejectTouch is received
* on a passive touch grab will contain older timestamps, update those
* so we dont get InvalidTime at grabs.
*/
device_event->time = priv->latest_evtime;
}
/* Update the internal latest evtime, for any possible later use */
priv->latest_evtime = device_event->time;
}
}
/* Clutter makes the assumption that there is only one X window
@ -117,6 +134,9 @@ maybe_spoof_event_as_stage_event (MetaBackendX11 *x11,
case XI_ButtonRelease:
case XI_KeyPress:
case XI_KeyRelease:
case XI_TouchBegin:
case XI_TouchUpdate:
case XI_TouchEnd:
translate_device_event (x11, (XIDeviceEvent *) input_event);
break;
default:
@ -142,17 +162,14 @@ handle_host_xevent (MetaBackend *backend,
MetaMonitorManager *manager = meta_backend_get_monitor_manager (backend);
if (META_IS_MONITOR_MANAGER_XRANDR (manager) &&
meta_monitor_manager_xrandr_handle_xevent (META_MONITOR_MANAGER_XRANDR (manager), event))
{
bypass_clutter = TRUE;
goto out;
}
bypass_clutter = TRUE;
}
maybe_spoof_event_as_stage_event (x11, event);
out:
if (!bypass_clutter)
clutter_x11_handle_event (event);
{
maybe_spoof_event_as_stage_event (x11, event);
clutter_x11_handle_event (event);
}
XFreeEventData (priv->xdisplay, &event->xcookie);
}
@ -313,6 +330,9 @@ meta_backend_x11_grab_device (MetaBackend *backend,
XIEventMask mask = { XIAllMasterDevices, sizeof (mask_bits), mask_bits };
int ret;
if (timestamp != CurrentTime)
timestamp = MAX (timestamp, priv->latest_evtime);
XISetMask (mask.mask, XI_ButtonPress);
XISetMask (mask.mask, XI_ButtonRelease);
XISetMask (mask.mask, XI_Enter);

View File

@ -385,6 +385,10 @@ meta_begin_modal_for_plugin (MetaCompositor *compositor,
display->grab_have_pointer = TRUE;
display->grab_have_keyboard = TRUE;
g_signal_emit_by_name (display, "grab-op-begin",
meta_plugin_get_screen (plugin),
display->grab_window, display->grab_op);
if (meta_is_wayland_compositor ())
meta_display_sync_wayland_input_focus (display);
@ -401,6 +405,10 @@ meta_end_modal_for_plugin (MetaCompositor *compositor,
g_return_if_fail (is_modal (display));
g_signal_emit_by_name (display, "grab-op-end",
meta_plugin_get_screen (plugin),
display->grab_window, display->grab_op);
display->grab_op = META_GRAB_OP_NONE;
display->grab_window = NULL;
display->grab_have_pointer = FALSE;

View File

@ -35,6 +35,7 @@
#include <meta/boxes.h>
#include <meta/display.h>
#include "keybindings-private.h"
#include "gesture-tracker-private.h"
#include <meta/prefs.h>
#include <meta/barrier.h>
#include <clutter/clutter.h>
@ -253,6 +254,8 @@ struct _MetaDisplay
/* Managed by compositor.c */
MetaCompositor *compositor;
MetaGestureTracker *gesture_tracker;
int composite_event_base;
int composite_error_base;
int composite_major_version;
@ -442,4 +445,6 @@ void meta_display_sanity_check_timestamps (MetaDisplay *display,
gboolean meta_display_timestamp_too_old (MetaDisplay *display,
guint32 *timestamp);
MetaGestureTracker * meta_display_get_gesture_tracker (MetaDisplay *display);
#endif

View File

@ -51,6 +51,8 @@
#include "meta-cursor-tracker-private.h"
#include "meta-backend.h"
#include "backends/x11/meta-backend-x11.h"
#include <clutter/x11/clutter-x11.h>
#include "compositor-private.h"
#ifdef HAVE_RANDR
#include <X11/extensions/Xrandr.h>
@ -146,6 +148,11 @@ static void update_window_grab_modifiers (MetaDisplay *display);
static void prefs_changed_callback (MetaPreference pref,
void *data);
static void meta_display_grab_window_touch (MetaDisplay *display,
Window xwindow);
static void meta_display_ungrab_window_touch (MetaDisplay *display,
Window xwindow);
static void
meta_display_get_property(GObject *object,
guint prop_id,
@ -413,6 +420,28 @@ meta_set_gnome_wm_keybindings (const char *wm_keybindings)
gnome_wm_keybindings = wm_keybindings;
}
static void
gesture_tracker_state_changed (MetaGestureTracker *tracker,
ClutterEventSequence *sequence,
MetaSequenceState state,
MetaDisplay *display)
{
MetaBackendX11 *backend = META_BACKEND_X11 (meta_get_backend ());
int event_mode;
if (state == META_SEQUENCE_ACCEPTED)
event_mode = XIAcceptTouch;
else if (state == META_SEQUENCE_REJECTED)
event_mode = XIRejectTouch;
else
return;
XIAllowTouchEvents (meta_backend_x11_get_xdisplay (backend),
META_VIRTUAL_CORE_POINTER_ID,
clutter_x11_event_sequence_get_touch_detail (sequence),
DefaultRootWindow (display->xdisplay), event_mode);
}
/**
* meta_display_open:
*
@ -808,6 +837,13 @@ meta_display_open (void)
meta_screen_init_workspaces (screen);
meta_screen_create_guard_window (screen);
/* Set up touch support */
the_display->gesture_tracker = meta_gesture_tracker_new (0);
g_signal_connect (the_display->gesture_tracker, "state-changed",
G_CALLBACK (gesture_tracker_state_changed), the_display);
meta_display_grab_window_touch (the_display,
DefaultRootWindow (the_display->xdisplay));
/* We know that if mutter is running as a Wayland compositor,
* we start out with no windows.
*/
@ -977,6 +1013,10 @@ meta_display_close (MetaDisplay *display,
meta_display_remove_autoraise_callback (display);
meta_display_ungrab_window_touch (display,
DefaultRootWindow (display->xdisplay));
g_clear_object (&display->gesture_tracker);
if (display->focus_timeout_id)
g_source_remove (display->focus_timeout_id);
display->focus_timeout_id = 0;
@ -2055,6 +2095,35 @@ meta_display_ungrab_window_buttons (MetaDisplay *display,
}
}
static void
meta_display_grab_window_touch (MetaDisplay *display,
Window xwindow)
{
MetaBackendX11 *backend = META_BACKEND_X11 (meta_get_backend ());
unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 };
XIEventMask mask = { META_VIRTUAL_CORE_POINTER_ID, sizeof (mask_bits), mask_bits };
XIGrabModifiers mods = { XIAnyModifier, 0 };
XISetMask (mask.mask, XI_TouchBegin);
XISetMask (mask.mask, XI_TouchUpdate);
XISetMask (mask.mask, XI_TouchEnd);
XIGrabTouchBegin (meta_backend_x11_get_xdisplay (backend),
META_VIRTUAL_CORE_POINTER_ID,
xwindow, False, &mask, 1, &mods);
}
static void
meta_display_ungrab_window_touch (MetaDisplay *display,
Window xwindow)
{
MetaBackendX11 *backend = META_BACKEND_X11 (meta_get_backend ());
XIGrabModifiers mods = { XIAnyModifier, 0 };
XIUngrabTouchBegin (meta_backend_x11_get_xdisplay (backend),
META_VIRTUAL_CORE_POINTER_ID, xwindow, 1, &mods);
}
/* Grab buttons we only grab while unfocused in click-to-focus mode */
#define MAX_FOCUS_BUTTON 4
void
@ -3152,3 +3221,9 @@ meta_display_create_x_cursor (MetaDisplay *display,
{
return meta_cursor_create_x_cursor (display->xdisplay, cursor);
}
MetaGestureTracker *
meta_display_get_gesture_tracker (MetaDisplay *display)
{
return display->gesture_tracker;
}

View File

@ -23,6 +23,7 @@
#include "config.h"
#include "events.h"
#include "core.h"
#include "display-private.h"
#include "window-private.h"
#include "backends/meta-backend.h"
@ -106,6 +107,7 @@ meta_display_handle_event (MetaDisplay *display,
MetaWindow *window;
gboolean bypass_clutter = FALSE, bypass_wayland = FALSE;
MetaWaylandCompositor *compositor = NULL;
MetaGestureTracker *tracker;
if (meta_is_wayland_compositor ())
{
@ -120,7 +122,9 @@ meta_display_handle_event (MetaDisplay *display,
display->current_time = event->any.time;
if (window && !window->override_redirect &&
(event->type == CLUTTER_KEY_PRESS || event->type == CLUTTER_BUTTON_PRESS))
(event->type == CLUTTER_KEY_PRESS ||
event->type == CLUTTER_BUTTON_PRESS ||
event->type == CLUTTER_TOUCH_BEGIN))
{
if (CurrentTime == display->current_time)
{
@ -139,6 +143,15 @@ meta_display_handle_event (MetaDisplay *display,
}
}
tracker = meta_display_get_gesture_tracker (display);
if (meta_gesture_tracker_handle_event (tracker, event))
{
bypass_wayland = TRUE;
bypass_clutter = meta_gesture_tracker_consumes_event (tracker, event);
goto out;
}
if (display->grab_window == window &&
meta_grab_op_is_moving_or_resizing (display->grab_op))
{

View File

@ -109,6 +109,19 @@ meta_window_ensure_frame (MetaWindow *window)
XChangeWindowAttributes (window->display->xdisplay,
frame->xwindow, CWEventMask, &attrs);
{
unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 };
XIEventMask mask = { XIAllMasterDevices, sizeof (mask_bits), mask_bits };
XISetMask (mask.mask, XI_ButtonPress);
XISetMask (mask.mask, XI_ButtonRelease);
XISetMask (mask.mask, XI_Motion);
XISetMask (mask.mask, XI_Enter);
XISetMask (mask.mask, XI_Leave);
XISelectEvents (window->display->xdisplay, frame->xwindow, &mask, 1);
}
meta_display_register_x_window (window->display, &frame->xwindow, window);
meta_error_trap_push (window->display);

View File

@ -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 */

555
src/core/gesture-tracker.c Normal file
View File

@ -0,0 +1,555 @@
/*
* 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"
#define DISTANCE_THRESHOLD 30
typedef struct _MetaGestureTrackerPrivate MetaGestureTrackerPrivate;
typedef struct _GestureActionData GestureActionData;
typedef struct _MetaSequenceInfo MetaSequenceInfo;
struct _MetaSequenceInfo
{
MetaGestureTracker *tracker;
ClutterEventSequence *sequence;
MetaSequenceState state;
guint autodeny_timeout_id;
gfloat start_x;
gfloat start_y;
};
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);
clutter_event_get_coords (event, &info->start_x, &info->start_y);
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;
gfloat x, y;
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:
info = g_hash_table_lookup (priv->sequences, sequence);
if (!info)
return FALSE;
clutter_event_get_coords (event, &x, &y);
if (info->state == META_SEQUENCE_NONE &&
(ABS (info->start_x - x) > DISTANCE_THRESHOLD ||
ABS (info->start_y - y) > DISTANCE_THRESHOLD))
meta_gesture_tracker_set_sequence_state (tracker, sequence,
META_SEQUENCE_REJECTED);
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));
}

View File

@ -1136,6 +1136,64 @@ meta_frame_right_click_event(MetaUIFrame *frame,
return meta_frame_titlebar_event (frame, event, action);
}
static gboolean
meta_frames_try_grab_op (MetaFrames *frames,
MetaUIFrame *frame,
MetaGrabOp op,
gdouble grab_x,
gdouble grab_y,
guint32 time)
{
Display *display;
gboolean ret;
display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
ret = meta_core_begin_grab_op (display,
frame->xwindow,
op,
FALSE,
TRUE,
frame->grab_button,
0,
time,
grab_x, grab_y);
if (!ret)
{
frames->current_grab_op = op;
frames->grab_frame = frame;
frames->grab_x = grab_x;
frames->grab_y = grab_y;
}
return ret;
}
static gboolean
meta_frames_retry_grab_op (MetaFrames *frames,
guint time)
{
Display *display;
MetaGrabOp op;
if (frames->current_grab_op == META_GRAB_OP_NONE)
return TRUE;
op = frames->current_grab_op;
frames->current_grab_op = META_GRAB_OP_NONE;
display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
return meta_core_begin_grab_op (display,
frames->grab_frame->xwindow,
op,
FALSE,
TRUE,
frames->grab_frame->grab_button,
0,
time,
frames->grab_x,
frames->grab_y);
}
static gboolean
meta_frames_button_press_event (GtkWidget *widget,
GdkEventButton *event)
@ -1190,6 +1248,8 @@ meta_frames_button_press_event (GtkWidget *widget,
if (meta_core_get_grab_op (display) != META_GRAB_OP_NONE)
return FALSE; /* already up to something */
frame->grab_button = event->button;
if (event->button == 1 &&
(control == META_FRAME_CONTROL_MAXIMIZE ||
control == META_FRAME_CONTROL_UNMAXIMIZE ||
@ -1293,16 +1353,9 @@ meta_frames_button_press_event (GtkWidget *widget,
break;
}
meta_core_begin_grab_op (display,
frame->xwindow,
op,
TRUE,
TRUE,
event->button,
0,
event->time,
event->x_root,
event->y_root);
meta_frames_try_grab_op (frames, frame, op,
event->x_root, event->y_root,
event->time);
}
else if (control == META_FRAME_CONTROL_TITLE &&
event->button == 1)
@ -1315,16 +1368,10 @@ meta_frames_button_press_event (GtkWidget *widget,
if (flags & META_FRAME_ALLOWS_MOVE)
{
meta_core_begin_grab_op (display,
frame->xwindow,
meta_frames_try_grab_op (frames, frame,
META_GRAB_OP_MOVING,
TRUE,
TRUE,
event->button,
0,
event->time,
event->x_root,
event->y_root);
event->x_root, event->y_root,
event->time);
}
}
else if (event->button == 2)
@ -1349,6 +1396,7 @@ meta_frames_button_release_event (GtkWidget *widget,
frames = META_FRAMES (widget);
display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
frames->current_grab_op = META_GRAB_OP_NONE;
frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window));
if (frame == NULL)
@ -1560,6 +1608,10 @@ meta_frames_motion_notify_event (GtkWidget *widget,
meta_frames_update_prelit_control (frames, frame, control);
}
if ((event->state & GDK_BUTTON1_MASK) &&
frames->current_grab_op != META_GRAB_OP_NONE)
meta_frames_retry_grab_op (frames, event->time);
return TRUE;
}

View File

@ -100,6 +100,12 @@ struct _MetaFrames
GtkStyleContext *normal_style;
GHashTable *style_variants;
MetaGrabOp current_grab_op;
MetaUIFrame *grab_frame;
guint grab_button;
gdouble grab_x;
gdouble grab_y;
Window grab_xwindow;
};

View File

@ -26,6 +26,7 @@
#include <meta/util.h>
#include "core.h"
#include "theme-private.h"
#include "x11/events.h"
#include <string.h>
#include <stdlib.h>
@ -119,7 +120,6 @@ maybe_redirect_mouse_event (XEvent *xevent)
switch (xev->evtype)
{
case XI_TouchBegin:
case XI_ButtonPress:
case XI_ButtonRelease:
case XI_Motion:
@ -147,19 +147,11 @@ maybe_redirect_mouse_event (XEvent *xevent)
gmanager = gdk_display_get_device_manager (gdisplay);
gdevice = gdk_x11_device_manager_lookup (gmanager, META_VIRTUAL_CORE_POINTER_ID);
/* If GDK already thinks it has a grab, we better let it see events; this
* is the menu-navigation case and events need to get sent to the appropriate
* (client-side) subwindow for individual menu items.
*/
if (gdk_display_device_is_grabbed (gdisplay, gdevice))
return FALSE;
switch (xev->evtype)
{
case XI_TouchBegin:
case XI_ButtonPress:
case XI_ButtonRelease:
if (xev_d->evtype == XI_ButtonPress || xev_d->evtype == XI_TouchBegin)
if (xev_d->evtype == XI_ButtonPress)
{
GtkSettings *settings = gtk_settings_get_default ();
int double_click_time;
@ -171,10 +163,7 @@ maybe_redirect_mouse_event (XEvent *xevent)
"gtk-double-click-distance", &double_click_distance,
NULL);
if (xev->evtype == XI_TouchBegin)
button = 1;
else
button = xev_d->detail;
button = xev_d->detail;
if (button == ui->button_click_number &&
xev_d->event == ui->button_click_window &&
@ -216,6 +205,12 @@ maybe_redirect_mouse_event (XEvent *xevent)
gevent = gdk_event_new (GDK_MOTION_NOTIFY);
gevent->motion.type = GDK_MOTION_NOTIFY;
gevent->motion.window = g_object_ref (gdk_window);
gevent->motion.time = xev_d->time;
gevent->motion.x_root = xev_d->root_x;
gevent->motion.y_root = xev_d->root_y;
if (XIMaskIsSet (xev_d->buttons.mask, 1))
gevent->motion.state |= GDK_BUTTON1_MASK;
break;
case XI_Enter:
case XI_Leave:
@ -237,15 +232,11 @@ maybe_redirect_mouse_event (XEvent *xevent)
return TRUE;
}
static GdkFilterReturn
ui_filter_func (GdkXEvent *xevent,
GdkEvent *event,
static void
ui_filter_func (gpointer xevent,
gpointer data)
{
if (maybe_redirect_mouse_event (xevent))
return GDK_FILTER_REMOVE;
else
return GDK_FILTER_CONTINUE;
maybe_redirect_mouse_event (xevent);
}
MetaUI*
@ -271,7 +262,7 @@ meta_ui_new (Display *xdisplay,
*/
gtk_widget_show (GTK_WIDGET (ui->frames));
gdk_window_add_filter (NULL, ui_filter_func, NULL);
meta_display_events_x11_add_func (ui_filter_func, NULL);
g_object_set_data (G_OBJECT (gdisplay), "meta-ui", ui);
@ -288,7 +279,7 @@ meta_ui_free (MetaUI *ui)
gdisplay = gdk_x11_lookup_xdisplay (ui->xdisplay);
g_object_set_data (G_OBJECT (gdisplay), "meta-ui", NULL);
gdk_window_remove_filter (NULL, ui_filter_func, NULL);
meta_display_events_x11_add_func (ui_filter_func, NULL);
g_free (ui);
}

View File

@ -444,29 +444,6 @@ touch_info_free (MetaWaylandTouchInfo *touch_info)
g_free (touch_info);
}
static void
touch_handle_cancel_event (MetaWaylandTouch *touch,
struct libinput_event *event)
{
GList *surfaces, *s;
surfaces = s = touch_get_surfaces (touch, FALSE);
while (s)
{
MetaWaylandTouchSurface *touch_surface = s->data;
struct wl_resource *resource;
struct wl_list *l;
l = &touch_surface->resource_list;
wl_resource_for_each(resource, l)
wl_touch_send_cancel (resource);
}
g_hash_table_remove_all (touch->touches);
g_list_free (surfaces);
}
static gboolean
evdev_filter_func (struct libinput_event *event,
gpointer data)
@ -495,7 +472,7 @@ evdev_filter_func (struct libinput_event *event,
* which are not so useful when sending a global signal as the protocol
* requires.
*/
touch_handle_cancel_event (touch, event);
meta_wayland_touch_cancel (touch);
break;
default:
break;
@ -546,3 +523,25 @@ meta_wayland_touch_create_new_resource (MetaWaylandTouch *touch,
wl_resource_set_implementation (cr, NULL, touch, unbind_resource);
wl_list_insert (&touch->resource_list, wl_resource_get_link (cr));
}
void
meta_wayland_touch_cancel (MetaWaylandTouch *touch)
{
GList *surfaces, *s;
surfaces = s = touch_get_surfaces (touch, FALSE);
while (s)
{
MetaWaylandTouchSurface *touch_surface = s->data;
struct wl_resource *resource;
struct wl_list *l;
l = &touch_surface->resource_list;
wl_resource_for_each(resource, l)
wl_touch_send_cancel (resource);
}
g_hash_table_remove_all (touch->touches);
g_list_free (surfaces);
}

View File

@ -58,5 +58,7 @@ void meta_wayland_touch_create_new_resource (MetaWaylandTouch *touch,
struct wl_client *client,
struct wl_resource *seat_resource,
uint32_t id);
void meta_wayland_touch_cancel (MetaWaylandTouch *touch);
#endif /* META_WAYLAND_TOUCH_H */

View File

@ -38,6 +38,16 @@
#include "wayland/meta-xwayland.h"
#include "wayland/meta-wayland-private.h"
typedef struct _EventFuncData EventFuncData;
struct _EventFuncData
{
GFunc func;
gpointer data;
};
static GList *event_funcs = NULL;
static XIEvent *
get_input_event (MetaDisplay *display,
XEvent *event)
@ -1801,11 +1811,37 @@ xevent_filter (GdkXEvent *xevent,
gpointer data)
{
MetaDisplay *display = data;
EventFuncData *event_data;
XIEvent *input_event;
GList *l;
if (meta_display_handle_xevent (display, xevent))
return GDK_FILTER_REMOVE;
else
meta_display_handle_xevent (display, xevent);
for (l = event_funcs; l; l = l->next)
{
event_data = l->data;
event_data->func (xevent, event_data->data);
}
input_event = get_input_event (display, xevent);
if (!input_event)
return GDK_FILTER_CONTINUE;
/* Filter all pointer and touch events, those are emulated
* above by the filters, and Gdk is bypassed there on purpose.
*/
if (input_event->evtype == XI_ButtonPress ||
input_event->evtype == XI_ButtonRelease ||
input_event->evtype == XI_Motion ||
input_event->evtype == XI_Enter ||
input_event->evtype == XI_Leave ||
input_event->evtype == XI_TouchBegin ||
input_event->evtype == XI_TouchUpdate ||
input_event->evtype == XI_TouchEnd)
return GDK_FILTER_REMOVE;
return GDK_FILTER_CONTINUE;
}
void
@ -1819,3 +1855,37 @@ meta_display_free_events_x11 (MetaDisplay *display)
{
gdk_window_remove_filter (NULL, xevent_filter, display);
}
void
meta_display_events_x11_add_func (GFunc func,
gpointer user_data)
{
EventFuncData *data;
data = g_slice_new0 (EventFuncData);
data->func = func;
data->data = user_data;
event_funcs = g_list_prepend (event_funcs, data);
}
void
meta_display_events_x11_remove_func (GFunc func,
gpointer user_data)
{
EventFuncData *data;
GList *l;
data = g_slice_new0 (EventFuncData);
for (l = event_funcs; l; l = l->next)
{
data = l->data;
if (data->func != func || data->data != user_data)
continue;
event_funcs = g_list_delete_link (event_funcs, l);
g_slice_free (EventFuncData, data);
break;
}
}

View File

@ -28,4 +28,9 @@
void meta_display_init_events_x11 (MetaDisplay *display);
void meta_display_free_events_x11 (MetaDisplay *display);
void meta_display_events_x11_add_func (GFunc func,
gpointer user_data);
void meta_display_events_x11_remove_func (GFunc func,
gpointer user_data);
#endif