369 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			369 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
| /*
 | |
|  * Copyright © 2011 Collabra Ltd.
 | |
|  * Copyright © 2011 Red Hat, Inc.
 | |
|  * Copyright © 2020 Povilas Kanapickas  <povilas@radix.lt>
 | |
|  *
 | |
|  * Permission is hereby granted, free of charge, to any person obtaining a
 | |
|  * copy of this software and associated documentation files (the "Software"),
 | |
|  * to deal in the Software without restriction, including without limitation
 | |
|  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 | |
|  * and/or sell copies of the Software, and to permit persons to whom the
 | |
|  * Software is furnished to do so, subject to the following conditions:
 | |
|  *
 | |
|  * The above copyright notice and this permission notice (including the next
 | |
|  * paragraph) shall be included in all copies or substantial portions of the
 | |
|  * Software.
 | |
|  *
 | |
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 | |
|  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 | |
|  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 | |
|  * DEALINGS IN THE SOFTWARE.
 | |
|  */
 | |
| 
 | |
| #include <dix-config.h>
 | |
| 
 | |
| #include "dix/dix_priv.h"
 | |
| #include "dix/dixgrabs_priv.h"
 | |
| #include "dix/eventconvert.h"
 | |
| #include "dix/input_priv.h"
 | |
| #include "dix/resource_priv.h"
 | |
| #include "mi/mi_priv.h"
 | |
| #include "os/bug_priv.h"
 | |
| 
 | |
| #include "inputstr.h"
 | |
| #include "scrnintstr.h"
 | |
| #include "eventstr.h"
 | |
| #include "exevents.h"
 | |
| #include "exglobals.h"
 | |
| #include "inpututils.h"
 | |
| #include "windowstr.h"
 | |
| 
 | |
| #define GESTURE_HISTORY_SIZE 100
 | |
| 
 | |
| Bool
 | |
| GestureInitGestureInfo(GestureInfoPtr gi)
 | |
