/* * Copyright © 2011 Collabra Ltd. * Copyright © 2011 Red Hat, Inc. * Copyright © 2020 Povilas Kanapickas * * 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. */ #ifdef HAVE_DIX_CONFIG_H #include #endif #include "inputstr.h" #include "scrnintstr.h" #include "dixgrabs.h" #include "eventstr.h" #include "exevents.h" #include "exglobals.h" #include "inpututils.h" #include "eventconvert.h" #include "windowstr.h" #include "mi.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; } /** * 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) { if (gi->listener.grab) { 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 RT_NONE since we keep the grab pointer */ GestureAddListener(gi, grab->resource, RT_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) { 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); }