summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPovilas Kanapickas <povilas@radix.lt>2021-05-30 18:24:01 +0300
committerPovilas Kanapickas <povilas@radix.lt>2021-05-30 18:34:36 +0300
commit056917e10ed6ace112a1fe98cd0f74a27ad5aad0 (patch)
treeb53c9c0addad2a393dc11b801cab14b0c8c93340
parent2d8cc468335383243aa9fb2cea3b8ae3bb2c66c1 (diff)
Implement tests for gesture events
-rw-r--r--meson.build8
-rw-r--r--tests/server/gestures-common.h268
-rw-r--r--tests/server/gestures.cpp688
-rw-r--r--tests/server/grab-gestures.cpp1491
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 */