diff --git a/12to11.c b/12to11.c
index 205ad02..08e840e 100644
--- a/12to11.c
+++ b/12to11.c
@@ -244,6 +244,7 @@ XLMain (int argc, char **argv)
XLInitRelativePointer ();
XLInitKeyboardShortcutsInhibit ();
XLInitIdleInhibit ();
+ XLInitPointerGestures ();
/* This has to come after the rest of the initialization. */
DetermineServerTime ();
diff --git a/12to11.man b/12to11.man
index 2a4fb56..0d754f3 100644
--- a/12to11.man
+++ b/12to11.man
@@ -293,6 +293,15 @@ Protocol Version
wp_drm_lease_device_v1 1
.TE
.PP
+When the X server supports version 2.4 or later of the X Input
+Extension, the following Wayland protocol is also supported:
+.TS H
+lb lb
+lb n .
+Protocol Version
+zwp_pointer_gestures_v1 3
+.TE
+.PP
However, Wayland clients are allowed to continue to access data from
the \fBCLIPBOARD\fP and \fBPRIMARY\fP selections even when they do not
have the keyboard focus, against the restrictions put out in the
diff --git a/Imakefile b/Imakefile
index a6db4f9..798ebe6 100644
--- a/Imakefile
+++ b/Imakefile
@@ -25,7 +25,7 @@ SRCS = 12to11.c run.c alloc.c fns.c output.c compositor.c \
wp_viewporter.c decoration.c text_input.c \
single_pixel_buffer.c drm_lease.c pointer_constraints.c \
time.c relative_pointer.c keyboard_shortcuts_inhibit.c \
- idle_inhibit.c process.c fence_ring.c
+ idle_inhibit.c process.c fence_ring.c pointer_gestures.c
OBJS = 12to11.o run.o alloc.o fns.o output.o compositor.o \
surface.o region.o shm.o atoms.o subcompositor.o positioner.o \
@@ -37,7 +37,7 @@ OBJS = 12to11.o run.o alloc.o fns.o output.o compositor.o \
wp_viewporter.o decoration.o text_input.o \
single_pixel_buffer.o drm_lease.o pointer_constraints.o \
time.o relative_pointer.o keyboard_shortcuts_inhibit.o \
- idle_inhibit.o process.o fence_ring.o
+ idle_inhibit.o process.o fence_ring.o pointer_gestures.o
GENHEADERS = transfer_atoms.h drm_modifiers.h
@@ -128,6 +128,7 @@ ScannerTarget(pointer-constraints-unstable-v1)
ScannerTarget(relative-pointer-unstable-v1)
ScannerTarget(keyboard-shortcuts-inhibit-unstable-v1)
ScannerTarget(idle-inhibit-unstable-v1)
+ScannerTarget(pointer-gestures-unstable-v1)
/* Make OBJS depend on scanner headers, and depend on both them and SRCS. */
$(OBJS): $(GENHEADERS)
diff --git a/README b/README
index a67797f..7ba6d9f 100644
--- a/README
+++ b/README
@@ -79,6 +79,11 @@ supported:
'wp_drm_lease_device_v1', version: 1
+When the X server supports version 2.4 or later of the X Input
+Extension, the following Wayland protocol is also supported:
+
+ 'zwp_pointer_gestures_v1', version: 3
+
With the main caveat being that zwp_linux_dmabuf_v1 has no real
support for multiple-provider setups (help wanted).
diff --git a/compositor.h b/compositor.h
index 075e1af..aed0295 100644
--- a/compositor.h
+++ b/compositor.h
@@ -94,6 +94,8 @@ struct _Compositor
typedef struct _Seat Seat;
typedef struct _Pointer Pointer;
typedef struct _RelativePointer RelativePointer;
+typedef struct _SwipeGesture SwipeGesture;
+typedef struct _PinchGesture PinchGesture;
/* Forward declarations from primary_selection.c. */
@@ -1522,6 +1524,8 @@ extern void XLInitTextInput (void);
extern int xi2_opcode;
extern int xi_first_event;
extern int xi_first_error;
+extern int xi2_major;
+extern int xi2_minor;
extern XLList *live_seats;
@@ -1570,6 +1574,10 @@ extern RelativePointer *XLSeatGetRelativePointer (Seat *, struct wl_resource *);
extern void XLSeatDestroyRelativePointer (RelativePointer *);
extern Bool XLSeatApplyExternalGrab (Seat *, Surface *);
extern void XLSeatCancelExternalGrab (Seat *);
+extern SwipeGesture *XLSeatGetSwipeGesture (Seat *, struct wl_resource *);
+extern PinchGesture *XLSeatGetPinchGesture (Seat *, struct wl_resource *);
+extern void XLSeatDestroySwipeGesture (SwipeGesture *);
+extern void XLSeatDestroyPinchGesture (PinchGesture *);
extern Cursor InitDefaultCursor (void);
@@ -1783,6 +1791,10 @@ extern void FenceAwait (Fence *);
extern void FenceRelease (Fence *);
extern XSyncFence FenceToXFence (Fence *);
+/* Defined in pointer_gestures.c. */
+
+extern void XLInitPointerGestures (void);
+
/* Utility functions that don't belong in a specific file. */
#define ArrayElements(arr) (sizeof (arr) / sizeof (arr)[0])
diff --git a/seat.c b/seat.c
index 6d05d08..2520548 100644
--- a/seat.c
+++ b/seat.c
@@ -41,6 +41,7 @@ along with 12to11. If not, see . */
#include
#include "xdg-shell.h"
+#include "pointer-gestures-unstable-v1.h"
/* X11 event opcode, event base, and error base for the input
extension. */
@@ -49,7 +50,7 @@ int xi2_opcode, xi_first_event, xi_first_error;
/* The version of the input extension in use. */
-static int xi2_major, xi2_minor;
+int xi2_major, xi2_minor;
/* The current keymap file descriptor. */
@@ -91,6 +92,8 @@ enum
IsPointerLocked = (1 << 6),
IsSurfaceCoordSet = (1 << 7),
IsExternalGrabApplied = (1 << 8),
+ IsInPinchGesture = (1 << 9),
+ IsInSwipeGesture = (1 << 10),
};
enum
@@ -323,6 +326,39 @@ struct _RelativePointer
RelativePointer *next, *last;
};
+struct _SwipeGesture
+{
+ /* The seat this swipe gesture refers to. */
+ Seat *seat;
+
+ /* The struct wl_resource associated with this swipe gesture. */
+ struct wl_resource *resource;
+
+ /* The seat client info associated with this swipe gesture
+ resource. */
+ SeatClientInfo *info;
+
+ /* The next and last swipe gestures attached to the seat client
+ info. */
+ SwipeGesture *next, *last;
+};
+
+struct _PinchGesture
+{
+ /* The seat this pinch gesture refers to. */
+ Seat *seat;
+
+ /* The struct wl_resource associated with this pinch gesture. */
+ struct wl_resource *resource;
+
+ /* The seat client info associated with this pinch gesture. */
+ SeatClientInfo *info;
+
+ /* The next and last pinch gestures attached to the seat client
+ info. */
+ PinchGesture *next, *last;
+};
+
struct _SeatClientInfo
{
/* The next and last structures in the client info chain. */
@@ -345,6 +381,12 @@ struct _SeatClientInfo
/* List of relative pointers on this seat for this client. */
RelativePointer relative_pointers;
+
+ /* List of swipe gestures on this seat for this client. */
+ SwipeGesture swipe_gestures;
+
+ /* List of pinch gestures on this seat for this client. */
+ PinchGesture pinch_gestures;
};
struct _ModifierChangeCallback
@@ -685,6 +727,10 @@ CreateSeatClientInfo (Seat *seat, struct wl_client *client)
info->keyboards.last = &info->keyboards;
info->relative_pointers.next = &info->relative_pointers;
info->relative_pointers.last = &info->relative_pointers;
+ info->swipe_gestures.next = &info->swipe_gestures;
+ info->swipe_gestures.last = &info->swipe_gestures;
+ info->pinch_gestures.next = &info->pinch_gestures;
+ info->pinch_gestures.last = &info->pinch_gestures;
}
/* Increase the reference count of info. */
@@ -3357,10 +3403,17 @@ UndefineCursorOn (Seat *seat, Surface *surface)
XUndefineCursor (compositor.display, window);
}
+/* Forward declaration. */
+
+static void SendGesturePinchEnd (Seat *, Surface *, Time, int);
+static void SendGestureSwipeEnd (Seat *, Surface *, Time, int);
+
static void
EnteredSurface (Seat *seat, Surface *surface, Time time,
double x, double y, Bool preserve_cursor)
{
+ Time gesture_time;
+
if (seat->grab_held && surface != seat->last_seen_surface)
{
/* If the seat is grabbed, delay this for later. */
@@ -3371,6 +3424,41 @@ EnteredSurface (Seat *seat, Surface *surface, Time time,
if (seat->last_seen_surface == surface)
return;
+ /* The surface currently entered changed (or will change). Cancel
+ any ongoing gestures. */
+
+ if (seat->flags & IsInPinchGesture
+ /* Not sure if this can actually be NULL here. */
+ && seat->last_seen_surface)
+ {
+ /* If time is 0 (CurrentTime), then just use the last user
+ time. */
+ gesture_time = time ? time : seat->last_user_time.milliseconds;
+
+ /* Send the gesture end event. */
+ SendGesturePinchEnd (seat, seat->last_seen_surface,
+ gesture_time, 1);
+
+ /* And clear the flag so further updates are not sent. */
+ seat->flags &= ~IsInPinchGesture;
+ }
+
+ if (seat->flags & IsInSwipeGesture
+ /* Not sure if this can actually be NULL here. */
+ && seat->last_seen_surface)
+ {
+ /* If time is 0 (CurrentTime), then just use the last user
+ time. */
+ gesture_time = time ? time : seat->last_user_time.milliseconds;
+
+ /* Send the gesture end event. */
+ SendGestureSwipeEnd (seat, seat->last_seen_surface,
+ gesture_time, 1);
+
+ /* And clear the flag so further updates are not sent. */
+ seat->flags &= ~IsInSwipeGesture;
+ }
+
if (seat->last_seen_surface)
{
if (seat->flags & IsDragging)
@@ -4296,6 +4384,343 @@ DispatchBarrierHit (XIBarrierEvent *barrier)
seat->last_user_time = TimestampFromServerTime (barrier->time);
}
+static void
+SendGesturePinchBegin (Seat *seat, Surface *dispatch, Time time,
+ int detail)
+{
+ PinchGesture *gesture;
+ uint32_t serial;
+ SeatClientInfo *info;
+
+ serial = wl_display_next_serial (compositor.wl_display);
+ info = ClientInfoForResource (seat, dispatch->resource);
+
+ if (!info)
+ return;
+
+ gesture = info->pinch_gestures.next;
+
+ for (; gesture != &info->pinch_gestures; gesture = gesture->next)
+ zwp_pointer_gesture_pinch_v1_send_begin (gesture->resource,
+ serial, time,
+ dispatch->resource,
+ detail);
+}
+
+static void
+SendGesturePinchUpdate (Seat *seat, Surface *dispatch, Time time,
+ double dx, double dy, double scale, double rotation)
+{
+ PinchGesture *gesture;
+ SeatClientInfo *info;
+
+ info = ClientInfoForResource (seat, dispatch->resource);
+
+ if (!info)
+ return;
+
+ gesture = info->pinch_gestures.next;
+
+ for (; gesture != &info->pinch_gestures; gesture = gesture->next)
+ zwp_pointer_gesture_pinch_v1_send_update (gesture->resource,
+ time,
+ wl_fixed_from_double (dx),
+ wl_fixed_from_double (dy),
+ wl_fixed_from_double (scale),
+ wl_fixed_from_double (rotation));
+}
+
+static void
+SendGesturePinchEnd (Seat *seat, Surface *dispatch, Time time, int cancelled)
+{
+ PinchGesture *gesture;
+ SeatClientInfo *info;
+ uint32_t serial;
+
+ info = ClientInfoForResource (seat, dispatch->resource);
+
+ if (!info)
+ return;
+
+ gesture = info->pinch_gestures.next;
+ serial = wl_display_next_serial (compositor.wl_display);
+
+ for (; gesture != &info->pinch_gestures; gesture = gesture->next)
+ zwp_pointer_gesture_pinch_v1_send_end (gesture->resource,
+ serial, time, cancelled);
+}
+
+static void
+DispatchGesturePinch (Subcompositor *subcompositor, XIGesturePinchEvent *pinch)
+{
+ Seat *seat;
+ Surface *dispatch, *actual_dispatch;
+ double x, y, event_x, event_y;
+
+ seat = XLLookUpAssoc (seats, pinch->deviceid);
+
+ if (!seat)
+ return;
+
+ /* Move the icon surface. */
+ if (seat->icon_surface)
+ XLMoveIconSurface (seat->icon_surface, pinch->root_x,
+ pinch->root_y);
+
+ /* Update information used for resize tracking. */
+ seat->its_root_x = pinch->root_x;
+ seat->its_root_y = pinch->root_y;
+ seat->its_press_time = pinch->time;
+
+ /* Update the last user time. */
+ seat->last_user_time = TimestampFromServerTime (pinch->time);
+
+ /* Now find the dispatch surface so we can enter it if required.
+ Most of this code is copied from DispatchMotion; it should
+ probably be moved to a separate function. */
+ actual_dispatch = FindSurfaceUnder (subcompositor, pinch->event_x,
+ pinch->event_y);
+
+ if (seat->grab_held)
+ {
+ /* If the grab is held, make the surface underneath the pointer
+ the pending unlock surface. */
+ SwapUnlockSurface (seat, actual_dispatch);
+ dispatch = seat->last_seen_surface;
+ }
+ else
+ dispatch = actual_dispatch;
+
+ event_x = pinch->event_y;
+ event_y = pinch->event_y;
+
+ if (!dispatch)
+ {
+ if (seat->grab_surface)
+ {
+ /* If the grab surface is set, translate the coordinates to
+ it and use it instead. */
+ TranslateGrabPosition (seat, pinch->event,
+ &event_x, &event_y);
+ dispatch = seat->grab_surface;
+
+ goto after_dispatch_set;
+ }
+
+ EnteredSurface (seat, dispatch, pinch->time, 0, 0, False);
+ return;
+ }
+
+ after_dispatch_set:
+ TransformToSurface (dispatch, event_x, event_y, &x, &y);
+ EnteredSurface (seat, dispatch, pinch->time, x, y, False);
+
+ /* Now do the actual event dispatch. */
+ switch (pinch->evtype)
+ {
+ case XI_GesturePinchBegin:
+ /* Send a motion event, in case the position changed. */
+ SendMotion (seat, dispatch, x, y, pinch->time);
+
+ /* Send a begin event. */
+ SendGesturePinchBegin (seat, dispatch, pinch->time, pinch->detail);
+
+ /* Say that the seat is in the middle of a pinch gesture, so it
+ can be cancelled should the pointer move out of this
+ surface. */
+ seat->flags |= IsInPinchGesture;
+ break;
+
+ case XI_GesturePinchUpdate:
+ /* The gesture sequence was cancelled for some other reason. */
+ if (!(seat->flags & IsInPinchGesture))
+ return;
+
+ /* Send an update event. */
+ SendGesturePinchUpdate (seat, dispatch, pinch->time,
+ pinch->delta_x, pinch->delta_y,
+ pinch->scale, pinch->delta_angle);
+ break;
+
+ case XI_GesturePinchEnd:
+ /* The gesture sequence was cancelled for some other reason. */
+ if (!(seat->flags & IsInPinchGesture))
+ return;
+
+ /* Send an end event. */
+ SendGesturePinchEnd (seat, dispatch, pinch->time,
+ pinch->flags & XIGesturePinchEventCancelled);
+ break;
+ }
+}
+
+static void
+SendGestureSwipeBegin (Seat *seat, Surface *dispatch, Time time,
+ int detail)
+{
+ SwipeGesture *gesture;
+ uint32_t serial;
+ SeatClientInfo *info;
+
+ serial = wl_display_next_serial (compositor.wl_display);
+ info = ClientInfoForResource (seat, dispatch->resource);
+
+ if (!info)
+ return;
+
+ gesture = info->swipe_gestures.next;
+
+ for (; gesture != &info->swipe_gestures; gesture = gesture->next)
+ zwp_pointer_gesture_swipe_v1_send_begin (gesture->resource,
+ serial, time,
+ dispatch->resource,
+ detail);
+}
+
+static void
+SendGestureSwipeUpdate (Seat *seat, Surface *dispatch, Time time,
+ double dx, double dy)
+{
+ SwipeGesture *gesture;
+ SeatClientInfo *info;
+
+ info = ClientInfoForResource (seat, dispatch->resource);
+
+ if (!info)
+ return;
+
+ gesture = info->swipe_gestures.next;
+
+ for (; gesture != &info->swipe_gestures; gesture = gesture->next)
+ zwp_pointer_gesture_swipe_v1_send_update (gesture->resource,
+ time,
+ wl_fixed_from_double (dx),
+ wl_fixed_from_double (dy));
+}
+
+static void
+SendGestureSwipeEnd (Seat *seat, Surface *dispatch, Time time, int cancelled)
+{
+ SwipeGesture *gesture;
+ SeatClientInfo *info;
+ uint32_t serial;
+
+ info = ClientInfoForResource (seat, dispatch->resource);
+
+ if (!info)
+ return;
+
+ gesture = info->swipe_gestures.next;
+ serial = wl_display_next_serial (compositor.wl_display);
+
+ for (; gesture != &info->swipe_gestures; gesture = gesture->next)
+ zwp_pointer_gesture_swipe_v1_send_end (gesture->resource,
+ serial, time, cancelled);
+}
+
+static void
+DispatchGestureSwipe (Subcompositor *subcompositor, XIGestureSwipeEvent *swipe)
+{
+ Seat *seat;
+ Surface *dispatch, *actual_dispatch;
+ double x, y, event_x, event_y;
+
+ seat = XLLookUpAssoc (seats, swipe->deviceid);
+
+ if (!seat)
+ return;
+
+ /* Move the icon surface. */
+ if (seat->icon_surface)
+ XLMoveIconSurface (seat->icon_surface, swipe->root_x,
+ swipe->root_y);
+
+ /* Update information used for resize tracking. */
+ seat->its_root_x = swipe->root_x;
+ seat->its_root_y = swipe->root_y;
+ seat->its_press_time = swipe->time;
+
+ /* Update the last user time. */
+ seat->last_user_time = TimestampFromServerTime (swipe->time);
+
+ /* Now find the dispatch surface so we can enter it if required.
+ Most of this code is copied from DispatchMotion; it should
+ probably be moved to a separate function. */
+ actual_dispatch = FindSurfaceUnder (subcompositor, swipe->event_x,
+ swipe->event_y);
+
+ if (seat->grab_held)
+ {
+ /* If the grab is held, make the surface underneath the pointer
+ the pending unlock surface. */
+ SwapUnlockSurface (seat, actual_dispatch);
+ dispatch = seat->last_seen_surface;
+ }
+ else
+ dispatch = actual_dispatch;
+
+ event_x = swipe->event_y;
+ event_y = swipe->event_y;
+
+ if (!dispatch)
+ {
+ if (seat->grab_surface)
+ {
+ /* If the grab surface is set, translate the coordinates to
+ it and use it instead. */
+ TranslateGrabPosition (seat, swipe->event,
+ &event_x, &event_y);
+ dispatch = seat->grab_surface;
+
+ goto after_dispatch_set;
+ }
+
+ EnteredSurface (seat, dispatch, swipe->time, 0, 0, False);
+ return;
+ }
+
+ after_dispatch_set:
+ TransformToSurface (dispatch, event_x, event_y, &x, &y);
+ EnteredSurface (seat, dispatch, swipe->time, x, y, False);
+
+ /* Now do the actual event dispatch. */
+ switch (swipe->evtype)
+ {
+ case XI_GestureSwipeBegin:
+ /* Send a motion event, in case the position changed. */
+ SendMotion (seat, dispatch, x, y, swipe->time);
+
+ /* Send a begin event. */
+ SendGestureSwipeBegin (seat, dispatch, swipe->time, swipe->detail);
+
+ /* Say that the seat is in the middle of a swipe gesture, so it
+ can be cancelled should the pointer move out of this
+ surface. */
+ seat->flags |= IsInSwipeGesture;
+ break;
+
+ case XI_GestureSwipeUpdate:
+ /* The gesture sequence was cancelled for some other reason. */
+ if (!(seat->flags & IsInSwipeGesture))
+ return;
+
+ /* Send an update event. */
+ SendGestureSwipeUpdate (seat, dispatch, swipe->time,
+ swipe->delta_x, swipe->delta_y);
+ break;
+
+ case XI_GestureSwipeEnd:
+ /* The gesture sequence was cancelled for some other reason. */
+ if (!(seat->flags & IsInSwipeGesture))
+ return;
+
+ /* Send an end event. */
+ SendGestureSwipeEnd (seat, dispatch, swipe->time,
+ swipe->flags & XIGestureSwipeEventCancelled);
+ break;
+ }
+}
+
static void
WriteKeymap (void)
{
@@ -4773,6 +5198,8 @@ XLGetGEWindowForSeats (XEvent *event)
XIEnterEvent *enter;
XIDeviceEvent *xev;
XIBarrierEvent *barrier;
+ XIGesturePinchEvent *pinch;
+ XIGestureSwipeEvent *swipe;
if (event->type == GenericEvent
&& event->xgeneric.extension == xi2_opcode)
@@ -4800,6 +5227,18 @@ XLGetGEWindowForSeats (XEvent *event)
case XI_BarrierHit:
barrier = event->xcookie.data;
return barrier->event;
+
+ case XI_GesturePinchBegin:
+ case XI_GesturePinchEnd:
+ case XI_GesturePinchUpdate:
+ pinch = event->xcookie.data;
+ return pinch->event;
+
+ case XI_GestureSwipeBegin:
+ case XI_GestureSwipeEnd:
+ case XI_GestureSwipeUpdate:
+ swipe = event->xcookie.data;
+ return swipe->event;
}
}
@@ -4830,6 +5269,18 @@ XLSelectStandardEvents (Window window)
XISetMask (mask.mask, XI_KeyRelease);
XISetMask (mask.mask, XI_BarrierHit);
+ if (xi2_major > 2 || xi2_minor >= 4)
+ {
+ /* Select for gesture events whenever supported. */
+
+ XISetMask (mask.mask, XI_GesturePinchBegin);
+ XISetMask (mask.mask, XI_GesturePinchUpdate);
+ XISetMask (mask.mask, XI_GesturePinchEnd);
+ XISetMask (mask.mask, XI_GestureSwipeBegin);
+ XISetMask (mask.mask, XI_GestureSwipeUpdate);
+ XISetMask (mask.mask, XI_GestureSwipeEnd);
+ }
+
XISelectEvents (compositor.display, window, &mask, 1);
}
@@ -4854,6 +5305,14 @@ XLDispatchGEForSeats (XEvent *event, Surface *surface,
DispatchKey (event->xcookie.data);
else if (event->xgeneric.evtype == XI_BarrierHit)
DispatchBarrierHit (event->xcookie.data);
+ else if (event->xgeneric.evtype == XI_GesturePinchBegin
+ || event->xgeneric.evtype == XI_GesturePinchUpdate
+ || event->xgeneric.evtype == XI_GesturePinchEnd)
+ DispatchGesturePinch (subcompositor, event->xcookie.data);
+ else if (event->xgeneric.evtype == XI_GestureSwipeBegin
+ || event->xgeneric.evtype == XI_GestureSwipeUpdate
+ || event->xgeneric.evtype == XI_GestureSwipeEnd)
+ DispatchGestureSwipe (subcompositor, event->xcookie.data);
}
Cursor
@@ -5555,7 +6014,7 @@ XLInitSeats (void)
/* This is the version of the input extension that we want. */
xi2_major = 2;
- xi2_minor = 3;
+ xi2_minor = 4;
if (XQueryExtension (compositor.display, "XInputExtension",
&xi2_opcode, &xi_first_event, &xi_first_error))
@@ -5804,6 +6263,84 @@ XLSeatDestroyRelativePointer (RelativePointer *relative_pointer)
XLFree (relative_pointer);
}
+SwipeGesture *
+XLSeatGetSwipeGesture (Seat *seat, struct wl_resource *resource)
+{
+ SwipeGesture *swipe_gesture;
+ SeatClientInfo *info;
+
+ /* Create a swipe gesture object for the resource RESOURCE. */
+ swipe_gesture = XLCalloc (1, sizeof *swipe_gesture);
+
+ /* Obtain or create the seat client info. */
+ info = CreateSeatClientInfo (seat, wl_resource_get_client (resource));
+
+ /* Link the gesture onto it. */
+ swipe_gesture->next = info->swipe_gestures.next;
+ swipe_gesture->last = &info->swipe_gestures;
+ info->swipe_gestures.next->last = swipe_gesture;
+ info->swipe_gestures.next = swipe_gesture;
+ swipe_gesture->info = info;
+
+ /* Set the seat and resource. */
+ swipe_gesture->seat = seat;
+ swipe_gesture->resource = resource;
+ RetainSeat (seat);
+
+ return swipe_gesture;
+}
+
+PinchGesture *
+XLSeatGetPinchGesture (Seat *seat, struct wl_resource *resource)
+{
+ PinchGesture *pinch_gesture;
+ SeatClientInfo *info;
+
+ /* Create a pinch gesture object for the resource RESOURCE. */
+ pinch_gesture = XLCalloc (1, sizeof *pinch_gesture);
+
+ /* Obtain or create the seat client info. */
+ info = CreateSeatClientInfo (seat, wl_resource_get_client (resource));
+
+ /* Link the gesture onto it. */
+ pinch_gesture->next = info->pinch_gestures.next;
+ pinch_gesture->last = &info->pinch_gestures;
+ info->pinch_gestures.next->last = pinch_gesture;
+ info->pinch_gestures.next = pinch_gesture;
+ pinch_gesture->info = info;
+
+ /* Set the seat and resource. */
+ pinch_gesture->seat = seat;
+ pinch_gesture->resource = resource;
+ RetainSeat (seat);
+
+ return pinch_gesture;
+}
+
+void
+XLSeatDestroySwipeGesture (SwipeGesture *swipe_gesture)
+{
+ swipe_gesture->last->next = swipe_gesture->next;
+ swipe_gesture->next->last = swipe_gesture->last;
+
+ ReleaseSeatClientInfo (swipe_gesture->info);
+ ReleaseSeat (swipe_gesture->seat);
+
+ XLFree (swipe_gesture);
+}
+
+void
+XLSeatDestroyPinchGesture (PinchGesture *pinch_gesture)
+{
+ pinch_gesture->last->next = pinch_gesture->next;
+ pinch_gesture->next->last = pinch_gesture->last;
+
+ ReleaseSeatClientInfo (pinch_gesture->info);
+ ReleaseSeat (pinch_gesture->seat);
+
+ XLFree (pinch_gesture);
+}
+
Bool
XLSeatApplyExternalGrab (Seat *seat, Surface *surface)
{