| {
 | |
|     memset(gi, 0, sizeof(*gi));
 | |
| 
 | |
|     gi->sprite.spriteTrace = calloc(32, sizeof(*gi->sprite.spriteTrace));
 | |
|     if (!gi->sprite.spriteTrace) {
 | |
|         return FALSE;
 | |
|     }
 | |
|     gi->sprite.spriteTraceSize = 32;
 | |
|     gi->sprite.spriteTrace[0] = screenInfo.screens[0]->root;
 | |
|     gi->sprite.hot.pScreen = screenInfo.screens[0];
 | |
|     gi->sprite.hotPhys.pScreen = screenInfo.screens[0];
 | |
| 
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| void
 | |
| GestureFreeGestureInfo(GestureInfoPtr gi)
 | |
| {
 | |
|     free(gi->sprite.spriteTrace);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Given an event type returns the associated gesture event info.
 | |
|  */
 | |
| GestureInfoPtr
 | |
| GestureFindActiveByEventType(DeviceIntPtr dev, int type)
 | |
| {
 | |
|     GestureClassPtr g = dev->gesture;
 | |
|     enum EventType type_to_expect = GestureTypeToBegin(type);
 | |
| 
 | |
|     if (!g || type_to_expect == 0 || !g->gesture.active ||
 | |
|         g->gesture.type != type_to_expect) {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     return &g->gesture;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sets up gesture info for a new gesture. Returns NULL on failure.
 | |
|  */
 | |
| GestureInfoPtr
 | |
| GestureBeginGesture(DeviceIntPtr dev, InternalEvent *ev)
 | |
| {
 | |
|     GestureClassPtr g = dev->gesture;
 | |
|     enum EventType gesture_type = GestureTypeToBegin(ev->any.type);
 | |
| 
 | |
|     /* Note that we ignore begin events when an existing gesture is active */
 | |
|     if (!g || gesture_type == 0 || g->gesture.active)
 | |
|         return NULL;
 | |
| 
 | |
|     g->gesture.type = gesture_type;
 | |
| 
 | |
|     if (!GestureBuildSprite(dev, &g->gesture))
 | |
|         return NULL;
 | |
| 
 | |
|     g->gesture.active = TRUE;
 | |
|     g->gesture.num_touches = ev->gesture_event.num_touches;
 | |
|     g->gesture.sourceid = ev->gesture_event.sourceid;
 | |
|     g->gesture.has_listener = FALSE;
 | |
|     return &g->gesture;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Releases a gesture: this must only be called after all events
 | |
|  * related to that gesture have been sent and finalised.
 | |
|  */
 | |
| void
 | |
| GestureEndGesture(GestureInfoPtr gi)
 | |
| {
 | |
|     if (gi->has_listener) {
 | |
|         FreeGrab(gi->listener.grab);
 | |
|         gi->listener.grab = NULL;
 | |
|         gi->listener.listener = 0;
 | |
|         gi->has_listener = FALSE;
 | |
|     }
 | |
| 
 | |
|     gi->active = FALSE;
 | |
|     gi->num_touches = 0;
 | |
|     gi->sprite.spriteTraceGood = 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Ensure a window trace is present in gi->sprite, constructing one for
 | |
|  * Gesture{Pinch,Swipe}Begin events.
 | |
|  */
 | |
| Bool
 | |
| GestureBuildSprite(DeviceIntPtr sourcedev, GestureInfoPtr gi)
 | |
| {
 | |
|     SpritePtr sprite = &gi->sprite;
 | |
| 
 | |
|     if (!sourcedev->spriteInfo->sprite)
 | |
|         return FALSE;
 | |
| 
 | |
|     if (!CopySprite(sourcedev->spriteInfo->sprite, sprite))
 | |
|         return FALSE;
 | |
| 
 | |
|     if (sprite->spriteTraceGood <= 0)
 | |
|         return FALSE;
 | |
| 
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @returns TRUE if the specified grab or selection is the current owner of
 | |
|  * the gesture sequence.
 | |
|  */
 | |
| Bool
 | |
| GestureResourceIsOwner(GestureInfoPtr gi, XID resource)
 | |
| {
 | |
|     return (gi->listener.listener == resource);
 | |
| }
 | |
| 
 | |
| void
 | |
| GestureAddListener(GestureInfoPtr gi, XID resource, int resource_type,
 | |
|                    enum GestureListenerType type, WindowPtr window, const GrabPtr grab)
 | |
| {
 | |
|     GrabPtr g = NULL;
 | |
| 
 | |
|     BUG_RETURN(gi->has_listener);
 | |
| 
 | |
|     /* We need a copy of the grab, not the grab itself since that may be deleted by
 | |
|      * a UngrabButton request and leaves us with a dangling pointer */
 | |
|     if (grab)
 | |
|         g = AllocGrab(grab);
 | |
| 
 | |
|     gi->listener.listener = resource;
 | |
|     gi->listener.resource_type = resource_type;
 | |
|     gi->listener.type = type;
 | |
|     gi->listener.window = window;
 | |
|     gi->listener.grab = g;
 | |
|     gi->has_listener = TRUE;
 | |
| }
 | |
| 
 | |
| static void
 | |
| GestureAddGrabListener(DeviceIntPtr dev, GestureInfoPtr gi, GrabPtr grab)
 | |
| {
 | |
|     enum GestureListenerType type;
 | |
| 
 | |
|     /* FIXME: owner_events */
 | |
| 
 | |
|     if (grab->grabtype == XI2) {
 | |
|         if (xi2mask_isset(grab->xi2mask, dev, XI_GesturePinchBegin) ||
 | |
|             xi2mask_isset(grab->xi2mask, dev, XI_GestureSwipeBegin)) {
 | |
|             type = GESTURE_LISTENER_GRAB;
 | |
|         } else
 | |
|             type = GESTURE_LISTENER_NONGESTURE_GRAB;
 | |
|     }
 | |
|     else if (grab->grabtype == XI || grab->grabtype == CORE) {
 | |
|         type = GESTURE_LISTENER_NONGESTURE_GRAB;
 | |
|     }
 | |
|     else {
 | |
|         BUG_RETURN_MSG(1, "Unsupported grab type\n");
 | |
|     }
 | |
| 
 | |
|     /* grab listeners are always X11_RESTYPE_NONE since we keep the grab pointer */
 | |
|     GestureAddListener(gi, grab->resource, X11_RESTYPE_NONE, type, grab->window, grab);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add one listener if there is a grab on the given window.
 | |
|  */
 | |
| static void
 | |
| GestureAddPassiveGrabListener(DeviceIntPtr dev, GestureInfoPtr gi, WindowPtr win, InternalEvent *ev)
 | |
| {
 | |
|     Bool activate = FALSE;
 | |
|     Bool check_core = FALSE;
 | |
| 
 | |
|     GrabPtr grab = CheckPassiveGrabsOnWindow(win, dev, ev, check_core,
 | |
|                                              activate);
 | |
|     if (!grab)
 | |
|         return;
 | |
| 
 | |
|     /* We'll deliver later in gesture-specific code */
 | |
|     ActivateGrabNoDelivery(dev, grab, ev, ev);
 | |
|     GestureAddGrabListener(dev, gi, grab);
 | |
| }
 | |
| 
 | |
| static void
 | |
| GestureAddRegularListener(DeviceIntPtr dev, GestureInfoPtr gi, WindowPtr win, InternalEvent *ev)
 | |
| {
 | |
|     InputClients *iclients = NULL;
 | |
|     OtherInputMasks *inputMasks = NULL;
 | |
|     uint16_t evtype = GetXI2Type(ev->any.type);
 | |
|     int mask;
 | |
| 
 | |
|     mask = EventIsDeliverable(dev, ev->any.type, win);
 | |
|     if (!mask)
 | |
|         return;
 | |
| 
 | |
|     inputMasks = wOtherInputMasks(win);
 | |
| 
 | |
|     if ((mask & EVENT_XI2_MASK) && (inputMasks != NULL)) {
 | |
|         nt_list_for_each_entry(iclients, inputMasks->inputClients, next) {
 | |
|             if (!xi2mask_isset(iclients->xi2mask, dev, evtype))
 | |
|                 continue;
 | |
| 
 | |
|             GestureAddListener(gi, iclients->resource, RT_INPUTCLIENT,
 | |
|                                GESTURE_LISTENER_REGULAR, win, NULL);
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| GestureSetupListener(DeviceIntPtr dev, GestureInfoPtr gi, InternalEvent *ev)
 | |
| {
 | |
|     int i;
 | |
|     SpritePtr sprite = &gi->sprite;
 | |
|     WindowPtr win;
 | |
| 
 | |
|     /* Any current grab will consume all gesture events */
 | |
|     if (dev->deviceGrab.grab) {
 | |
|         GestureAddGrabListener(dev, gi, dev->deviceGrab.grab);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* Find passive grab that would be activated by this event, if any. If we're handling
 | |
|      * ReplayDevice then the search starts from the descendant of the grab window, otherwise
 | |
|      * the search starts at the root window. The search ends at deepest child window. */
 | |
|     i = 0;
 | |
|     if (syncEvents.playingEvents) {
 | |
|         while (i < dev->spriteInfo->sprite->spriteTraceGood) {
 | |
|             if (dev->spriteInfo->sprite->spriteTrace[i++] == syncEvents.replayWin)
 | |
|                 break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     for (; i < sprite->spriteTraceGood; i++) {
 | |
|         win = sprite->spriteTrace[i];
 | |
|         GestureAddPassiveGrabListener(dev, gi, win, ev);
 | |
|         if (gi->has_listener)
 | |
|             return;
 | |
|     }
 | |
| 
 | |
|     /* Find the first client with an applicable event selection,
 | |
|      * going from deepest child window back up to the root window. */
 | |
|     for (i = sprite->spriteTraceGood - 1; i >= 0; i--) {
 | |
|         win = sprite->spriteTrace[i];
 | |
|         GestureAddRegularListener(dev, gi, win, ev);
 | |
|         if (gi->has_listener)
 | |
|             return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* As gesture grabs don't turn into active grabs with their own resources, we
 | |
|  * need to walk all the gestures and remove this grab from listener */
 | |
| void
 | |
| GestureListenerGone(XID resource)
 | |
| {
 | |
|     GestureInfoPtr gi;
 | |
|     DeviceIntPtr dev;
 | |
|     InternalEvent *events = InitEventList(GetMaximumEventsNum());
 | |
| 
 | |
|     if (!events)
 | |
|         FatalError("GestureListenerGone: couldn't allocate events\n");
 | |
| 
 | |
|     for (dev = inputInfo.devices; dev; dev = dev->next) {
 | |
|         if (!dev->gesture)
 | |
|             continue;
 | |
| 
 | |
|         gi = &dev->gesture->gesture;
 | |
|         if (!gi->active)
 | |
|             continue;
 | |
| 
 | |
|         if (CLIENT_BITS(gi->listener.listener) == resource)
 | |
|             GestureEndGesture(gi);
 | |
|     }
 | |
| 
 | |
|     FreeEventList(events, GetMaximumEventsNum());
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * End physically active gestures for a device.
 | |
|  */
 | |
| void
 | |
| GestureEndActiveGestures(DeviceIntPtr dev)
 | |
| {
 | |
|     GestureClassPtr g = dev->gesture;
 | |
|     InternalEvent *eventlist;
 | |
| 
 | |
|     if (!g)
 | |
|         return;
 | |
| 
 | |
|     eventlist = InitEventList(GetMaximumEventsNum());
 | |
| 
 | |
|     input_lock();
 | |
|     mieqProcessInputEvents();
 | |
|     if (g->gesture.active) {
 | |
|         int j;
 | |
|         int type = GetXI2Type(GestureTypeToEnd(g->gesture.type));
 | |
|         int nevents = GetGestureEvents(eventlist, dev, type, g->gesture.num_touches,
 | |
|                                        0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
 | |
| 
 | |
|         for (j = 0; j < nevents; j++)
 | |
|             mieqProcessDeviceEvent(dev, eventlist + j, NULL);
 | |
|     }
 | |
|     input_unlock();
 | |
| 
 | |
|     FreeEventList(eventlist, GetMaximumEventsNum());
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Generate and deliver a Gesture{Pinch,Swipe}End event to the owner.
 | |
|  *
 | |
|  * @param dev The device to deliver the event for.
 | |
|  * @param gi The gesture record to deliver the event for.
 | |
|  */
 | |
| void
 | |
| GestureEmitGestureEndToOwner(DeviceIntPtr dev, GestureInfoPtr gi)
 | |
| {
 | |
|     InternalEvent event;
 | |
|     /* We're not processing a gesture end for a frozen device */
 | |
|     if (dev->deviceGrab.sync.frozen)
 | |
|         return;
 | |
| 
 | |
|     DeliverDeviceClassesChangedEvent(gi->sourceid, GetTimeInMillis());
 | |
|     InitGestureEvent(&event, dev, GetTimeInMillis(), GestureTypeToEnd(gi->type),
 | |
|                      0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
 | |
|     DeliverGestureEventToOwner(dev, gi, &event);
 | |
| }
 |