diff options
author | Povilas Kanapickas <povilas@radix.lt> | 2021-05-30 18:24:01 +0300 |
---|---|---|
committer | Povilas Kanapickas <povilas@radix.lt> | 2021-05-30 18:34:36 +0300 |
commit | 056917e10ed6ace112a1fe98cd0f74a27ad5aad0 (patch) | |
tree | b53c9c0addad2a393dc11b801cab14b0c8c93340 | |
parent | 2d8cc468335383243aa9fb2cea3b8ae3bb2c66c1 (diff) |
Implement tests for gesture events
-rw-r--r-- | meson.build | 8 | ||||
-rw-r--r-- | tests/server/gestures-common.h | 268 | ||||
-rw-r--r-- | tests/server/gestures.cpp | 688 | ||||
-rw-r--r-- | tests/server/grab-gestures.cpp | 1491 |
4 files changed, 2455 insertions, 0 deletions
diff --git a/meson.build b/meson.build index f0bf895..2573f09 100644 --- a/meson.build +++ b/meson.build @@ -5,6 +5,12 @@ project('xorg-integration-tests', 'cpp', pkgconfig = import('pkgconfig') dep_x11 = dependency('x11') + +dep_inputproto24 = dependency('inputproto', version: '>= 2.3.99.1', required: false) +if dep_inputproto24.found() + add_project_arguments('-DHAVE_XI24=1', language: 'cpp') +endif + dep_xi = dependency('xi') dep_xext = dependency('xext') dep_xrandr = dependency('xrandr') @@ -238,6 +244,8 @@ test('test-xserver', executable('test-xserver', 'tests/server/dga.cpp', 'tests/server/grab.cpp', + 'tests/server/gestures.cpp', + 'tests/server/grab-gestures.cpp', 'tests/server/xigrabbutton.cpp', 'tests/server/touch.cpp', 'tests/server/xtest.cpp', diff --git a/tests/server/gestures-common.h b/tests/server/gestures-common.h new file mode 100644 index 0000000..d581d4e --- /dev/null +++ b/tests/server/gestures-common.h @@ -0,0 +1,268 @@ +/* + * 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. + * + */ + +#ifndef XIT_SERVER_GESTURES_COMMON_H +#define XIT_SERVER_GESTURES_COMMON_H + +#include "xit-event.h" +#include "xit-server-input-test.h" +#include "device-inputtest-interface.h" + +#include <xorg/gtest/xorg-gtest.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/extensions/XInput2.h> +#include <X11/extensions/XInput.h> + +#if HAVE_XI24 +class GestureTest : public XITServerInputTest, + public DeviceInputTestInterface { +public: + xorg::testing::inputtest::Device& TouchpadDev() { return Dev(0); } + xorg::testing::inputtest::Device& TouchDev() { return Dev(1); } + xorg::testing::inputtest::Device& KeyboardDev() { return Dev(2); } + + bool IsPinch() const { return is_pinch; } + void SetIsPinch(bool is) { is_pinch = is; } + + void SetUp() override + { + AddDevice(xorg::testing::inputtest::DeviceType::POINTER_GESTURE); + AddDevice(xorg::testing::inputtest::DeviceType::TOUCH); + AddDevice(xorg::testing::inputtest::DeviceType::KEYBOARD); + + xi2_major_minimum = 2; + xi2_minor_minimum = 4; + + XITServerInputTest::SetUp(); + } + + /** + * Sets up an xorg.conf for a single evdev CoreKeyboard device based on + * the evemu device. The input from GetParam() is used as XkbLayout. + */ + void SetUpConfigAndLog() override + { + config.AddDefaultScreenWithDriver(); + config.AddInputSection(XORG_INPUTTEST_DRIVER, "--touchpad-device--", + "Option \"CorePointer\" \"on\"\n" + + TouchpadDev().GetOptions()); + config.AddInputSection(XORG_INPUTTEST_DRIVER, "--device--", + "Option \"CorePointer\" \"on\"\n" + + TouchDev().GetOptions()); + /* add default keyboard device */ + config.AddInputSection(XORG_INPUTTEST_DRIVER, "--kbd-device--", + "Option \"CoreKeyboard\" \"on\"\n" + + KeyboardDev().GetOptions()); + config.WriteConfig(); + } + + void StartServer() override + { + XITServerInputTest::StartServer(); + WaitForDevices(); + } + + /** + * Return a new synchronized client given our default server connection. + */ + virtual ::Display* NewClient(int maj = 2, int min = 4) + { + ::Display *d = XOpenDisplay(server.GetDisplayString().c_str()); + if (!d) + ADD_FAILURE() << "Failed to open display for new client.\n"; + XSynchronize(d, True); + EnsureVersion(d, maj, min); + return d; + } + + void EnsureVersion(::Display* dpy, int major, int minor) + { + int got_major = major, got_minor = minor; + if (major >= 2 && XIQueryVersion(dpy, &got_major, &got_minor) != Success) + ADD_FAILURE() << "XIQueryVersion failed on new client.\n"; + if (got_major < major || got_minor < minor) + ADD_FAILURE() << "The new client does not support requested input versions.\n"; + } + + enum GestureGrabType : unsigned { + GrabPinchGestures, + GrabSwipeGestures + }; + + void GrabDevice(::Display *dpy, int deviceid, Window win, int grab_mode, + const std::vector<int>& events) + { + EventMaskBuilder mask{VIRTUAL_CORE_POINTER_ID, events}; + ASSERT_EQ(Success, XIGrabDevice(dpy, deviceid, + win, CurrentTime, None, + grab_mode, GrabModeAsync, + False, mask.GetMask())); + XSync(dpy, False); + } + + void GrabButton(::Display* dpy, Window win, int button, int grab_mode, + const std::vector<int>& events) + { + EventMaskBuilder mask{VIRTUAL_CORE_POINTER_ID, events}; + + XIGrabModifiers mods = {}; + mods.modifiers = XIAnyModifier; + ASSERT_EQ(Success, XIGrabButton(dpy, VIRTUAL_CORE_POINTER_ID, button, win, None, + grab_mode, GrabModeAsync, False, + mask.GetMask(), 1, &mods)); + XSync(dpy, False); + } + + void GrabGestureBegin(::Display* dpy, Window win, int grab_mode, int other_grab_mode, + const std::vector<int>& events, bool is_pinch) + { + EventMaskBuilder mask{VIRTUAL_CORE_POINTER_ID, events}; + + XIGrabModifiers mods = {}; + mods.modifiers = XIAnyModifier; + if (is_pinch) { + ASSERT_EQ(Success, XIGrabPinchGestureBegin(dpy, VIRTUAL_CORE_POINTER_ID, win, grab_mode, + other_grab_mode, False, + mask.GetMask(), 1, &mods)); + } else { + ASSERT_EQ(Success, XIGrabSwipeGestureBegin(dpy, VIRTUAL_CORE_POINTER_ID, win, grab_mode, + other_grab_mode, False, + mask.GetMask(), 1, &mods)); + } + XSync(dpy, False); + } + + void GrabMatchingGestureBegin(::Display* dpy, Window win, int grab_mode, int other_grab_mode, + const std::vector<int>& events) + { + GrabGestureBegin(dpy, win, grab_mode, other_grab_mode, events, IsPinch()); + } + + void GrabNonMatchingGestureBegin(::Display* dpy, Window win, int grab_mode, int other_grab_mode, + const std::vector<int>& events) + { + GrabGestureBegin(dpy, win, grab_mode, other_grab_mode, events, !IsPinch()); + } + + void UngrabGestureBegin(::Display* dpy, Window win, bool is_pinch) + { + XIGrabModifiers mods = {}; + mods.modifiers = XIAnyModifier; + if (is_pinch) { + ASSERT_EQ(Success, XIUngrabPinchGestureBegin(dpy, VIRTUAL_CORE_POINTER_ID, win, + 1, &mods)); + } else { + ASSERT_EQ(Success, XIUngrabSwipeGestureBegin(dpy, VIRTUAL_CORE_POINTER_ID, win, + 1, &mods)); + } + } + + void UngrabMatchingGestureGrab(::Display* dpy, Window win) + { + UngrabGestureBegin(dpy, win, IsPinch()); + } + + void UngrabNonMatchingGestureGrab(::Display* dpy, Window win) + { + UngrabGestureBegin(dpy, win, !IsPinch()); + } + + int GetXIGestureBegin() const + { + return IsPinch() ? XI_GesturePinchBegin : XI_GestureSwipeBegin; + } + int GetXIGestureUpdate() const + { + return IsPinch() ? XI_GesturePinchUpdate : XI_GestureSwipeUpdate; + } + int GetXIGestureEnd() const + { + return IsPinch() ? XI_GesturePinchEnd : XI_GestureSwipeEnd; + } + + void GestureBegin() + { + if (IsPinch()) { + TouchpadDev().GesturePinchBegin(3, 0, 0, 0, 0, 1.0, 0); + } else { + TouchpadDev().GestureSwipeBegin(3, 0, 0, 0, 0); + } + } + + void GestureUpdate() + { + if (IsPinch()) { + TouchpadDev().GesturePinchUpdate(3, 1, 1, 0, 0, 1.0, 0); + } else { + TouchpadDev().GestureSwipeUpdate(3, 1, 1, 0, 0); + } + } + + void GestureEnd() + { + if (IsPinch()) { + TouchpadDev().GesturePinchEnd(3, 1, 1, 1, 1, 1.0, 0); + } else { + TouchpadDev().GestureSwipeEnd(3, 1, 1, 1, 1); + } + } + + void GesturePlayCancel() + { + if (IsPinch()) { + TouchpadDev().GesturePinchCancel(3, 1, 1, 1, 1, 1.0, 0); + } else { + TouchpadDev().GestureSwipeCancel(3, 1, 1, 1, 1); + } + } + +private: + bool is_pinch = true; +}; + +class GestureTypesTest : public GestureTest, + public ::testing::WithParamInterface<int> +{ +public: + GestureTypesTest() { + SetIsPinch(GetParam() == XI_GesturePinchBegin); + } +}; + +class GestureRootChildWindowTest : public GestureTest, + public ::testing::WithParamInterface<std::tuple<int, int, int>> +{ +public: + GestureRootChildWindowTest() + { + SetIsPinch(std::get<0>(GetParam()) == XI_GesturePinchBegin); + } + int Window1Depth() const { return std::get<1>(GetParam()); } + int Window2Depth() const { return std::get<2>(GetParam()); } +}; + +#endif +#endif diff --git a/tests/server/gestures.cpp b/tests/server/gestures.cpp new file mode 100644 index 0000000..f949e73 --- /dev/null +++ b/tests/server/gestures.cpp @@ -0,0 +1,688 @@ +/* + * 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. + * + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdexcept> +#include <tuple> + +#include "helpers.h" +#include "gestures-common.h" + +#if HAVE_XI24 + +class GestureEventTest : public GestureTypesTest {}; + +TEST_P(GestureEventTest, GestureEventFlags) +{ + XORG_TESTCASE("Register for gesture events on root window.\n" + "Trigger gesture begin/cancel\n" + "Verify only gesture flags are set on gesture events\n"); + + ::Display *dpy = Display(); + SelectXI2Events(dpy, VIRTUAL_CORE_POINTER_ID, DefaultRootWindow(dpy), + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + GesturePlayCancel(); + + if (IsPinch()) { + ASSERT_EVENT(XIGesturePinchEvent, ebegin, dpy, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(XIGesturePinchEvent, eupdate, dpy, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(XIGesturePinchEvent, ecancel, dpy, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EQ(ebegin->flags, 0); + ASSERT_EQ(eupdate->flags, 0); + ASSERT_EQ(ecancel->flags, XIGesturePinchEventCancelled); + } else { + ASSERT_EVENT(XIGestureSwipeEvent, ebegin, dpy, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(XIGestureSwipeEvent, eupdate, dpy, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(XIGestureSwipeEvent, ecancel, dpy, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EQ(ebegin->flags, 0); + ASSERT_EQ(eupdate->flags, 0); + ASSERT_EQ(ecancel->flags, XIGestureSwipeEventCancelled); + } + ASSERT_TRUE(NoEventPending(dpy)); +} + +TEST_P(GestureEventTest, DisableDeviceEndGestures) +{ + XORG_TESTCASE("Register for gesture events on root window.\n" + "Trigger gesture begin.\n" + "Disable the gesture device.\n" + "Ensure a GestureFooEnd is sent to that gesture.\n"); + + ::Display *dpy = Display(); + SelectXI2Events(dpy, VIRTUAL_CORE_POINTER_ID, DefaultRootWindow(dpy), + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + GestureBegin(); + ASSERT_EVENT(void, ebegin, dpy, GenericEvent, xi2_opcode, GetXIGestureBegin()); + + int deviceid = 0; + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad-device--", &deviceid), 1); + + XDevice *xdevice = XOpenDevice(dpy, deviceid); + ASSERT_TRUE(xdevice != NULL); + + XDeviceEnableControl enable_control; + enable_control.enable = false; + XDeviceControl *control = reinterpret_cast<XDeviceControl*>(&enable_control); + + ASSERT_TRUE(NoEventPending(dpy)); + ASSERT_EQ(XChangeDeviceControl(Display(), xdevice, DEVICE_ENABLE, control), Success); + XCloseDevice(Display(), xdevice); + XFlush(Display()); + + ASSERT_EVENT(void, eend, dpy, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy)); +} + +INSTANTIATE_TEST_CASE_P(, GestureEventTest, + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin)); + +class GestureSelectDevicesTest : public GestureTest, + public ::testing::WithParamInterface<std::tuple<int, int, int>> { +public: + GestureSelectDevicesTest() { + SetIsPinch(std::get<0>(GetParam()) == XI_GesturePinchBegin); + } +}; + +TEST_P(GestureSelectDevicesTest, GestureSelectionDeviceConflicts) +{ + XORG_TESTCASE("Client C1 selects for gesture events on a device.\n" + "Client C2 selects for gesture events, expected behavior:\n" + "- if C2 selects on the same specific device as A, generate an error.\n" + "- if C1 has XIAll(Master)Devices and B selects the same, generate an error.\n" + "- if C1 has XIAllDevices and B selects XIAllMasterDevices, allow.\n" + "- if C1 has XIAll(Master)Device and B selects a specific device, allow.\n" + "Same results with A and B swapped.\n"); + + ::Display *dpy = Display(); + ::Display *dpy2 = NewClient(); + + int client1_deviceid = std::get<1>(GetParam()); + int client2_deviceid = std::get<2>(GetParam()); + + SelectXI2Events(dpy, client1_deviceid, DefaultRootWindow(dpy), + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + SetErrorTrap(dpy); + SelectXI2Events(dpy2, client2_deviceid, DefaultRootWindow(dpy2), + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + XSync(dpy, False); + bool received_error = (ReleaseErrorTrap(dpy) != NULL); + + bool want_error = false; + + #define GetMask(deviceid1, deviceid2) ((deviceid1) | (deviceid2) << 8) + + switch (GetMask(client1_deviceid, client2_deviceid)) { + /* C1 on XIAllDevices */ + case GetMask(XIAllDevices, XIAllDevices): + want_error = true; + break; + case GetMask(XIAllDevices, XIAllMasterDevices): + want_error = false; /* XXX: Really? */ + break; + case GetMask(XIAllDevices, VIRTUAL_CORE_POINTER_ID): + want_error = false; + break; + + /* C1 on XIAllMasterDevices */ + case GetMask(XIAllMasterDevices, XIAllDevices): + want_error = false; /* XXX: really? */ + break; + case GetMask(XIAllMasterDevices, XIAllMasterDevices): + want_error = true; + break; + case GetMask(XIAllMasterDevices, VIRTUAL_CORE_POINTER_ID): + want_error = false; + break; + + /* C1 on VCP */ + case GetMask(VIRTUAL_CORE_POINTER_ID, XIAllDevices): + want_error = false; + break; + case GetMask(VIRTUAL_CORE_POINTER_ID, XIAllMasterDevices): + want_error = false; + break; + case GetMask(VIRTUAL_CORE_POINTER_ID, VIRTUAL_CORE_POINTER_ID): + want_error = true; + break; + default: + FAIL(); + break; + } + #undef GetMask + + if (want_error != received_error) { + ADD_FAILURE() << "Event selection failed\n" + " Client C1 selected on " << DeviceIDToString(client1_deviceid) << "\n" + " Client C2 selected on " << DeviceIDToString(client2_deviceid) << "\n" + " Expected an error? " << want_error << "\n" + " Received an error? " << received_error; + } + ASSERT_TRUE(NoEventPending(dpy)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +INSTANTIATE_TEST_CASE_P(, GestureSelectDevicesTest, + ::testing::Combine( + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin), + ::testing::Values(XIAllDevices, XIAllMasterDevices, VIRTUAL_CORE_POINTER_ID), + ::testing::Values(XIAllDevices, XIAllMasterDevices, VIRTUAL_CORE_POINTER_ID))); + +class GestureSelectPriorityTest : public GestureTest, + public ::testing::WithParamInterface<std::tuple<int, int, int, int, int>> { +public: + GestureSelectPriorityTest() { + SetIsPinch(GetEventType() == XI_GesturePinchBegin); + } + int GetEventType() const { return std::get<0>(GetParam()); } + int GetSelect1Type() { return std::get<1>(GetParam()); } + int GetSelect2Type() { return std::get<2>(GetParam()); } + + std::vector<int> GetSelectEventTypes(int evtype) const { + if (evtype == XI_GesturePinchBegin) { + return { XI_GesturePinchBegin, XI_GesturePinchUpdate, XI_GesturePinchEnd }; + } else { + return { XI_GestureSwipeBegin, XI_GestureSwipeUpdate, XI_GestureSwipeEnd }; + } + } + + int Window1Depth() const { return std::get<3>(GetParam()); } + int Window2Depth() const { return std::get<4>(GetParam()); } +}; + +std::vector<Window> CreateWindowHierarchy(Display *dpy, int depth) { + Window root = DefaultRootWindow(dpy); + + std::vector<Window> windows; + + windows.push_back(root); + + Window parent = root; + Window top_parent = None; + + while (depth--) { + Window win; + win = XCreateSimpleWindow(dpy, parent, 0, 0, + DisplayWidth(dpy, DefaultScreen(dpy)), + DisplayHeight(dpy, DefaultScreen(dpy)), + 0, 0, 0); + if (top_parent == None) + XSelectInput(dpy, win, StructureNotifyMask); + XMapWindow(dpy, win); + + windows.push_back(win); + parent = win; + } + + if (windows.size() > 1) { + XSync(dpy, False); + + if (xorg::testing::XServer::WaitForEventOfType(dpy, MapNotify, -1, -1)) { + XEvent ev; + XNextEvent(dpy, &ev); + } else { + ADD_FAILURE() << "Failed waiting for Exposure"; + } + + XSelectInput(dpy, windows[1], 0); + } + XSync(dpy, True); + + return windows; +} + + +TEST_P(GestureSelectPriorityTest, SelectionPriorities) +{ + XORG_TESTCASE("Client C1 creates several windows in a hierarchy.\n" + "Clients C1 and C2 select for gesture events on different windows.\n" + "If a client selects for events on a specific window and there is another\n" + "client which selects for the same events deeper in the hierarchy, then\n" + "the first client should get no events.\n"); + + ::Display *dpy = Display(); + ::Display *dpy2 = NewClient(); + + std::vector<Window> windows = CreateWindowHierarchy(dpy, 3); + Window event1_win = windows[Window1Depth()]; + Window event2_win = windows[Window2Depth()]; + + SelectXI2Events(dpy, VIRTUAL_CORE_POINTER_ID, event1_win, + GetSelectEventTypes(GetSelect1Type())); + SetErrorTrap(dpy); + SelectXI2Events(dpy2, VIRTUAL_CORE_POINTER_ID, event2_win, + GetSelectEventTypes(GetSelect2Type())); + XSync(dpy, False); + bool received_error = (ReleaseErrorTrap(dpy) != NULL); + + if (GetSelect1Type() == GetSelect2Type() && Window1Depth() == Window2Depth()) { + ASSERT_TRUE(received_error); + } else { + ASSERT_FALSE(received_error); + } + + bool client1_receive_events = (GetSelect1Type() == GetEventType()) && + !(GetSelect1Type() == GetSelect2Type() && Window1Depth() < Window2Depth()); + bool client2_receive_events = (GetSelect2Type() == GetEventType()) && + !(GetSelect1Type() == GetSelect2Type() && Window2Depth() <= Window1Depth()); + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + if (client1_receive_events) { + ASSERT_EVENT(void, event1, dpy, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, event2, dpy, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, event3, dpy, GenericEvent, xi2_opcode, GetXIGestureEnd()); + } + + if (client2_receive_events) { + ASSERT_EVENT(void, event1, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, event2, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, event3, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + } + + ASSERT_TRUE(NoEventPending(dpy)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +INSTANTIATE_TEST_CASE_P(, GestureSelectPriorityTest, + ::testing::Combine( + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin), + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin), + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin), + ::testing::Values(0, 1, 2), + ::testing::Values(0, 1, 2))); + +class GestureDeviceChangeSameDeviceTest : public GestureTypesTest {}; + +TEST_P(GestureDeviceChangeSameDeviceTest, SendsDeviceChangedEventWhenSourceDeviceChanges) +{ + XORG_TESTCASE("Register for gesture and motion events on root window.\n" + "Trigger mouse and gesture sequences the same device concurrently.\n" + "Expect no DeviceChangedEvent\n"); + + ::Display *dpy = Display(); + SelectXI2Events(dpy, VIRTUAL_CORE_POINTER_ID, DefaultRootWindow(dpy), + { XI_GesturePinchBegin, XI_GesturePinchUpdate, XI_GesturePinchEnd, + XI_GestureSwipeBegin, XI_GestureSwipeUpdate, XI_GestureSwipeEnd, + XI_DeviceChanged, XI_Motion }); + + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + TouchpadDev().RelMotion(1, 0); + GestureEnd(); + + ASSERT_EVENT(void, edcce, dpy, GenericEvent, xi2_opcode, XI_DeviceChanged); + ASSERT_EVENT(void, ebegin1, dpy, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, emotion1, dpy, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, eupdate1, dpy, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, emotion2, dpy, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, eend1, dpy, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy)); +} + +INSTANTIATE_TEST_CASE_P(, GestureDeviceChangeSameDeviceTest, + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin)); + +class GestureMultipleDevicesTest : public GestureTest, + public ::testing::WithParamInterface<std::tuple<int, int>> { +public: + xorg::testing::inputtest::Device& Touchpad2Dev() { return Dev(3); } + bool IsPinch2() const { return std::get<1>(GetParam()); } + + GestureMultipleDevicesTest() { + SetIsPinch(std::get<0>(GetParam()) == XI_GesturePinchBegin); + } + + void SetUp() override { + AddDevice(xorg::testing::inputtest::DeviceType::POINTER_GESTURE); + AddDevice(xorg::testing::inputtest::DeviceType::TOUCH); + AddDevice(xorg::testing::inputtest::DeviceType::KEYBOARD); + AddDevice(xorg::testing::inputtest::DeviceType::POINTER_GESTURE); + + xi2_major_minimum = 2; + xi2_minor_minimum = 4; + + XITServerInputTest::SetUp(); + } + + void AssertEventWithDeltaX(::Display* dpy, double delta_x, int event_type) { + if (event_type == XI_GesturePinchBegin || event_type == XI_GesturePinchUpdate || + event_type == XI_GesturePinchEnd) { + ASSERT_EVENT(XIGesturePinchEvent, event, dpy, GenericEvent, xi2_opcode, event_type); + ASSERT_EQ(event->delta_x, delta_x); + } else { + ASSERT_EVENT(XIGestureSwipeEvent, event, dpy, GenericEvent, xi2_opcode, event_type); + ASSERT_EQ(event->delta_x, delta_x); + } + } + + void AssertDeviceChangedEvent(::Display* dpy, int device_id) { + ASSERT_EVENT(XIDeviceChangedEvent, event, dpy, GenericEvent, xi2_opcode, XI_DeviceChanged); + ASSERT_EQ(event->deviceid, VIRTUAL_CORE_POINTER_ID); + ASSERT_EQ(event->sourceid, device_id); + ASSERT_EQ(event->reason, XISlaveSwitch); + } + + void SetUpConfigAndLog() override { + config.AddDefaultScreenWithDriver(); + config.AddInputSection(XORG_INPUTTEST_DRIVER, "--touchpad-device--", + "Option \"CorePointer\" \"on\"\n" + + TouchpadDev().GetOptions()); + config.AddInputSection(XORG_INPUTTEST_DRIVER, "--touch-device--", + "Option \"CorePointer\" \"on\"\n" + + TouchDev().GetOptions()); + /* add default keyboard device */ + config.AddInputSection(XORG_INPUTTEST_DRIVER, "--kbd-device--", + "Option \"CoreKeyboard\" \"on\"\n" + + KeyboardDev().GetOptions()); + config.AddInputSection(XORG_INPUTTEST_DRIVER, "--touchpad2-device--", + "Option \"CorePointer\" \"on\"\n" + + Touchpad2Dev().GetOptions()); + config.WriteConfig(); + } +}; + +class GestureDeviceChangeMultipleGesturesDeviceTest : public GestureMultipleDevicesTest {}; + +TEST_P(GestureDeviceChangeMultipleGesturesDeviceTest, IgnoresSimulataneousGestures) +{ + XORG_TESTCASE("Register for gesture events on root window.\n" + "Trigger two concurrent gestures from two devices simultaneously\n" + "Expect that the second gesture will be ignored.\n"); + + ::Display *dpy = Display(); + SelectXI2Events(dpy, VIRTUAL_CORE_POINTER_ID, DefaultRootWindow(dpy), + { XI_GesturePinchBegin, XI_GesturePinchUpdate, XI_GesturePinchEnd, + XI_GestureSwipeBegin, XI_GestureSwipeUpdate, XI_GestureSwipeEnd, + XI_DeviceChanged }); + + if (IsPinch()) { + TouchpadDev().GesturePinchBegin(3, 10, 0, 0, 0, 1.0, 0); + } else { + TouchpadDev().GestureSwipeBegin(3, 10, 0, 0, 0); + } + if (IsPinch2()) { + Touchpad2Dev().GesturePinchBegin(3, 20, 0, 0, 0, 1.0, 0); + } else { + Touchpad2Dev().GestureSwipeBegin(3, 20, 0, 0, 0); + } + if (IsPinch()) { + TouchpadDev().GesturePinchUpdate(3, 10, 1, 0, 0, 1.0, 0); + } else { + TouchpadDev().GestureSwipeUpdate(3, 10, 1, 0, 0); + } + if (IsPinch2()) { + Touchpad2Dev().GesturePinchUpdate(3, 20, 1, 0, 0, 1.0, 0); + } else { + Touchpad2Dev().GestureSwipeUpdate(3, 20, 1, 0, 0); + } + if (IsPinch()) { + TouchpadDev().GesturePinchEnd(3, 10, 1, 1, 1, 1.0, 0); + } else { + TouchpadDev().GestureSwipeEnd(3, 10, 1, 1, 1); + } + if (IsPinch2()) { + Touchpad2Dev().GesturePinchEnd(3, 20, 1, 1, 1, 1.0, 0); + } else { + Touchpad2Dev().GestureSwipeEnd(3, 20, 1, 1, 1); + } + + int deviceid1 = 0; + int deviceid2 = 0; + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad-device--", &deviceid1), 1); + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad2-device--", &deviceid2), 1); + + // FIXME: we ignore simultaneous gestures on multiple devices, but the DCCE events are generated + // during event submission, so we get back to back DCCE events that change to and from a + // slave device. + AssertDeviceChangedEvent(dpy, deviceid1); + AssertEventWithDeltaX(dpy, 10.0, GetXIGestureBegin()); + AssertDeviceChangedEvent(dpy, deviceid2); + AssertDeviceChangedEvent(dpy, deviceid1); + AssertEventWithDeltaX(dpy, 10.0, GetXIGestureUpdate()); + AssertDeviceChangedEvent(dpy, deviceid2); + AssertDeviceChangedEvent(dpy, deviceid1); + AssertEventWithDeltaX(dpy, 10.0, GetXIGestureEnd()); + AssertDeviceChangedEvent(dpy, deviceid2); + ASSERT_TRUE(NoEventPending(dpy)); +} + +TEST_P(GestureDeviceChangeMultipleGesturesDeviceTest, IgnoresSimulataneousGesturesMultiple) +{ + XORG_TESTCASE("Register for gesture events on root window.\n" + "Trigger several concurrent gestures from two devices simultaneously\n" + "Expect that the second gesture will be ignored.\n"); + + ::Display *dpy = Display(); + SelectXI2Events(dpy, VIRTUAL_CORE_POINTER_ID, DefaultRootWindow(dpy), + { XI_GesturePinchBegin, XI_GesturePinchUpdate, XI_GesturePinchEnd, + XI_GestureSwipeBegin, XI_GestureSwipeUpdate, XI_GestureSwipeEnd, + XI_DeviceChanged }); + + if (IsPinch()) { + TouchpadDev().GesturePinchBegin(3, 10, 0, 0, 0, 1.0, 0); + } else { + TouchpadDev().GestureSwipeBegin(3, 10, 0, 0, 0); + } + if (IsPinch2()) { + Touchpad2Dev().GesturePinchBegin(3, 20, 0, 0, 0, 1.0, 0); + } else { + Touchpad2Dev().GestureSwipeBegin(3, 20, 0, 0, 0); + } + if (IsPinch()) { + TouchpadDev().GesturePinchUpdate(3, 10, 1, 0, 0, 1.0, 0); + } else { + TouchpadDev().GestureSwipeUpdate(3, 10, 1, 0, 0); + } + if (IsPinch2()) { + Touchpad2Dev().GesturePinchUpdate(3, 20, 1, 0, 0, 1.0, 0); + Touchpad2Dev().GesturePinchEnd(3, 20, 1, 1, 1, 1.0, 0); + + Touchpad2Dev().GesturePinchBegin(4, 20, 0, 0, 0, 1.0, 0); + Touchpad2Dev().GesturePinchUpdate(4, 20, 1, 0, 0, 1.0, 0); + Touchpad2Dev().GesturePinchEnd(4, 20, 1, 1, 1, 1.0, 0); + + } else { + Touchpad2Dev().GestureSwipeUpdate(3, 20, 1, 0, 0); + Touchpad2Dev().GestureSwipeEnd(3, 20, 1, 1, 1); + + Touchpad2Dev().GestureSwipeBegin(4, 20, 0, 0, 0); + Touchpad2Dev().GestureSwipeUpdate(4, 20, 1, 0, 0); + Touchpad2Dev().GestureSwipeEnd(4, 20, 1, 1, 1); + } + if (IsPinch()) { + TouchpadDev().GesturePinchEnd(3, 10, 1, 1, 1, 1.0, 0); + } else { + TouchpadDev().GestureSwipeEnd(3, 10, 1, 1, 1); + } + + int deviceid1 = 0; + int deviceid2 = 0; + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad-device--", &deviceid1), 1); + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad2-device--", &deviceid2), 1); + + // FIXME: we ignore simultaneous gestures on multiple devices, but the DCCE events are generated + // during event submission, so we get back to back DCCE events that change to and from a + // slave device. + AssertDeviceChangedEvent(dpy, deviceid1); + AssertEventWithDeltaX(dpy, 10.0, GetXIGestureBegin()); + AssertDeviceChangedEvent(dpy, deviceid2); + AssertDeviceChangedEvent(dpy, deviceid1); + AssertEventWithDeltaX(dpy, 10.0, GetXIGestureUpdate()); + AssertDeviceChangedEvent(dpy, deviceid2); + AssertDeviceChangedEvent(dpy, deviceid1); + AssertEventWithDeltaX(dpy, 10.0, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy)); +} + +TEST_P(GestureDeviceChangeMultipleGesturesDeviceTest, + SendsDeviceChangedEventWhenSourceDeviceChanges) +{ + XORG_TESTCASE("Register for gesture events on root window.\n" + "Trigger gesture sequences from two devices non-concurrently.\n" + "Expect a DeviceChangedEvent for the mouse on the VCP\n"); + + ::Display *dpy = Display(); + SelectXI2Events(dpy, VIRTUAL_CORE_POINTER_ID, DefaultRootWindow(dpy), + { XI_GesturePinchBegin, XI_GesturePinchUpdate, XI_GesturePinchEnd, + XI_GestureSwipeBegin, XI_GestureSwipeUpdate, XI_GestureSwipeEnd, + XI_DeviceChanged }); + + if (IsPinch()) { + TouchpadDev().GesturePinchBegin(3, 10, 0, 0, 0, 1.0, 0); + TouchpadDev().GesturePinchUpdate(3, 10, 1, 0, 0, 1.0, 0); + TouchpadDev().GesturePinchEnd(3, 10, 1, 1, 1, 1.0, 0); + } else { + TouchpadDev().GestureSwipeBegin(3, 10, 0, 0, 0); + TouchpadDev().GestureSwipeUpdate(3, 10, 1, 0, 0); + TouchpadDev().GestureSwipeEnd(3, 10, 1, 1, 1); + } + if (IsPinch2()) { + Touchpad2Dev().GesturePinchBegin(3, 20, 0, 0, 0, 1.0, 0); + Touchpad2Dev().GesturePinchUpdate(3, 20, 1, 0, 0, 1.0, 0); + Touchpad2Dev().GesturePinchEnd(3, 20, 1, 1, 1, 1.0, 0); + } else { + Touchpad2Dev().GestureSwipeBegin(3, 20, 0, 0, 0); + Touchpad2Dev().GestureSwipeUpdate(3, 20, 1, 0, 0); + Touchpad2Dev().GestureSwipeEnd(3, 20, 1, 1, 1); + } + + int deviceid1 = 0, deviceid2 = 0; + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad-device--", &deviceid1), 1); + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad2-device--", &deviceid2), 1); + + AssertDeviceChangedEvent(dpy, deviceid1); + AssertEventWithDeltaX(dpy, 10.0, GetXIGestureBegin()); + AssertEventWithDeltaX(dpy, 10.0, GetXIGestureUpdate()); + AssertEventWithDeltaX(dpy, 10.0, GetXIGestureEnd()); + AssertDeviceChangedEvent(dpy, deviceid2); + if (IsPinch2()) { + AssertEventWithDeltaX(dpy, 20.0, XI_GesturePinchBegin); + AssertEventWithDeltaX(dpy, 20.0, XI_GesturePinchUpdate); + AssertEventWithDeltaX(dpy, 20.0, XI_GesturePinchEnd); + } else { + AssertEventWithDeltaX(dpy, 20.0, XI_GestureSwipeBegin); + AssertEventWithDeltaX(dpy, 20.0, XI_GestureSwipeUpdate); + AssertEventWithDeltaX(dpy, 20.0, XI_GestureSwipeEnd); + } + ASSERT_TRUE(NoEventPending(dpy)); +} + +INSTANTIATE_TEST_CASE_P(, GestureDeviceChangeMultipleGesturesDeviceTest, + ::testing::Combine( + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin), + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin))); + +class GestureDeviceChangeMultiplePointerDeviceTest : public GestureMultipleDevicesTest {}; + +TEST_P(GestureDeviceChangeMultiplePointerDeviceTest, DeviceChangedEventSimultaneousPointerEvents) +{ + XORG_TESTCASE("Register for gesture and motion events on root window.\n" + "Trigger concurrent gesture and pointer events from two devices simultaneously\n" + "Expect all input and device changed events to arrive.\n"); + + ::Display *dpy = Display(); + SelectXI2Events(dpy, VIRTUAL_CORE_POINTER_ID, DefaultRootWindow(dpy), + { XI_GesturePinchBegin, XI_GesturePinchUpdate, XI_GesturePinchEnd, + XI_GestureSwipeBegin, XI_GestureSwipeUpdate, XI_GestureSwipeEnd, + XI_DeviceChanged, XI_Motion }); + + GestureBegin(); + Touchpad2Dev().RelMotion(1, 0); + GestureUpdate(); + Touchpad2Dev().RelMotion(1, 0); + GestureEnd(); + + int deviceid1 = 0; + int deviceid2 = 0; + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad-device--", &deviceid1), 1); + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad2-device--", &deviceid2), 1); + + AssertDeviceChangedEvent(dpy, deviceid1); + ASSERT_EVENT(void, e_begin, dpy, GenericEvent, xi2_opcode, GetXIGestureBegin()); + AssertDeviceChangedEvent(dpy, deviceid2); + ASSERT_EVENT(void, e_motion1, dpy, GenericEvent, xi2_opcode, XI_Motion); + AssertDeviceChangedEvent(dpy, deviceid1); + ASSERT_EVENT(void, e_update, dpy, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + AssertDeviceChangedEvent(dpy, deviceid2); + ASSERT_EVENT(void, e_motion2, dpy, GenericEvent, xi2_opcode, XI_Motion); + AssertDeviceChangedEvent(dpy, deviceid1); + ASSERT_EVENT(void, e_end, dpy, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy)); +} + +TEST_P(GestureDeviceChangeMultiplePointerDeviceTest, DeviceChangedEventSimultaneousTouchEvents) +{ + XORG_TESTCASE("Register for gesture and motion events on root window.\n" + "Trigger concurrent gesture and pointer events from two devices simultaneously\n" + "Expect all input and device changed events to arrive.\n"); + + ::Display *dpy = Display(); + SelectXI2Events(dpy, VIRTUAL_CORE_POINTER_ID, DefaultRootWindow(dpy), + { XI_GesturePinchBegin, XI_GesturePinchUpdate, XI_GesturePinchEnd, + XI_GestureSwipeBegin, XI_GestureSwipeUpdate, XI_GestureSwipeEnd, + XI_DeviceChanged, XI_TouchBegin, XI_TouchUpdate, XI_TouchEnd }); + + GestureBegin(); + TouchDev().TouchBegin(100, 100, 1); + GestureUpdate(); + TouchDev().TouchUpdate(100, 100, 1); + GestureEnd(); + TouchDev().TouchEnd(100, 100, 1); + + int deviceid1 = 0; + int deviceid2 = 0; + ASSERT_EQ(FindInputDeviceByName(dpy, "--touchpad-device--", &deviceid1), 1); + ASSERT_EQ(FindInputDeviceByName(dpy, "--touch-device--", &deviceid2), 1); + + AssertDeviceChangedEvent(dpy, deviceid1); + ASSERT_EVENT(void, e_begin, dpy, GenericEvent, xi2_opcode, GetXIGestureBegin()); + AssertDeviceChangedEvent(dpy, deviceid2); + ASSERT_EVENT(void, e_touch_begin, dpy, GenericEvent, xi2_opcode, XI_TouchBegin); + AssertDeviceChangedEvent(dpy, deviceid1); + ASSERT_EVENT(void, e_update, dpy, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + AssertDeviceChangedEvent(dpy, deviceid2); + ASSERT_EVENT(void, e_touch_update, dpy, GenericEvent, xi2_opcode, XI_TouchUpdate); + AssertDeviceChangedEvent(dpy, deviceid1); + ASSERT_EVENT(void, e_end, dpy, GenericEvent, xi2_opcode, GetXIGestureEnd()); + AssertDeviceChangedEvent(dpy, deviceid2); + ASSERT_EVENT(void, e_touch_end, dpy, GenericEvent, xi2_opcode, XI_TouchEnd); + ASSERT_TRUE(NoEventPending(dpy)); +} + +INSTANTIATE_TEST_CASE_P(, GestureDeviceChangeMultiplePointerDeviceTest, + ::testing::Combine( + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin), + ::testing::Values(XI_GesturePinchBegin))); + +#endif diff --git a/tests/server/grab-gestures.cpp b/tests/server/grab-gestures.cpp new file mode 100644 index 0000000..d1c6ee6 --- /dev/null +++ b/tests/server/grab-gestures.cpp @@ -0,0 +1,1491 @@ +/* + * 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. + * + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include "helpers.h" +#include "gestures-common.h" + +#include <linux/input-event-codes.h> + +#if HAVE_XI24 + +enum GrabType { + GRABTYPE_CORE, + GRABTYPE_XI1, + GRABTYPE_XI2, +}; + +class GestureRootChildWindowGrabTypeTest : public GestureTest, + public ::testing::WithParamInterface<std::tuple<int, int, int, int>> { +public: + GestureRootChildWindowGrabTypeTest() { + SetIsPinch(std::get<0>(GetParam()) == XI_GesturePinchBegin); + } + int Window1Depth() const { return std::get<1>(GetParam()); } + int Window2Depth() const { return std::get<2>(GetParam()); } + GrabType GetGrabType() const { return static_cast<GrabType>(std::get<3>(GetParam())); } +}; + +TEST_P(GestureRootChildWindowGrabTypeTest, ActiveGrabOverGestureSelection) +{ + XORG_TESTCASE("Client C1 creates window and selects for touchpad gestures.\n" + "Client C2 has async active grab\n" + "Play a gesture on the window\n" + "Gestures events should not come to any client\n" + "This should hold regardless of whether grab or selection happened on the\n" + "root or the child window\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = CreateWindow(dpy1, None); + + Window select_win = Window1Depth() == 0 ? DefaultRootWindow(dpy1) : win; + Window grab_win = Window2Depth() == 0 ? DefaultRootWindow(dpy2) : win; + + SelectXI2Events(dpy1, XIAllMasterDevices, select_win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + EventMaskBuilder grab_mask{VIRTUAL_CORE_POINTER_ID, { XI_Motion }}; + + int deviceid; + FindInputDeviceByName(dpy2, "--touchpad-device--", &deviceid); + XDevice *xdev = XOpenDevice(dpy2, deviceid); + XEventClass cls; + int xi_motion; + DeviceMotionNotify(xdev, xi_motion, cls); + + switch (GetGrabType()) { + case GRABTYPE_CORE: + ASSERT_EQ(Success, XGrabPointer(dpy2, grab_win, False, PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, None, + CurrentTime)); + break; + case GRABTYPE_XI2: + ASSERT_EQ(Success, XIGrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, grab_win, CurrentTime, + None, GrabModeAsync, GrabModeAsync, False, + grab_mask.GetMask())); + break; + case GRABTYPE_XI1: + // XI1 does not allow us to grab main pointer. + break; + } + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XCloseDisplay(dpy2); +} + +INSTANTIATE_TEST_CASE_P(, GestureRootChildWindowGrabTypeTest, + ::testing::Combine( + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin), + ::testing::Values(0, 1), + ::testing::Values(0, 1), + ::testing::Values(GRABTYPE_CORE, GRABTYPE_XI2))); + +class GestureGrabRootChildWindowTest : public GestureRootChildWindowTest {}; + +TEST_P(GestureGrabRootChildWindowTest, ActiveGestureGrabOverGestureSelection) +{ + XORG_TESTCASE("Client C1 creates window and selects for touchpad gestures.\n" + "Client C2 has async active gesture grab.\n" + "Play a gesture on the window.\n" + "Gestures should come only to the C2 client.\n" + "This should hold regardless of whether grab or selection happened on the\n" + "root or the child window\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = CreateWindow(dpy1, None); + + Window select_win = Window1Depth() == 0 ? DefaultRootWindow(dpy1) : win; + Window grab_win = Window2Depth() == 0 ? DefaultRootWindow(dpy2) : win; + + SelectXI2Events(dpy1, XIAllMasterDevices, select_win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, grab_win, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, gesture11, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, gesture12, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, gesture13, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, gesture21, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, gesture22, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, gesture23, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy1)); + + XCloseDisplay(dpy2); +} + +TEST_P(GestureGrabRootChildWindowTest, PassiveMatchingGestureGrabOverGestureSelection) +{ + XORG_TESTCASE("Client C1 creates window and selects for touchpad gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture equence that triggers the grab\n" + "Gestures should come only to the C2 client.\n" + "This should hold regardless of whether grab or selection happened on the\n" + "root or the child window\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = CreateWindow(dpy1, None); + + Window select_win = Window1Depth() == 0 ? DefaultRootWindow(dpy1) : win; + Window grab_win = Window2Depth() == 0 ? DefaultRootWindow(dpy2) : win; + + SelectXI2Events(dpy1, XIAllMasterDevices, select_win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GrabMatchingGestureBegin(dpy2, grab_win, GrabModeAsync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, gesture1, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, gesture2, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, gesture3, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy1)); +} + +TEST_P(GestureGrabRootChildWindowTest, PassiveNonMatchingGestureGrabOverGestureSelection) +{ + XORG_TESTCASE("Client C1 creates window and selects for touchpad gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that does not trigger the grab\n" + "Gestures should come only to the C1 client.\n" + "This should hold regardless of whether grab or selection happened on the\n" + "root or the child window\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = CreateWindow(dpy1, None); + + Window select_win = Window1Depth() == 0 ? DefaultRootWindow(dpy1) : win; + Window grab_win = Window2Depth() == 0 ? DefaultRootWindow(dpy2) : win; + + SelectXI2Events(dpy1, XIAllMasterDevices, select_win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GrabNonMatchingGestureBegin(dpy2, grab_win, GrabModeAsync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, gesture1, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, gesture2, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, gesture3, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +INSTANTIATE_TEST_CASE_P(, GestureGrabRootChildWindowTest, + ::testing::Combine( + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin), + ::testing::Values(0, 1), + ::testing::Values(0, 1))); + +class GesturePassiveGrab : public GestureTypesTest {}; + +TEST_P(GesturePassiveGrab, SyncGrabReplaysEventsBeforeEnd) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that does triggers the grab.\n" + "C2 processes gesture begin only after begin and update are queued.\n" + "Gesture event should come only to the C2 client.\n" + "C2 calls XIAllowEvents(ReplayDevice) after getting gesture begin.\n" + "Events should be replayed to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GrabMatchingGestureBegin(dpy2, win, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureEnd(); + + ASSERT_EVENT(void, e_c1_end, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GesturePassiveGrab, SyncGrabReplaysEventsAtEnd) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that does triggers the grab.\n" + "C2 processes gesture begin only after begin, update and end are queued.\n" + "Gesture event should come only to the C2 client.\n" + "C2 calls XIAllowEvents(ReplayDevice) after getting gesture begin.\n" + "Events should be replayed to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GrabMatchingGestureBegin(dpy2, win, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c1_end, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GesturePassiveGrab, SyncGrabIgnoresPassiveGrabsAboveWhenReplaying) +{ + XORG_TESTCASE("Client C1 creates window and selects for gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that does trigger the grab.\n" + "Client C3 creates a passive gesture grab above the window." + "Gestures should come only to the C2 client.\n" + "C2 calls XIAllowEvents(ReplayDevice) and the events are replayed to C1.\n" + "No gestures should come to C3.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + ::Display *dpy3 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + Window win2 = CreateWindow(dpy1, None); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win2, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GrabMatchingGestureBegin(dpy2, win2, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + GrabMatchingGestureBegin(dpy3, win, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + GestureEnd(); + + ASSERT_EVENT(void, e_c1_end, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); +} + +TEST_P(GesturePassiveGrab, SyncGrabDoesNotIgnorePassiveGrabsBelowWhenReplaying) +{ + XORG_TESTCASE("Client C1 creates window and selects for gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that does trigger the grab.\n" + "Client C3 creates a passive gesture grab below the window." + "Gestures should come only to the C2 client.\n" + "C2 calls XIAllowEvents(ReplayPointer) and the events are replayed to C3.\n" + "C3 calls XIAllowEvents(ReplayPointer) and the events are replayed to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + ::Display *dpy3 = NewClient(); + + Window win2 = CreateWindow(dpy1, None); + Window win3 = CreateWindow(dpy1, win2); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win3, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GrabMatchingGestureBegin(dpy2, win2, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + GrabMatchingGestureBegin(dpy3, win3, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c3_begin, dpy3, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + XIAllowEvents(dpy3, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c3_end, dpy3, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + GestureEnd(); + + ASSERT_EVENT(void, e_c1_end, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); +} + +TEST_P(GesturePassiveGrab, SyncPointerGrabReplaysGestureEventsBeforeEnd) +{ + XORG_TESTCASE("Client C1 creates window and selects for events.\n" + "Client C2 creates a passive pointer grab\n" + "Client C3 creates a passive gesture grab below the window." + "Play button press that triggers the grab. Events should come to C2.\n" + "C2 calls XIAllowEvents(SyncDevice)" + "Gestures should come only to the C2 client.\n" + "C2 calls XIAllowEvents(ReplayDevice) and the events are replayed to C3.\n" + "C3 calls XIAllowEvents(ReplayDevice) and the events are replayed to C1.\n"); + + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + ::Display *dpy3 = NewClient(); + + Window win2 = CreateWindow(dpy1, None); + Window win3 = CreateWindow(dpy1, win2); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win3, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_ButtonRelease, XI_Motion }); + + GrabButton(dpy2, win2, 1, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_ButtonRelease, XI_Motion }); + + GrabMatchingGestureBegin(dpy3, win3, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_ButtonRelease, XI_Motion }); + + TouchpadDev().ButtonDown(1); // freezes device + TouchpadDev().RelMotion(1, 0); + + ASSERT_EVENT(void, e_c2_press, dpy2, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + GestureBegin(); // freezes device + GestureUpdate(); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c3_begin, dpy3, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + XIAllowEvents(dpy3, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c3_end, dpy3, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + GestureEnd(); + + ASSERT_EVENT(void, e_c1_end, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); +} + +TEST_P(GesturePassiveGrab, SyncPointerGrabReplaysGestureEventsAfterEnd) +{ + XORG_TESTCASE("Client C1 creates window and selects for events.\n" + "Client C2 creates a passive pointer grab\n" + "Client C3 creates a passive gesture grab below the window.\n" + "Play button press that triggers the grab.\n" + "Play a gesture sequence before anyone gets a chance to react.\n" + "Events should come to C2.\n" + "C2 calls XIAllowEvents(SyncDevice)" + "Gestures should come only to the C2 client.\n" + "C2 calls XIAllowEvents(ReplayDevice) and the events are replayed to C3.\n" + "C3 calls XIAllowEvents(ReplayDevice) and the events are replayed to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + ::Display *dpy3 = NewClient(); + + Window win2 = CreateWindow(dpy1, None); + Window win3 = CreateWindow(dpy1, win2); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win3, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_ButtonRelease, XI_Motion }); + + GrabButton(dpy2, win2, 1, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_ButtonRelease, XI_Motion }); + + GrabMatchingGestureBegin(dpy3, win3, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_ButtonRelease, XI_Motion }); + + TouchpadDev().ButtonDown(1); // freezes device + TouchpadDev().RelMotion(1, 0); + GestureBegin(); // freezes device second time below + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, e_c2_press, dpy2, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c3_begin, dpy3, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + XIAllowEvents(dpy3, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c3_end, dpy3, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c1_end, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); +} + +TEST_P(GesturePassiveGrab, SyncGrabReplayDeviceReplaysNotGestureBeforeEnd) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that triggers the grab.\n" + "C2 allows events only after begin, update and motion are queued.\n" + "Events should come only to the C2 client.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_Motion, XI_ButtonRelease }); + + GrabMatchingGestureBegin(dpy2, win, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_Motion, XI_ButtonRelease }); + + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_update, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().ButtonDown(1); + + ASSERT_EVENT(void, e_c2_press, dpy2, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c1_press, dpy1, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().ButtonUp(1); + + ASSERT_EVENT(void, e_c1_release, dpy1, GenericEvent, xi2_opcode, XI_ButtonRelease); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureEnd(); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GesturePassiveGrab, SyncGrabReplayDeviceReplaysNotGestureAfterEnd) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that triggers the grab.\n" + "C2 allows events only after begin, update and motion are queued.\n" + "All events should come only to the C2 client.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_Motion, XI_ButtonRelease }); + + GrabMatchingGestureBegin(dpy2, win, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_Motion, XI_ButtonRelease }); + + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_update, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().ButtonDown(1); + GestureEnd(); + + ASSERT_EVENT(void, e_c2_press, dpy2, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIReplayDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c1_press, dpy1, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().ButtonUp(1); + + ASSERT_EVENT(void, e_c1_release, dpy1, GenericEvent, xi2_opcode, XI_ButtonRelease); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GesturePassiveGrab, SyncPointerGrabFreezesOnGestureBeginBeforeGestureStarts) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a passive pointer grab\n" + "Press button to trigger the grab.\n" + "C2 allows events in synchronous mode.\n" + "A gesture is played to freeze the device again.\n" + "All events should come to the C2 client.\n" + "Grab only ends when the button is released\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_Motion, XI_ButtonRelease }); + + GrabButton(dpy2, win, 1, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_Motion, XI_ButtonRelease }); + + TouchpadDev().ButtonDown(1); + TouchpadDev().RelMotion(1, 0); + + ASSERT_EVENT(void, e_c2_press, dpy2, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion1, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureBegin(); // freezes the device + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion2, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_update, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureEnd(); // freezes the device + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().RelMotion(1, 0); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion3, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().ButtonUp(1); // releases the grab + ASSERT_EVENT(void, e_c2_release, dpy2, GenericEvent, xi2_opcode, XI_ButtonRelease); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().RelMotion(1, 0); + ASSERT_EVENT(void, e_c1_motion, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GesturePassiveGrab, SyncPointerGrabFreezesOnGestureBeginAfterGestureStarts) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a passive pointer grab\n" + "Press button to trigger the grab.\n" + "A gesture is played to freeze the device again after events are allowed.\n" + "C2 allows events in synchronous mode.\n" + "All events should come to the C2 client.\n" + "Grab only ends when the button is released\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_Motion, XI_ButtonRelease }); + + GrabButton(dpy2, win, 1, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_ButtonPress, XI_Motion, XI_ButtonRelease }); + + TouchpadDev().ButtonDown(1); // freezes the device + TouchpadDev().RelMotion(1, 0); + GestureBegin(); // freezes the device immediately after XIAllowEventsBelow + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + + ASSERT_EVENT(void, e_c2_press, dpy2, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion1, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion2, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_update, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureEnd(); // freezes the device + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().RelMotion(1, 0); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XISyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion3, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().ButtonUp(1); // releases the grab + ASSERT_EVENT(void, e_c2_release, dpy2, GenericEvent, xi2_opcode, XI_ButtonRelease); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().RelMotion(1, 0); + ASSERT_EVENT(void, e_c1_motion, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GesturePassiveGrab, SyncGrabGetsQueuedEventsAfterAsyncAllowBeforeEnd) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that triggers the grab.\n" + "C2 processes gesture begin only after some events are queued.\n" + "C2 allows async processing of events via AllowEvents.\n" + "All events go to C2 until the passive grab is deactivated.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GrabMatchingGestureBegin(dpy2, win, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_Motion }); + + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIAsyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_update, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureEnd(); + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + TouchpadDev().RelMotion(1, 0); + ASSERT_EVENT(void, e_c1_motion, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GesturePassiveGrab, SyncGrabGetsQueuedEventsAfterAsyncAllowAfterEnd) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that triggers the grab.\n" + "C2 processes gesture begin only after some events are queued.\n" + "C2 allows async processing of events via AllowEvents.\n" + "All events go to C2 until the passive grab is deactivated.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GrabMatchingGestureBegin(dpy2, win, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_Motion }); + + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + GestureEnd(); + TouchpadDev().RelMotion(1, 0); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIAsyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_motion, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_update, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c1_motion, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GesturePassiveGrab, SyncGrabUnfreezesWhenClientExits) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a passive gesture grab\n" + "Play gesture sequence that triggers the grab.\n" + "C2 exits. All events including the replayed ones should go to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GrabMatchingGestureBegin(dpy2, win, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), + XI_Motion }); + + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + GestureEnd(); + TouchpadDev().RelMotion(1, 0); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XCloseDisplay(dpy2); + + ASSERT_EVENT(void, e_c1_motion, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c1_motion2, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_TRUE(NoEventPending(dpy1)); +} + +TEST_P(GesturePassiveGrab, AsyncGrabKeyboardSync) +{ + XORG_TESTCASE("C2 creates an async passive gesture grab (keyboard mode sync) on window\n" + "Generate gesture begin event\n" + "Generate keyboard events\n" + "Generate gesture end event\n" + "Allow keyboard events.\n" + "Make sure all events arrive to client C2.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_KEYBOARD_ID, win, + { XI_KeyPress, XI_KeyRelease }); + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + SelectXI2Events(dpy2, VIRTUAL_CORE_KEYBOARD_ID, win, { XI_KeyPress, XI_KeyRelease }); + GrabMatchingGestureBegin(dpy2, win, GrabModeAsync, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + KeyboardDev().KeyDown(KEY_A); + KeyboardDev().KeyUp(KEY_A); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureEnd(); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c2_press, dpy2, GenericEvent, xi2_opcode, XI_KeyPress); + ASSERT_EVENT(void, e_c2_release, dpy2, GenericEvent, xi2_opcode, XI_KeyRelease); + ASSERT_EVENT(void, e_c1_press, dpy1, GenericEvent, xi2_opcode, XI_KeyPress); + ASSERT_EVENT(void, e_c1_release, dpy1, GenericEvent, xi2_opcode, XI_KeyRelease); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +INSTANTIATE_TEST_CASE_P(, GesturePassiveGrab, + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin)); + +class GestureActiveGrab : public GestureTypesTest {}; + +TEST_P(GestureActiveGrab, AsyncGrabWhenGestureAlreadyBegun) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "A gesture is begun.\n" + "Client C2 creates an active async gesture grab\n" + "Play events.\n" + "Events for existing gesture should go to C1, events for new gesture should go to C2.\n" + "C2 deactivates gesture grab\n" + "All events go to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy2); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GestureBegin(); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, win, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureUpdate(); + TouchpadDev().RelMotion(1, 0); + GestureEnd(); + + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c2_motion1, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c1_end, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureBegin(); + GestureUpdate(); + TouchpadDev().RelMotion(1, 0); + GestureEnd(); + + ASSERT_EVENT(void, e_c2_begin2, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c2_update2, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c2_motion2, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_end2, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIUngrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, CurrentTime); + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, e_c1_begin2, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_update2, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c1_end2, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GestureActiveGrab, AsyncGrabSameClientAfterGestureBegunNoGesturesInGrabMaskUngrabBeforeEnd) +{ + XORG_TESTCASE("Clients C1 and C2 selects for gestures.\n" + "The selection of C1 takes precedence over the selection of C2.\n" + "A gesture begins.\n" + "Client C1 creates an active async grab without gesture events in the mask\n" + "A gesture end event is immediately sent to client C1." + "Gesture is updated, but all events related to it should be discarded.\n" + "C2 does not get any events.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window root = DefaultRootWindow(dpy2); + Window win = CreateWindow(dpy2, root); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + SelectXI2Events(dpy2, VIRTUAL_CORE_POINTER_ID, root, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + + EXPECT_EVENT(XIDeviceEvent, e_c1_begin1, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + EXPECT_EVENT(XIDeviceEvent, e_c1_update1, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + // grab event type that is not related to gestures + EventMaskBuilder mask{VIRTUAL_CORE_POINTER_ID, { XI_Enter }}; + ASSERT_EQ(Success, XIGrabDevice(dpy1, VIRTUAL_CORE_POINTER_ID, win, CurrentTime, None, + GrabModeAsync, GrabModeAsync, False, mask.GetMask())); + XSync(dpy1, False); + + EXPECT_EVENT(XIDeviceEvent, e_c1_end1, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + + GestureUpdate(); + GestureUpdate(); + XIUngrabDevice(dpy1, VIRTUAL_CORE_POINTER_ID, CurrentTime); + GestureUpdate(); + GestureEnd(); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GestureActiveGrab, AsyncGrabSameClientAfterGestureBegunNoGesturesInGrabMaskUngrabAfterEnd) +{ + XORG_TESTCASE("Clients C1 and C2 selects for gestures.\n" + "The selection of C1 takes precedence over the selection of C2.\n" + "A gesture begins.\n" + "Client C1 creates an active async grab without gesture events in the mask\n" + "A gesture end event is immediately sent to client C1." + "Gestures is updated, but all events related to it should be discarded.\n" + "C2 does not get any events.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window root = DefaultRootWindow(dpy2); + Window win = CreateWindow(dpy2, root); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + SelectXI2Events(dpy2, VIRTUAL_CORE_POINTER_ID, root, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + GestureUpdate(); + + EXPECT_EVENT(XIDeviceEvent, e_c1_begin1, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + EXPECT_EVENT(XIDeviceEvent, e_c1_update1, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + // grab event type that is not related to gestures + EventMaskBuilder mask{VIRTUAL_CORE_POINTER_ID, { XI_Enter }}; + ASSERT_EQ(Success, XIGrabDevice(dpy1, VIRTUAL_CORE_POINTER_ID, win, CurrentTime, None, + GrabModeAsync, GrabModeAsync, False, mask.GetMask())); + XSync(dpy1, False); + + EXPECT_EVENT(XIDeviceEvent, e_c1_end1, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + + GestureUpdate(); + GestureUpdate(); + GestureEnd(); + XIUngrabDevice(dpy1, VIRTUAL_CORE_POINTER_ID, CurrentTime); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GestureActiveGrab, SyncGrabWhenGestureAlreadyBegun) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "A gesture is begun.\n" + "Client C2 creates an active sync gesture grab\n" + "Play events. C2 allows events.\n" + "Events for existing gesture should go to C1, events for new gesture should go to C2.\n" + "C2 deactivates gesture grab\n" + "All events go to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy2); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GestureBegin(); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, win, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureUpdate(); + TouchpadDev().RelMotion(1, 0); + GestureEnd(); + GestureBegin(); + GestureUpdate(); + TouchpadDev().RelMotion(1, 0); + GestureEnd(); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIAsyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c2_motion1, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c1_end, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c2_begin2, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c2_update2, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c2_motion2, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_end2, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIUngrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, CurrentTime); + + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, e_c1_begin2, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_update2, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c1_end2, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GestureActiveGrab, SyncGrabGetsQueuedEventsAfterAsyncAllow) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates an active gesture grab\n" + "Play events.\n" + "C2 processes gesture begin only after some events are queued.\n" + "C2 allows async processing of events via AllowEvents.\n" + "All events go to C2 until the grab is deactivated.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, win, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_POINTER_ID, XIAsyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c2_motion, dpy2, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c2_update, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureEnd(); + GestureBegin(); + GestureUpdate(); + GestureEnd(); + GestureBegin(); + + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c2_begin2, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c2_update2, dpy2, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c2_end2, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_EVENT(void, e_c2_begin3, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIUngrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, CurrentTime); + ASSERT_EVENT(void, e_c2_end3, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureEnd(); + GestureBegin(); + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, e_c1_begin2, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_update2, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c1_end2, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GestureActiveGrab, SyncGrabGetsQueuedEventsAfterUngrabDuringFreezeBeforeEnd) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates an active gesture grab\n" + "Play events.\n" + "C2 deactivates gesture grab\n" + "All events go to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy2); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, win, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + TouchpadDev().RelMotion(1, 0); + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIUngrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, CurrentTime); + + ASSERT_EVENT(void, e_c1_motion1, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_motion2, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + GestureUpdate(); + GestureEnd(); + + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c1_end2, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GestureActiveGrab, SyncGrabGetsQueuedEventsAfterUngrabDuringFreezeAfterEnd) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates an active gesture grab\n" + "Play events.\n" + "C2 deactivates gesture grab\n" + "All events go to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy2); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, win, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + TouchpadDev().RelMotion(1, 0); + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + GestureEnd(); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIUngrabDevice(dpy2, VIRTUAL_CORE_POINTER_ID, CurrentTime); + + ASSERT_EVENT(void, e_c1_motion1, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_motion2, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c1_end2, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +TEST_P(GestureActiveGrab, SyncGrabUnfreezesWhenClientExits) +{ + XORG_TESTCASE("Client C1 selects for gestures.\n" + "Client C2 creates a active gesture grab\n" + "Play gesture sequence.\n" + "C2 exits. All events including the replayed ones should go to C1.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + GrabXI2Device(dpy2, VIRTUAL_CORE_POINTER_ID, win, GrabModeSync, GrabModeAsync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd(), XI_Motion }); + + TouchpadDev().RelMotion(1, 0); + GestureBegin(); + TouchpadDev().RelMotion(1, 0); + GestureUpdate(); + GestureEnd(); + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XCloseDisplay(dpy2); + + ASSERT_EVENT(void, e_c1_motion1, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c1_begin, dpy1, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c1_motion2, dpy1, GenericEvent, xi2_opcode, XI_Motion); + ASSERT_EVENT(void, e_c1_update, dpy1, GenericEvent, xi2_opcode, GetXIGestureUpdate()); + ASSERT_EVENT(void, e_c1_end2, dpy1, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); +} + +TEST_P(GestureActiveGrab, AsyncGrabKeyboardSync) +{ + XORG_TESTCASE("C2 creates an async gesture grab (keyboard mode sync) on window\n" + "Generate gesture begin event\n" + "Generate keyboard events\n" + "Generate gesture end event\n" + "Allow keyboard events.\n" + "Make sure all events arrive to client C2.\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = NewClient(); + + Window win = DefaultRootWindow(dpy1); + + SelectXI2Events(dpy1, VIRTUAL_CORE_KEYBOARD_ID, win, + { XI_KeyPress, XI_KeyRelease }); + SelectXI2Events(dpy1, VIRTUAL_CORE_POINTER_ID, win, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + SelectXI2Events(dpy2, VIRTUAL_CORE_KEYBOARD_ID, win, { XI_KeyPress, XI_KeyRelease }); + GrabXI2Device(dpy2, VIRTUAL_CORE_POINTER_ID, win, GrabModeAsync, GrabModeSync, + { GetXIGestureBegin(), GetXIGestureUpdate(), GetXIGestureEnd() }); + + GestureBegin(); + KeyboardDev().KeyDown(KEY_A); + KeyboardDev().KeyUp(KEY_A); + GestureEnd(); + + ASSERT_EVENT(void, e_c2_begin, dpy2, GenericEvent, xi2_opcode, GetXIGestureBegin()); + ASSERT_EVENT(void, e_c2_end, dpy2, GenericEvent, xi2_opcode, GetXIGestureEnd()); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + + XIAllowEvents(dpy2, VIRTUAL_CORE_KEYBOARD_ID, XIAsyncDevice, CurrentTime); + + ASSERT_EVENT(void, e_c1_press, dpy1, GenericEvent, xi2_opcode, XI_KeyPress); + ASSERT_EVENT(void, e_c1_release, dpy1, GenericEvent, xi2_opcode, XI_KeyRelease); + ASSERT_EVENT(void, e_c2_press, dpy2, GenericEvent, xi2_opcode, XI_KeyPress); + ASSERT_EVENT(void, e_c2_release, dpy2, GenericEvent, xi2_opcode, XI_KeyRelease); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +INSTANTIATE_TEST_CASE_P(, GestureActiveGrab, + ::testing::Values(XI_GesturePinchBegin, XI_GestureSwipeBegin)); + +#endif /* HAVE_XI24 */ |