diff options
author | Peter Hutterer <peter.hutterer@who-t.net> | 2013-07-18 15:25:26 +1000 |
---|---|---|
committer | Peter Hutterer <peter.hutterer@who-t.net> | 2013-07-18 15:25:26 +1000 |
commit | 26313a21eb124e0d4a849287a6480be26e80f1a0 (patch) | |
tree | cd01169a435fdc592e40b9c3443145d062cb14d4 | |
parent | bf7fc9521db715d1994c942c04fd6d1cc47a6335 (diff) | |
parent | 32f47024fc2e3d61c4bd483935381a301e4ed32c (diff) |
Merge branch 'touch-grab-race-condition-56578'
Conflicts:
tests/server/grab.cpp
-rw-r--r-- | tests/common/xit-event.h | 4 | ||||
-rw-r--r-- | tests/server/grab.cpp | 869 | ||||
-rw-r--r-- | tests/server/touch.cpp | 33 |
3 files changed, 842 insertions, 64 deletions
diff --git a/tests/common/xit-event.h b/tests/common/xit-event.h index b9e75a4..e9f641c 100644 --- a/tests/common/xit-event.h +++ b/tests/common/xit-event.h @@ -45,6 +45,10 @@ _type* _name = _name ## _xit_event.ev; \ ASSERT_TRUE(_name); +#define EXPECT_EVENT(_type, _name, ...) \ + XITEvent<_type> _name ## _xit_event(__VA_ARGS__); \ + _type* _name = _name ## _xit_event.ev; \ + EXPECT_TRUE(_name); /** * Template class to work around the Xlib cookie API. * Create a single EW object that pulls down then next event from the wire, diff --git a/tests/server/grab.cpp b/tests/server/grab.cpp index ba08b0b..75b26a3 100644 --- a/tests/server/grab.cpp +++ b/tests/server/grab.cpp @@ -339,11 +339,114 @@ TEST_F(PointerGrabTest, ImplicitGrabToActiveGrabDeactivated) ASSERT_EQ(server.GetState(), xorg::testing::Process::RUNNING); } +TEST_F(PointerGrabTest, AsyncPassiveGrabPressRelease) +{ + XORG_TESTCASE("Client 2 creates window with button mask.\n" + "Client 1 async passive button grab on that window.\n" + "Double-click.\n" + "Client 1 should get press/release\n" + "Client 2 gets nothing\n" + "Ungrab button, replay, now C2 gets the events\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = XOpenDisplay(server.GetDisplayString().c_str()); + XSynchronize(dpy1, True); + XSynchronize(dpy2, True); + + Window win = CreateWindow(dpy2, None); + XSelectInput(dpy2, win, ButtonPressMask|ButtonReleaseMask); + + XGrabButton(dpy1, AnyButton, XIAnyModifier, win, False, + ButtonPressMask|ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, None, None); + + dev->PlayOne(EV_KEY, BTN_LEFT, 1, true); + dev->PlayOne(EV_KEY, BTN_LEFT, 0, true); + dev->PlayOne(EV_KEY, BTN_LEFT, 1, true); + dev->PlayOne(EV_KEY, BTN_LEFT, 0, true); + + for (int i = 0; i < 2; i++) { + ASSERT_EVENT(XEvent, press, dpy1, ButtonPress); + ASSERT_EVENT(XEvent, release, dpy1, ButtonRelease); + } + + XUngrabButton(dpy1, AnyButton, XIAnyModifier, win); + dev->PlayOne(EV_KEY, BTN_LEFT, 1, true); + dev->PlayOne(EV_KEY, BTN_LEFT, 0, true); + dev->PlayOne(EV_KEY, BTN_LEFT, 1, true); + dev->PlayOne(EV_KEY, BTN_LEFT, 0, true); + + for (int i = 0; i < 2; i++) { + ASSERT_EVENT(XEvent, press, dpy2, ButtonPress); + ASSERT_EVENT(XEvent, release, dpy2, ButtonRelease); + } + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +/** + * @tparam AsyncPointer, SyncPointer, ReplayPointer + */ +class PointerGrabTestAllowEvents : public PointerGrabTest, + public ::testing::WithParamInterface<int> { +}; + +TEST_P(PointerGrabTestAllowEvents, AllowEventsDoubleClick) +{ + int mode = GetParam(); + std::string strmode; + switch(mode) { + case ReplayPointer: strmode = "ReplayPointer"; break; + case AsyncPointer: strmode = "AsyncPointer"; break; + case SyncPointer: strmode = "SyncPointer"; break; + } + XORG_TESTCASE("Client 2 creates window with button mask.\n" + "Client 1 sync passive button grab on that window.\n" + "Double-click.\n" + "Client1: AllowEvents(" + strmode + ")\n" + "Client2: events for Replay, not for Sync/Async\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = XOpenDisplay(server.GetDisplayString().c_str()); + XSynchronize(dpy1, True); + XSynchronize(dpy2, True); + + Window win = CreateWindow(dpy2, None); + XSelectInput(dpy2, win, ButtonPressMask|ButtonReleaseMask); + + XGrabButton(dpy1, AnyButton, XIAnyModifier, win, False, + ButtonPressMask|ButtonReleaseMask, + GrabModeSync, GrabModeAsync, None, None); + + dev->PlayOne(EV_KEY, BTN_LEFT, 1, true); + dev->PlayOne(EV_KEY, BTN_LEFT, 0, true); + dev->PlayOne(EV_KEY, BTN_LEFT, 1, true); + dev->PlayOne(EV_KEY, BTN_LEFT, 0, true); + + for (int i = 0; i < 2; i++) { + ASSERT_EVENT(XEvent, press, dpy1, ButtonPress); + XAllowEvents(dpy1, mode, CurrentTime); + if (mode == ReplayPointer) { + ASSERT_EVENT(XEvent, press, dpy2, ButtonPress); + ASSERT_EVENT(XEvent, release, dpy2, ButtonRelease); + } else { + ASSERT_EVENT(XEvent, release, dpy1, ButtonRelease); + } + } + + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); +} + +INSTANTIATE_TEST_CASE_P(, PointerGrabTestAllowEvents, + ::testing::Values(AsyncPointer, SyncPointer, ReplayPointer)); enum GrabType { GRABTYPE_CORE, GRABTYPE_XI1, GRABTYPE_XI2, + GRABTYPE_XI2TOUCH, }; static std::string grabtype_enum_to_string(enum GrabType gt) { @@ -351,6 +454,7 @@ static std::string grabtype_enum_to_string(enum GrabType gt) { case GRABTYPE_CORE: return "GRABTYPE_CORE"; break; case GRABTYPE_XI1: return "GRABTYPE_XI1"; break; case GRABTYPE_XI2: return "GRABTYPE_XI2"; break; + case GRABTYPE_XI2TOUCH: return "GRABTYPE_XI2TOUCH"; break; } ADD_FAILURE() << "BUG: shouldn't get here"; @@ -502,6 +606,10 @@ TEST_P(PointerGrabTypeTest, DifferentGrabTypesProhibited) rc = XGrabDevice(dpy, device, root, False, 0, NULL, GrabModeAsync, GrabModeAsync, CurrentTime); break; + default: + FAIL(); + break; + } ASSERT_EQ(rc, GrabSuccess); @@ -526,6 +634,9 @@ TEST_P(PointerGrabTypeTest, DifferentGrabTypesProhibited) rc = XGrabDevice(dpy, device, root, False, 0, NULL, GrabModeAsync, GrabModeAsync, CurrentTime); break; + default: + FAIL(); + break; } ASSERT_EQ(rc, expected_status); @@ -571,24 +682,40 @@ public: config.WriteConfig(); } - virtual Window CreateWindow(::Display *dpy, Window parent) + /** + * Return a new synchronized client given our default server connection. + */ + virtual ::Display* NewClient(int maj = 2, int min = 2) { + ::Display *d = XOpenDisplay(server.GetDisplayString().c_str()); + if (!d) + ADD_FAILURE() << "Failed to open display for new client.\n"; + XSynchronize(d, True); + int major = maj, minor = min; + if (XIQueryVersion(d, &major, &minor) != Success) + ADD_FAILURE() << "XIQueryVersion failed on new client.\n"; + return d; + } + + virtual void GrabDevice(::Display *dpy, int deviceid, Window win, bool ownership = false) { - Window win; - win = XCreateSimpleWindow(dpy, parent, 0, 0, - DisplayWidth(dpy, DefaultScreen(dpy)), - DisplayHeight(dpy, DefaultScreen(dpy)), - 0, 0, 0); - XSelectInput(dpy, win, StructureNotifyMask); - XMapWindow(dpy, win); - if (xorg::testing::XServer::WaitForEventOfType(dpy, MapNotify, -1, -1)) { - XEvent ev; - XNextEvent(dpy, &ev); - } else { - ADD_FAILURE() << "Failed waiting for Exposure"; - } - XSelectInput(dpy, win, 0); + XIEventMask mask; + mask.deviceid = deviceid; + mask.mask_len = XIMaskLen(XI_TouchOwnership); + mask.mask = new unsigned char[mask.mask_len](); + + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchEnd); + XISetMask(mask.mask, XI_TouchUpdate); + + if (ownership) + XISetMask(mask.mask, XI_TouchOwnership); + + ASSERT_EQ(Success, XIGrabDevice(dpy, deviceid, + win, CurrentTime, None, + GrabModeAsync, GrabModeAsync, + False, &mask)); + delete[] mask.mask; XSync(dpy, False); - return win; } virtual void GrabPointer(::Display *dpy, int deviceid, Window win) @@ -610,21 +737,6 @@ public: XSync(dpy, False); } - /** - * Return a new synchronized client given our default server connection. - * Client is initialised for XI 2.2 - */ - virtual ::Display* NewClient(void) { - ::Display *d = XOpenDisplay(server.GetDisplayString().c_str()); - if (!d) - ADD_FAILURE() << "Failed to open display for new client.\n"; - XSynchronize(d, True); - int major = 2, minor = 2; - if (XIQueryVersion(d, &major, &minor) != Success) - ADD_FAILURE() << "XIQueryVersion failed on new client.\n"; - return d; - } - virtual void TouchBegin(int x, int y) { dev->PlayOne(EV_KEY, BTN_TOUCH, 1); TouchUpdate(x, y); @@ -692,12 +804,667 @@ TEST_F(TouchGrabTest, ActivePointerGrabOverPointerSelection) XCloseDisplay(dpy2); } +TEST_F(TouchGrabTest, PassiveTouchGrabPassedToTouchClient) +{ + XORG_TESTCASE("Client 1: register for passive touch grab on roow window\n" + "Client 2: register for touch events on window\n" + "Trigger touch begin/end\n" + "Client 1: reject after TouchEnd\n" + "Client 2: verify touch event is received\n" + "Verify pointer has no button set\n"); + + int major = 2, minor = 2; + ::Display *dpy1 = Display(); + ::Display *dpy2 = XOpenDisplay(server.GetDisplayString().c_str()); + XSynchronize(dpy2, True); + XIQueryVersion(dpy2, &major, &minor); + + Window root = DefaultRootWindow(dpy1); + Window win = CreateWindow(dpy2, None); + + XIEventMask mask; + mask.deviceid = VIRTUAL_CORE_POINTER_ID; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = new unsigned char[mask.mask_len](); + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + XIGrabModifiers mods = {}; + mods.modifiers = XIAnyModifier; + ASSERT_EQ(Success, XIGrabTouchBegin(dpy1, VIRTUAL_CORE_POINTER_ID, + root, False, &mask, 1, &mods)); + XISelectEvents(dpy2, win, &mask, 1); + delete[] mask.mask; + + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_begin.events"); + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_end.events"); + + ASSERT_EVENT(XIDeviceEvent, tbegin, dpy1, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_EVENT(XIDeviceEvent, tend, dpy1, GenericEvent, xi2_opcode, XI_TouchEnd); + XIAllowTouchEvents(dpy1, tbegin->deviceid, tbegin->detail, root, XIRejectTouch); + + ASSERT_EVENT(XEvent, tbegin2, dpy2, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_EVENT(XEvent, tend2, dpy2, GenericEvent, xi2_opcode, XI_TouchEnd); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy1)); + + /* use a third client for checking pointer state, since a XI2.2 client + * won't see emulated touch state */ + ::Display *dpy3 = XOpenDisplay(server.GetDisplayString().c_str()); + XSynchronize(dpy3, True); + Window child; + double win_x, win_y; + double root_x, root_y; + XIButtonState buttons; + XIModifierState modifiers; + XIGroupState group; + XIQueryPointer(dpy3, VIRTUAL_CORE_POINTER_ID, + root, &root, &child, + &root_x, &root_y, + &win_x, &win_y, + &buttons, &modifiers, &group); + + ASSERT_GE(buttons.mask_len, 1); + for (int i = 0; i < buttons.mask_len * 8; i++) + ASSERT_FALSE(XIMaskIsSet(buttons.mask, i)); +} + +TEST_F(TouchGrabTest, TouchGrabPassedToCoreGrab) +{ + XORG_TESTCASE("Client 1: Register for passive touch grab on root window\n" + "Client 2: Register for passive button grab on client window\n" + "Trigger touch begin/end\n" + "Client 1: reject after TouchEnd\n" + "Client 2: verify button event is received\n" + "Repeat 10 times\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = XOpenDisplay(server.GetDisplayString().c_str()); + XSynchronize(dpy2, True); + + Window root = DefaultRootWindow(dpy1); + Window win = CreateWindow(dpy2, None); + + XIEventMask mask; + mask.deviceid = VIRTUAL_CORE_POINTER_ID; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = new unsigned char[mask.mask_len](); + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + XIGrabModifiers mods = {}; + mods.modifiers = XIAnyModifier; + ASSERT_EQ(Success, XIGrabTouchBegin(dpy1, VIRTUAL_CORE_POINTER_ID, + root, False, &mask, 1, &mods)); + delete[] mask.mask; + + XGrabButton(dpy2, AnyButton, XIAnyModifier, win, False, + ButtonPressMask|ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, None, None); + + for (int i = 0; i < 10; i++) { + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_begin.events"); + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_end.events"); + + ASSERT_EVENT(XIDeviceEvent, tbegin, dpy1, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_EVENT(XIDeviceEvent, tend, dpy1, GenericEvent, xi2_opcode, XI_TouchEnd); + XIAllowTouchEvents(dpy1, tbegin->deviceid, tbegin->detail, root, XIRejectTouch); + + ASSERT_EVENT(XEvent, press, dpy2, ButtonPress); + ASSERT_EVENT(XEvent, release, dpy2, ButtonRelease); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy1)); + } +} + +TEST_F(TouchGrabTest, TouchGrabPassedToTouch) +{ + XORG_TESTCASE("Client C1 has touch grab on root window.\n" + "Client c2 has touch grab on window.\n" + "Touch begin/end\n" + "Reject from C1\n" + "Verify C2 gets all events\n"); + + int major = 2, minor = 2; + ::Display *dpy1 = Display(); + ::Display *dpy2 = XOpenDisplay(server.GetDisplayString().c_str()); + XIQueryVersion(dpy2, &major, &minor); + XSynchronize(dpy2, True); + + Window root = DefaultRootWindow(dpy1); + Window win = CreateWindow(dpy2, None); + + XIEventMask mask; + mask.deviceid = VIRTUAL_CORE_POINTER_ID; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = new unsigned char[mask.mask_len](); + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + XIGrabModifiers mods = {}; + mods.modifiers = XIAnyModifier; + ASSERT_EQ(Success, XIGrabTouchBegin(dpy1, VIRTUAL_CORE_POINTER_ID, + root, False, &mask, 1, &mods)); + ASSERT_EQ(Success, XIGrabTouchBegin(dpy2, VIRTUAL_CORE_POINTER_ID, + win, False, &mask, 1, &mods)); + delete[] mask.mask; + + for (int i = 0; i < 10; i++) { + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_begin.events"); + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_end.events"); + + ASSERT_EVENT(XIDeviceEvent, tbegin, dpy1, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_EVENT(XIDeviceEvent, tend, dpy1, GenericEvent, xi2_opcode, XI_TouchEnd); + XIAllowTouchEvents(dpy1, tbegin->deviceid, tbegin->detail, root, XIRejectTouch); + ASSERT_TRUE(NoEventPending(dpy1)); + + ASSERT_EVENT(XIDeviceEvent, tbegin2, dpy2, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_EVENT(XIDeviceEvent, tend2, dpy2, GenericEvent, xi2_opcode, XI_TouchEnd); + XIAllowTouchEvents(dpy2, tend2->deviceid, tend2->detail, win, XIAcceptTouch); + ASSERT_TRUE(NoEventPending(dpy2)); + } +} + +TEST_F(TouchGrabTest, TouchGrabPassedToTouchEarlyAccept) +{ + XORG_TESTCASE("Client C1 has touch grab on root window.\n" + "Client C2 has touch grab with ownership on window.\n" + "Client C3 has touch grab with ownership on window.\n" + "Touch begin\n" + "Accept touch from C2.\n" + "Expect TouchEnd for C3.\n" + "Reject from C1\n" + "Expect TouchEnd for C1\n" + "Expect TouchOwnerhip for C2\n" + "Touch end\n" + "Expect TouchEnd for C2\n"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = XOpenDisplay(server.GetDisplayString().c_str()); + ::Display *dpy3 = XOpenDisplay(server.GetDisplayString().c_str()); + XSynchronize(dpy1, True); + XSynchronize(dpy2, True); + XSynchronize(dpy3, True); + + int major = 2, minor = 3; + XIQueryVersion(dpy2, &major, &minor); + XIQueryVersion(dpy3, &major, &minor); + + + Window root = DefaultRootWindow(dpy1); + Window win = CreateWindow(dpy3, None); + Window subwin = CreateWindow(dpy3, win); + + XIEventMask mask; + mask.deviceid = VIRTUAL_CORE_POINTER_ID; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = new unsigned char[mask.mask_len](); + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + XIGrabModifiers mods = {}; + mods.modifiers = XIAnyModifier; + ASSERT_EQ(Success, XIGrabTouchBegin(dpy1, VIRTUAL_CORE_POINTER_ID, + root, False, &mask, 1, &mods)); + + XISetMask(mask.mask, XI_TouchOwnership); + + ASSERT_EQ(Success, XIGrabTouchBegin(dpy2, VIRTUAL_CORE_POINTER_ID, + win, False, &mask, 1, &mods)); + ASSERT_EQ(Success, XIGrabTouchBegin(dpy3, VIRTUAL_CORE_POINTER_ID, + subwin, False, &mask, 1, &mods)); + delete[] mask.mask; + + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_begin.events"); + + /* C1 is owner, but all three get begin event */ + ASSERT_EVENT(XIDeviceEvent, tbegin1, dpy1, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_EVENT(XIDeviceEvent, tbegin2, dpy2, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_EVENT(XIDeviceEvent, tbegin3, dpy3, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_TRUE(NoEventPending(dpy1)); + ASSERT_TRUE(NoEventPending(dpy2)); + ASSERT_TRUE(NoEventPending(dpy3)); + + /* Accept from C2, changes nothing on the wire yet */ + XIAllowTouchEvents(dpy2, tbegin2->deviceid, tbegin2->detail, win, XIAcceptTouch); + + /* Reject from C1, finishes for C1, owner is C2 */ + XIAllowTouchEvents(dpy1, tbegin1->deviceid, tbegin1->detail, root, XIRejectTouch); + ASSERT_EVENT(XIDeviceEvent, tend1, dpy1, GenericEvent, xi2_opcode, XI_TouchEnd); + ASSERT_EVENT(XIDeviceEvent, towner2, dpy2, GenericEvent, xi2_opcode, XI_TouchOwnership); + + /* Physicall end touch */ + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_end.events"); + ASSERT_EVENT(XIDeviceEvent, tend2, dpy2, GenericEvent, xi2_opcode, XI_TouchEnd); + ASSERT_TRUE(NoEventPending(dpy2)); + + ASSERT_EVENT(XIDeviceEvent, tend3, dpy3, GenericEvent, xi2_opcode, XI_TouchEnd); + ASSERT_TRUE(NoEventPending(dpy3)); +} + +class TouchGrabTestOnLegacyClient : public TouchGrabTest { + virtual void SetUp() { + SetDevice("tablets/N-Trig-MultiTouch.desc"); + + xi2_major_minimum = 2; + xi2_minor_minimum = 0; + + XITServerInputTest::SetUp(); + } +}; + +TEST_F(TouchGrabTestOnLegacyClient, ActivePointerGrabUngrabDuringTouch) +{ + XORG_TESTCASE("Create window\n" + "Select for button events on XIAllMasterDevices.\n" + "Touch begin\n" + "Activate async active grab on the window\n" + "Touch end, second touch begin\n" + "Ungrab device\n" + "https://bugs.freedesktop.org/show_bug.cgi?id=66720"); + + ::Display *dpy = Display(); /* XI 2.0 client */ + + int deviceid; + FindInputDeviceByName(dpy, "--device--", &deviceid); + + Window win = CreateWindow(dpy, DefaultRootWindow(dpy)); + + SelectXI2Events(dpy, XIAllMasterDevices, win, + XI_ButtonPress, + XI_ButtonRelease, + XI_Enter, + XI_Leave, + XI_Motion, + -1); + + TouchBegin(200, 200); + TouchUpdate(202, 202); + + EXPECT_EVENT(XIDeviceEvent, m1, dpy, GenericEvent, xi2_opcode, XI_Motion); + EXPECT_EVENT(XIDeviceEvent, p1, dpy, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_TRUE(NoEventPending(dpy)); + + GrabPointer(dpy, VIRTUAL_CORE_POINTER_ID, win); + TouchEnd(); + + EXPECT_EVENT(XIDeviceEvent, r1, dpy, GenericEvent, xi2_opcode, XI_ButtonRelease); + + TouchBegin(200, 200); + TouchUpdate(202, 202); + EXPECT_EVENT(XIDeviceEvent, m2, dpy, GenericEvent, xi2_opcode, XI_Motion); + EXPECT_EVENT(XIDeviceEvent, p2, dpy, GenericEvent, xi2_opcode, XI_ButtonPress); + XIUngrabDevice(dpy, VIRTUAL_CORE_POINTER_ID, CurrentTime); + + TouchEnd(); + + ASSERT_TRUE(NoEventPending(dpy)); +} + + +TEST_F(TouchGrabTestOnLegacyClient, ActivePointerGrabOverPointerSelection) +{ + XORG_TESTCASE("Create window\n" + "Select for button press on slave device and XIAllMasterDevices.\n" + "Async active grab on the window\n" + "Touch begin/end over the window.\n" + "Expect button events on slave\n" + "Expect button events on master\n"); + + ::Display *dpy = Display(); /* XI 2.0 client */ + + int deviceid; + FindInputDeviceByName(dpy, "--device--", &deviceid); + + Window win = CreateWindow(dpy, DefaultRootWindow(dpy)); + + XIEventMask mask[2]; + mask[0].deviceid = deviceid; + mask[0].mask_len = XIMaskLen(XI_TouchOwnership); + mask[0].mask = new unsigned char[mask[0].mask_len](); + + XISetMask(mask[0].mask, XI_ButtonPress); + XISetMask(mask[0].mask, XI_ButtonRelease); + XISetMask(mask[0].mask, XI_Enter); + XISetMask(mask[0].mask, XI_Leave); + XISetMask(mask[0].mask, XI_Motion); + + mask[1].deviceid = XIAllMasterDevices; + mask[1].mask_len = XIMaskLen(XI_TouchOwnership); + mask[1].mask = new unsigned char[mask[1].mask_len](); + + XISetMask(mask[1].mask, XI_ButtonPress); + XISetMask(mask[1].mask, XI_ButtonRelease); + XISetMask(mask[1].mask, XI_Enter); + XISetMask(mask[1].mask, XI_Leave); + XISetMask(mask[1].mask, XI_Motion); + + XISelectEvents(dpy, win, mask, 2); + GrabPointer(dpy, VIRTUAL_CORE_POINTER_ID, win); + + TouchBegin(200, 200); + TouchUpdate(202, 202); + TouchEnd(); + + EXPECT_EVENT(XIDeviceEvent, press_dev_1, dpy, GenericEvent, xi2_opcode, XI_ButtonPress); + EXPECT_EQ(press_dev_1->deviceid, deviceid); + EXPECT_EVENT(XIDeviceEvent, motion, dpy, GenericEvent, xi2_opcode, XI_Motion); + EXPECT_EQ(motion->deviceid, VIRTUAL_CORE_POINTER_ID); + + EXPECT_EVENT(XIDeviceEvent, press, dpy, GenericEvent, xi2_opcode, XI_ButtonPress); + EXPECT_EQ(press->deviceid, VIRTUAL_CORE_POINTER_ID); + EXPECT_EVENT(XIDeviceEvent, release_dev_1, dpy, GenericEvent, xi2_opcode, XI_ButtonRelease); + EXPECT_EQ(release_dev_1->deviceid, deviceid); + EXPECT_EVENT(XIDeviceEvent, release, dpy, GenericEvent, xi2_opcode, XI_ButtonRelease); + EXPECT_EQ(release->deviceid, VIRTUAL_CORE_POINTER_ID); + + ASSERT_TRUE(NoEventPending(dpy)); +} + + +class TouchUngrabTest : public TouchGrabTest, + public ::testing::WithParamInterface<enum GrabType> +{ + virtual void SetUp() { + SetDevice("tablets/N-Trig-MultiTouch.desc"); + + xi2_major_minimum = 2; + switch(GetParam()) { + case GRABTYPE_XI2TOUCH: + xi2_minor_minimum = 2; + break; + default: + xi2_minor_minimum = 0; + break; + } + + XITServerInputTest::SetUp(); + } +}; + +TEST_P(TouchUngrabTest, UngrabButtonDuringTouch) +{ + XORG_TESTCASE("Grab a button\n" + "Trigger pointer-emulated button grab\n" + "Ungrab the button\n" + "Continue with touch events\n"); + enum GrabType grab_type = GetParam(); + SCOPED_TRACE("" + grabtype_enum_to_string(grab_type)); + + ::Display *dpy = Display(); + + XIEventMask mask; + mask.mask_len = XIMaskLen(XI_LASTEVENT); + mask.deviceid = VIRTUAL_CORE_POINTER_ID; + mask.mask = new unsigned char[mask.mask_len](); + if (grab_type == GRABTYPE_XI2) { + XISetMask(mask.mask, XI_ButtonPress); + XISetMask(mask.mask, XI_ButtonRelease); + XISetMask(mask.mask, XI_Motion); + } else if (grab_type == GRABTYPE_XI2TOUCH) { + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + } + + XIGrabModifiers modifiers = {}; + modifiers.modifiers = XIAnyModifier; + + switch (grab_type) { + case GRABTYPE_CORE: + XGrabButton(dpy, AnyButton, XIAnyModifier, + DefaultRootWindow(dpy), False, + ButtonPressMask|ButtonReleaseMask|PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, None); + break; + case GRABTYPE_XI1: + break; + case GRABTYPE_XI2: + XIGrabButton(dpy, VIRTUAL_CORE_POINTER_ID, + XIAnyButton, DefaultRootWindow(dpy), + None, GrabModeAsync, GrabModeAsync, False, + &mask, 1, &modifiers); + break; + case GRABTYPE_XI2TOUCH: + XIGrabTouchBegin(dpy, VIRTUAL_CORE_POINTER_ID, + DefaultRootWindow(dpy), False, + &mask, 1, &modifiers); + break; + } + + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_begin.events"); + + switch (grab_type) { + case GRABTYPE_CORE: + { + ASSERT_EVENT(XEvent, press, dpy, ButtonPress); + XUngrabButton(dpy, AnyButton, AnyModifier, DefaultRootWindow(dpy)); + break; + } + case GRABTYPE_XI1: + break; + case GRABTYPE_XI2: + { + ASSERT_EVENT(XIDeviceEvent, press, dpy, GenericEvent, xi2_opcode, XI_ButtonPress); + XIUngrabButton(dpy, VIRTUAL_CORE_POINTER_ID, XIAnyButton, + DefaultRootWindow(dpy), 1, &modifiers); + break; + } + case GRABTYPE_XI2TOUCH: + { + ASSERT_EVENT(XIDeviceEvent, tbegin, dpy, GenericEvent, xi2_opcode, XI_TouchBegin); + XIUngrabTouchBegin(dpy, VIRTUAL_CORE_POINTER_ID, DefaultRootWindow(dpy), 1, &modifiers); + } + } + + ASSERT_TRUE(NoEventPending(dpy)); + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_update.events"); + + switch (grab_type) { + case GRABTYPE_CORE: + { + ASSERT_EVENT(XEvent, motion, dpy, MotionNotify); + break; + } + case GRABTYPE_XI1: + break; + case GRABTYPE_XI2: + { + ASSERT_EVENT(XIDeviceEvent, motion, dpy, GenericEvent, xi2_opcode, XI_Motion); + break; + } + case GRABTYPE_XI2TOUCH: + { + ASSERT_EVENT(XIDeviceEvent, tupdate, dpy, GenericEvent, xi2_opcode, XI_TouchUpdate); + break; + } + } + + ASSERT_TRUE(NoEventPending(dpy)); + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_end.events"); + + switch (grab_type) { + case GRABTYPE_CORE: + { + ASSERT_EVENT(XEvent, release, dpy, ButtonRelease); + break; + } + case GRABTYPE_XI1: + break; + case GRABTYPE_XI2: + { + ASSERT_EVENT(XIDeviceEvent, release, dpy, GenericEvent, + xi2_opcode, XI_ButtonRelease); + break; + } + case GRABTYPE_XI2TOUCH: + { + ASSERT_EVENT(XIDeviceEvent, tend, dpy, GenericEvent, xi2_opcode, XI_TouchEnd); + XIAllowTouchEvents(dpy, tend->deviceid, tend->detail, + DefaultRootWindow(dpy), XIAcceptTouch); + break; + } + } + + delete[] mask.mask; + +} + +INSTANTIATE_TEST_CASE_P(, TouchUngrabTest, + ::testing::Values(GRABTYPE_CORE, GRABTYPE_XI2, GRABTYPE_XI2TOUCH)); + +/** + * @tparam AsyncPointer, SyncPointer, ReplayPointer + */ +class TouchGrabTestAllowSome : public TouchGrabTest, + public ::testing::WithParamInterface<int> { +}; + +TEST_P(TouchGrabTestAllowSome, TouchGrabPassedToPassivePassedToRegular) +{ + int mode = GetParam(); + std::string strmode; + switch(mode) { + case ReplayPointer: strmode = "ReplayPointer"; break; + case AsyncPointer: strmode = "AsyncPointer"; break; + case SyncPointer: strmode = "SyncPointer"; break; + } + + XORG_TESTCASE("Client 1: register for touch grabs with ownership on root window.\n" + "Client 2: register for sync passive core pointer grab on root window.\n" + "Client 3: register for core pointer events on root window.\n" + "Touch begin/end, repeat\n" + "Client 1: reject touch\n" + "Client 2: AllowSome(" + strmode + ")\n" + "Client 3: consume events.\n" + "Repeat\n" + "https://bugs.freedesktop.org/show_bug.cgi?id=56578"); + + ::Display *dpy1 = Display(); + ::Display *dpy2 = XOpenDisplay(server.GetDisplayString().c_str()); + ::Display *dpy3 = XOpenDisplay(server.GetDisplayString().c_str()); + + XSynchronize(dpy1, True); + XSynchronize(dpy2, True); + XSynchronize(dpy3, True); + + Window root = DefaultRootWindow(dpy1); + Window win = CreateWindow(dpy2, None); + + XIEventMask mask; + mask.deviceid = VIRTUAL_CORE_POINTER_ID; + mask.mask_len = XIMaskLen(XI_TouchOwnership); + mask.mask = new unsigned char[mask.mask_len](); + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchOwnership); + XISetMask(mask.mask, XI_TouchEnd); + + XIGrabModifiers mods = {}; + mods.modifiers = XIAnyModifier; + ASSERT_EQ(Success, XIGrabTouchBegin(dpy1, VIRTUAL_CORE_POINTER_ID, + root, False, &mask, 1, &mods)); + delete[] mask.mask; + + XGrabButton(dpy2, AnyButton, XIAnyModifier, win, False, + ButtonPressMask|ButtonReleaseMask, + GrabModeSync, GrabModeAsync, None, None); + + XSelectInput(dpy3, win, ButtonPressMask|ButtonReleaseMask); + + for (int i = 0; i < 2; i++) { + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_begin.events"); + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_update.events"); + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_end.events"); + } + + for (int i = 0; i < 2; i++) { + ASSERT_EVENT(XIDeviceEvent, tbegin, dpy1, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_EVENT(XIDeviceEvent, towner, dpy1, GenericEvent, xi2_opcode, XI_TouchOwnership); + ASSERT_EVENT(XIDeviceEvent, tupdate, dpy1, GenericEvent, xi2_opcode, XI_TouchUpdate); + ASSERT_EVENT(XIDeviceEvent, tend, dpy1, GenericEvent, xi2_opcode, XI_TouchEnd); + XIAllowTouchEvents(dpy1, tbegin->deviceid, tbegin->detail, root, XIRejectTouch); + if (i == 0) + ASSERT_EQ(XPending(dpy1), 4); /* begin, owner, update, end of + second touch */ + else + ASSERT_TRUE(NoEventPending(dpy1)); + + ASSERT_EVENT(XEvent, grab_press, dpy2, ButtonPress); + XAllowEvents(dpy2, mode, CurrentTime); + + if (mode == ReplayPointer) { + ASSERT_EVENT(XEvent, press, dpy3, ButtonPress); + ASSERT_EVENT(XEvent, release, dpy3, ButtonRelease); + ASSERT_TRUE(NoEventPending(dpy2)); + } else { + ASSERT_EVENT(XEvent, release, dpy2, ButtonRelease); + ASSERT_TRUE(NoEventPending(dpy3)); + } + } +} + +INSTANTIATE_TEST_CASE_P(, TouchGrabTestAllowSome, + ::testing::Values(AsyncPointer, SyncPointer, ReplayPointer)); class TouchGrabTestMultipleModes : public TouchGrabTest, public ::testing::WithParamInterface<int> { }; +TEST_P(TouchGrabTestMultipleModes, SingleTouchGrabListenerAcceptRejectBeforeTouchEnd) +{ + int mode = GetParam(); + std::string strmode = (mode == XIAcceptTouch) ? "XIAcceptTouch" : "XIRejectTouch"; + + XORG_TESTCASE("Register for a touch grab.\n" + "Begin touch\n" + "Verify begin event is received.\n" + "Call XIAllowTouchEvents(i" + strmode + ")\n" + "End touch\n" + "Verify end event is received at the right time.\n"); + + ::Display *dpy = Display(); + Window root = DefaultRootWindow(dpy); + + XIEventMask mask; + mask.deviceid = VIRTUAL_CORE_POINTER_ID; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = new unsigned char[mask.mask_len](); + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + XIGrabModifiers mods = {}; + mods.modifiers = XIAnyModifier; + ASSERT_EQ(Success, XIGrabTouchBegin(dpy, VIRTUAL_CORE_POINTER_ID, + root, False, &mask, 1, &mods)); + delete[] mask.mask; + + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_begin.events"); + + ASSERT_EVENT(XIDeviceEvent, tbegin, dpy, GenericEvent, xi2_opcode, XI_TouchBegin); + XIAllowTouchEvents(dpy, tbegin->deviceid, tbegin->detail, root, mode); + + if (mode == XIAcceptTouch) + ASSERT_TRUE(NoEventPending(dpy)); + else { + ASSERT_EVENT(XIDeviceEvent, tend, dpy, GenericEvent, xi2_opcode, XI_TouchEnd); + } + + dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_end.events"); + + if (mode == XIAcceptTouch) { + ASSERT_EVENT(XIDeviceEvent, tend, dpy, GenericEvent, xi2_opcode, XI_TouchEnd); + } + + ASSERT_TRUE(NoEventPending(dpy)); +} + TEST_P(TouchGrabTestMultipleModes, ActiveAndPassiveGrab) { int mode = GetParam(); @@ -804,6 +1571,7 @@ TEST_P(TouchGrabTestMultipleTaps, PassiveGrabPointerEmulationMultipleTouchesFast mask.mask_len = XIMaskLen(XI_ButtonRelease); mask.mask = new unsigned char[mask.mask_len](); XISetMask(mask.mask, XI_ButtonPress); + XISetMask(mask.mask, XI_ButtonRelease); XIGrabModifiers mods; mods.modifiers = XIAnyModifier; @@ -829,14 +1597,15 @@ TEST_P(TouchGrabTestMultipleTaps, PassiveGrabPointerEmulationMultipleTouchesFast XSync(dpy1, False); XSync(dpy2, False); - ASSERT_TRUE(XPending(dpy1)); + ASSERT_GT(XPending(dpy1), 0); int event_count = 0; while (XPending(dpy1)) { event_count++; - ASSERT_EVENT(XIDeviceEvent, ev, dpy1, GenericEvent, xi2_opcode, XI_ButtonPress); + ASSERT_EVENT(XIDeviceEvent, press, dpy1, GenericEvent, xi2_opcode, XI_ButtonPress); if (mode == GrabModeSync) - XIAllowEvents(dpy1, ev->deviceid, XISyncDevice, CurrentTime); + XIAllowEvents(dpy1, press->deviceid, XISyncDevice, CurrentTime); + ASSERT_EVENT(XIDeviceEvent, release, dpy1, GenericEvent, xi2_opcode, XI_ButtonRelease); } ASSERT_EQ(event_count, repeats); // ButtonPress event @@ -952,28 +1721,6 @@ public: XSync(dpy, False); } - void GrabDevice(::Display *dpy, int deviceid, Window win, bool ownership = false) - { - XIEventMask mask; - mask.deviceid = deviceid; - mask.mask_len = XIMaskLen(XI_TouchOwnership); - mask.mask = new unsigned char[mask.mask_len](); - - XISetMask(mask.mask, XI_TouchBegin); - XISetMask(mask.mask, XI_TouchEnd); - XISetMask(mask.mask, XI_TouchUpdate); - - if (ownership) - XISetMask(mask.mask, XI_TouchOwnership); - - ASSERT_EQ(Success, XIGrabDevice(dpy, deviceid, - win, CurrentTime, None, - GrabModeAsync, GrabModeAsync, - False, &mask)); - delete[] mask.mask; - XSync(dpy, False); - } - void SelectTouchOnWindow(::Display *dpy, Window win, bool ownership = false) { XIEventMask mask; @@ -1267,7 +2014,6 @@ TEST_F(TouchOwnershipTest, ActiveGrabOwnershipUngrabDevice) ASSERT_EQ(A_end->detail, touchid); /* Now we expect TouchOwnership on B */ - ASSERT_EQ(XPending(dpy2), 1); ASSERT_EVENT(XITouchOwnershipEvent, oe, dpy2, GenericEvent, xi2_opcode, XI_TouchOwnership); ASSERT_EQ(oe->touchid, (unsigned int)touchid); @@ -1329,8 +2075,7 @@ TEST_F(TouchOwnershipTest, ActivePointerUngrabDuringTouch) "TouchBegin in the window.\n" "Expect ButtonPress to A.\n" "Ungrab pointer.\n" - "Expect TouchBegin and TouchOwnership to B.\n" - "TouchEnd in the window, expect TouchEnd on B.\n"); + "TouchEnd to B.\n"); ::Display *dpy = Display(); XSynchronize(dpy, True); @@ -1354,14 +2099,14 @@ TEST_F(TouchOwnershipTest, ActivePointerUngrabDuringTouch) ASSERT_TRUE(NoEventPending(dpy2)); XIUngrabDevice(dpy, VIRTUAL_CORE_POINTER_ID, CurrentTime); + ASSERT_TRUE(NoEventPending(dpy)); - ASSERT_EVENT(XITouchOwnershipEvent, B_ownership, dpy2, GenericEvent, xi2_opcode, XI_TouchOwnership); + ASSERT_EVENT(XIDeviceEvent, B_end, dpy2, GenericEvent, xi2_opcode, XI_TouchEnd); dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_end.events"); - ASSERT_EVENT(XIDeviceEvent, B_end, dpy2, GenericEvent, xi2_opcode, XI_TouchEnd); - ASSERT_TRUE(NoEventPending(dpy)); + ASSERT_TRUE(NoEventPending(dpy2)); } #endif /* HAVE_XI22 */ diff --git a/tests/server/touch.cpp b/tests/server/touch.cpp index d8cdeb2..b226848 100644 --- a/tests/server/touch.cpp +++ b/tests/server/touch.cpp @@ -84,6 +84,36 @@ protected: } }; +TEST_F(TouchTest, TouchEventFlags) +{ + XORG_TESTCASE("Register for touch events on root window.\n" + "Trigger touch end/receive\n" + "Verify only touch flags are set on touch event\n"); + + ::Display *dpy = Display(); + XIEventMask mask; + mask.deviceid = VIRTUAL_CORE_POINTER_ID; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = new unsigned char[mask.mask_len](); + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + XISelectEvents(dpy, DefaultRootWindow(dpy), &mask, 1); + delete[] mask.mask; + + TouchBegin(100, 100); + TouchEnd(); + + ASSERT_EVENT(XIDeviceEvent, tbegin, dpy, GenericEvent, xi2_opcode, XI_TouchBegin); + ASSERT_EVENT(XIDeviceEvent, tend, dpy, GenericEvent, xi2_opcode, XI_TouchEnd); + + ASSERT_TRUE(tbegin->flags & XITouchEmulatingPointer); + ASSERT_TRUE(tend->flags & XITouchEmulatingPointer); + + ASSERT_EQ(0, tbegin->flags & ~XITouchEmulatingPointer); + ASSERT_EQ(0, tend->flags & ~XITouchEmulatingPointer); +} + /** * A test fixture for testing touch across XInput 2.x extension versions. * @@ -267,8 +297,7 @@ TEST_P(TouchTestXI2Version, NoEmulatedButton1MotionWithoutButtonPress) dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_update.events"); dev->Play(RECORDINGS_DIR "tablets/N-Trig-MultiTouch.touch_1_end.events"); - XSync(dpy, False); - ASSERT_EQ(XPending(dpy), 0); + ASSERT_TRUE(NoEventPending(dpy)); } TEST_P(TouchTestXI2Version, EmulatedButton1MotionMaskOnTouch) |