[PATCH 2/2] Add test for XIQueryPointer button mask when physical touch is active

Chase Douglas chase.douglas at canonical.com
Wed Apr 25 18:15:51 PDT 2012


Signed-off-by: Chase Douglas <chase.douglas at canonical.com>
---
The functions to wait for specific events should probably be moved to
xorg-gtest eventually, but I want to make sure they are generic enough for
all use cases before doing that.

 configure.ac                                       |   14 +
 test/integration/.gitignore                        |    1 +
 test/integration/Makefile.am                       |   24 ++
 .../recordings/ntrig_dell_xt2/1_begin.record       |   11 +
 .../recordings/ntrig_dell_xt2/1_end.record         |    3 +
 .../recordings/ntrig_dell_xt2/device.prop          |   32 +++
 test/integration/xi2.cpp                           |  267 ++++++++++++++++++++
 7 files changed, 352 insertions(+), 0 deletions(-)
 create mode 100644 test/integration/.gitignore
 create mode 100644 test/integration/recordings/ntrig_dell_xt2/1_begin.record
 create mode 100644 test/integration/recordings/ntrig_dell_xt2/1_end.record
 create mode 100644 test/integration/recordings/ntrig_dell_xt2/device.prop
 create mode 100644 test/integration/xi2.cpp

diff --git a/configure.ac b/configure.ac
index fe350c9..bc9f46f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2141,6 +2141,20 @@ if [test "x$XORG" = xyes && test "x$enable_integration_tests" != xno]; then
     fi
 fi
 
+PKG_CHECK_MODULES(TEST_X11, x11, have_test_x11=yes, have_test_x11=no)
+PKG_CHECK_MODULES(TEST_XINPUT, [xi >= 1.6], have_test_xinput=yes, have_test_xinput=no)
+
+if [test "x$have_test_x11" != xyes]; then
+    AC_MSG_NOTICE([libX11 not available, skipping input tests])
+elif [test "x$have_test_xinput" != xyes]; then
+    AC_MSG_NOTICE([libXi 1.6 not available, skipping input tests])
+elif [test "x$have_xorg_gtest_evemu" != xyes]; then
+    AC_MSG_NOTICE([uTouch-Evemu not available, skipping input tests])
+else
+    enable_input_tests=yes
+fi
+AM_CONDITIONAL(ENABLE_XORG_GTEST_INPUT_TESTS, [test "x$enable_input_tests" = xyes])
+
 dnl and the rest of these are generic, so they're in config.h
 dnl 
 dnl though, thanks to the passing of some significant amount of time, the
diff --git a/test/integration/.gitignore b/test/integration/.gitignore
new file mode 100644
index 0000000..ab86ebf
--- /dev/null
+++ b/test/integration/.gitignore
@@ -0,0 +1 @@
+gtest-tests
diff --git a/test/integration/Makefile.am b/test/integration/Makefile.am
index e70d642..3b7c858 100644
--- a/test/integration/Makefile.am
+++ b/test/integration/Makefile.am
@@ -1,4 +1,28 @@
+TESTS =
+
 if ENABLE_XORG_GTEST_TESTS
 include $(top_srcdir)/test/integration/Makefile-xorg-gtest.am
 check_LIBRARIES = $(XORG_GTEST_BUILD_LIBS)
+
+if ENABLE_XORG_GTEST_INPUT_TESTS
+TESTS += gtest-tests
 endif
+endif
+
+check_PROGRAMS = $(TESTS)
+
+AM_CPPFLAGS = \
+	-DDEFAULT_XORG_SERVER=\"$(abs_top_builddir)/hw/xfree86/Xorg\" \
+	-DTEST_ROOT_DIR=\"$(abs_top_srcdir)/test/integration/\" \
+	$(GTEST_CPPFLAGS) \
+	$(XORG_GTEST_CPPFLAGS)
+
+AM_CXXFLAGS = $(GTEST_CXXFLAGS) $(XORG_GTEST_CXXFLAGS)
+
+gtest_tests_SOURCES = xi2.cpp
+gtest_tests_LDADD = \
+	$(TEST_XINPUT_LIBS) \
+	$(TEST_X11_LIBS) \
+	$(XORG_GTEST_LIBS) \
+	$(EVEMU_LIBS) \
+	$(XORG_GTEST_MAIN_LIBS)
diff --git a/test/integration/recordings/ntrig_dell_xt2/1_begin.record b/test/integration/recordings/ntrig_dell_xt2/1_begin.record
new file mode 100644
index 0000000..28a849b
--- /dev/null
+++ b/test/integration/recordings/ntrig_dell_xt2/1_begin.record
@@ -0,0 +1,11 @@
+E: 1327542640.244087 0003 0000 2745
+E: 1327542640.244089 0003 0001 1639
+E: 1327542640.244090 0003 0035 2745
+E: 1327542640.244091 0003 0036 1639
+E: 1327542640.244092 0003 0034 0
+E: 1327542640.244093 0003 0030 468
+E: 1327542640.244094 0003 0031 306
+E: 1327542640.244095 0000 0002 0
+E: 1327542640.244251 0001 014d 1
+E: 1327542640.244251 0001 014a 1
+E: 1327542640.244253 0000 0000 0
diff --git a/test/integration/recordings/ntrig_dell_xt2/1_end.record b/test/integration/recordings/ntrig_dell_xt2/1_end.record
new file mode 100644
index 0000000..cd6a9d9
--- /dev/null
+++ b/test/integration/recordings/ntrig_dell_xt2/1_end.record
@@ -0,0 +1,3 @@
+E: 1327542642.244253 0001 014d 0
+E: 1327542642.244253 0001 014a 0
+E: 1327542642.244253 0000 0000 0
diff --git a/test/integration/recordings/ntrig_dell_xt2/device.prop b/test/integration/recordings/ntrig_dell_xt2/device.prop
new file mode 100644
index 0000000..2738c04
--- /dev/null
+++ b/test/integration/recordings/ntrig_dell_xt2/device.prop
@@ -0,0 +1,32 @@
+N: N-Trig MultiTouch (Virtual Test Device)
+I: 0003 1b96 0001 0110
+P: 00 00 00 00 00 00 00 00
+B: 00 0b 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 04 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 02 00 00 00 00 00 00 00 00
+B: 03 03 00 00 00 00 01 73 00
+B: 04 00 00 00 00 00 00 00 00
+B: 05 00 00 00 00 00 00 00 00
+B: 11 00 00 00 00 00 00 00 00
+B: 12 00 00 00 00 00 00 00 00
+B: 15 00 00 00 00 00 00 00 00
+B: 15 00 00 00 00 00 00 00 00
+A: 00 0 9600 75 0
+A: 01 0 7200 78 0
+A: 28 0 255 0 0
+A: 30 0 9600 200 0
+A: 31 0 7200 150 0
+A: 34 0 1 0 0
+A: 35 0 9600 75 0
+A: 36 0 7200 78 0
diff --git a/test/integration/xi2.cpp b/test/integration/xi2.cpp
new file mode 100644
index 0000000..ea2f783
--- /dev/null
+++ b/test/integration/xi2.cpp
@@ -0,0 +1,267 @@
+#include <stdexcept>
+
+#include <xorg/gtest/xorg-gtest.h>
+
+#include <X11/extensions/XInput2.h>
+
+namespace {
+
+/**
+ * Wait for an event on the X connection.
+ *
+ * param [in] display The X display connection
+ * param [in] timeout The timeout in milliseconds
+ *
+ * @return Whether an event is available
+ */
+bool wait_for_event(::Display *display, time_t timeout = 1000)
+{
+    fd_set fds;
+    FD_ZERO(&fds);
+
+    int display_fd = ConnectionNumber(display);
+
+    XSync(display, False);
+
+    if (XPending(display))
+        return true;
+    else {
+        FD_SET(display_fd, &fds);
+
+        struct timeval timeval = {
+            static_cast<time_t>(timeout / 1000),
+            static_cast<time_t>(timeout % 1000),
+        };
+
+        int ret;
+        if (timeout)
+            ret = select(display_fd + 1, &fds, NULL, NULL, &timeval);
+        else
+            ret = select(display_fd + 1, &fds, NULL, NULL, NULL);
+
+        if (ret < 0)
+            throw std::runtime_error("Failed to select on X fd");
+
+        if (ret == 0)
+            return false;
+
+        return XPending(display);
+    }
+}
+
+/**
+ * Wait for an event of a specific type on the X connection.
+ *
+ * param [in] display   The X display connection
+ * param [in] type      The X core protocol event type
+ * param [in] extension The X extension opcode of a generic event, or -1 for any
+ *                      generic event
+ * param [in] evtype    The X extension event type of a generic event, or -1 for
+ *                      any event of the given extension
+ * param [in] timeout   The timeout in milliseconds
+ *
+ * @return Whether an event is available
+ */
+bool wait_for_event_of_type(::Display *display, int type, int extension,
+                            int evtype, time_t timeout = 1000)
+{
+    while (wait_for_event(display)) {
+        XEvent event;
+        if (!XPeekEvent(display, &event))
+            throw std::runtime_error("Failed to peek X event");
+
+        if (event.type != type) {
+            if (XNextEvent(display, &event) != Success)
+                throw std::runtime_error("Failed to remove X event");
+            continue;
+        }
+
+        if (event.type != GenericEvent || extension == -1)
+            return true;
+
+        XGenericEvent *generic_event = reinterpret_cast<XGenericEvent*>(&event);
+
+        if (generic_event->extension != extension) {
+            if (XNextEvent(display, &event) != Success)
+                throw std::runtime_error("Failed to remove X event");
+            continue;
+        }
+
+        if (evtype == -1 || generic_event->evtype == evtype)
+            return true;
+
+        if (XNextEvent(display, &event) != Success)
+            throw std::runtime_error("Failed to remove X event");
+    }
+}
+
+/**
+ * Wait for a specific device to be added to the server.
+ *
+ * param [in] display The X display connection
+ * param [in] name    The name of the device to wait for
+ * param [in] timeout The timeout in milliseconds
+ *
+ * @return Whether the device was added
+ */
+bool wait_for_device(::Display *display, const std::string &name,
+                     time_t timeout = 1000)
+{
+    int opcode;
+    int event_start;
+    int error_start;
+
+    if (!XQueryExtension(display, "XInputExtension", &opcode, &event_start,
+                         &error_start))
+        throw std::runtime_error("Failed to query XInput extension");
+
+    while (wait_for_event_of_type(display, GenericEvent, opcode,
+                                  XI_HierarchyChanged)) {
+        XEvent event;
+        if (XNextEvent(display, &event) != Success)
+            throw std::runtime_error("Failed to get X event");
+
+        XGenericEventCookie *xcookie =
+            reinterpret_cast<XGenericEventCookie*>(&event.xcookie);
+        if (!XGetEventData(display, xcookie))
+            throw std::runtime_error("Failed to get X event data");
+
+        XIHierarchyEvent *hierarchy_event =
+            reinterpret_cast<XIHierarchyEvent*>(xcookie->data);
+
+        if (!(hierarchy_event->flags & XISlaveAdded)) {
+            XFreeEventData(display, xcookie);
+            continue;
+        }
+
+        bool device_found = false;
+        for (int i = 0; i < hierarchy_event->num_info; i++) {
+            if (!(hierarchy_event->info[i].flags & XISlaveAdded))
+                continue;
+
+            int num_devices;
+            XIDeviceInfo *device_info =
+                XIQueryDevice(display, hierarchy_event->info[i].deviceid,
+                              &num_devices);
+            if (num_devices != 1 || !device_info)
+                throw std::runtime_error("Failed to query device");
+
+            if (name.compare(device_info[0].name) == 0) {
+                device_found = true;
+                break;
+            }
+        }
+
+        XFreeEventData(display, xcookie);
+
+        if (device_found)
+            return true;
+    }
+
+    return false;
+}
+
+}
+
+/**
+ * A test fixture for testing the XInput 2.x extension.
+ *
+ * @tparam The XInput 2.x minor version
+ */
+class XInput2Test : public xorg::testing::Test,
+                    public ::testing::WithParamInterface<int> {
+protected:
+    virtual void SetUp()
+    {
+        ASSERT_NO_FATAL_FAILURE(xorg::testing::Test::SetUp());
+
+        int event_start;
+        int error_start;
+
+        ASSERT_TRUE(XQueryExtension(Display(), "XInputExtension", &xi2_opcode_,
+                                    &event_start, &error_start));
+
+        int major = 2;
+        int minor = GetParam();
+
+        ASSERT_EQ(Success, XIQueryVersion(Display(), &major, &minor));
+    }
+
+    int xi2_opcode_;
+};
+
+/**
+ * XIQueryPointer for XInput 2.1 and earlier should report the first button
+ * pressed if a touch is physically active. For XInput 2.2 and later clients,
+ * the first button should not be reported.
+ */
+TEST_P(XInput2Test, XIQueryPointerTouchscreen)
+{
+    XIEventMask mask;
+    mask.deviceid = XIAllDevices;
+    mask.mask_len = XIMaskLen(XI_HierarchyChanged);
+    mask.mask = reinterpret_cast<unsigned char*>(
+        calloc(XIMaskLen(XI_HierarchyChanged), 1));
+    XISetMask(mask.mask, XI_HierarchyChanged);
+
+    ASSERT_EQ(Success,
+              XISelectEvents(Display(), DefaultRootWindow(Display()), &mask,
+              1));
+
+    mask.deviceid = XIAllMasterDevices;
+    XIClearMask(mask.mask, XI_HierarchyChanged);
+    XISetMask(mask.mask, XI_ButtonPress);
+
+    ASSERT_EQ(Success,
+              XISelectEvents(Display(), DefaultRootWindow(Display()), &mask,
+              1));
+
+    free(mask.mask);
+
+    XFlush(Display());
+
+    xorg::testing::evemu::Device device(
+        TEST_ROOT_DIR "recordings/ntrig_dell_xt2/device.prop");
+
+    ASSERT_TRUE(wait_for_device(Display(),
+                                "N-Trig MultiTouch (Virtual Test Device)"));
+
+    device.Play(TEST_ROOT_DIR "recordings/ntrig_dell_xt2/1_begin.record");
+
+    ASSERT_TRUE(wait_for_event_of_type(Display(), GenericEvent, xi2_opcode_,
+                                       XI_ButtonPress));
+
+    XEvent event;
+    ASSERT_EQ(Success, XNextEvent(Display(), &event));
+
+    XGenericEventCookie *xcookie = &event.xcookie;
+    ASSERT_TRUE(XGetEventData(Display(), xcookie));
+
+    XIDeviceEvent *device_event =
+        reinterpret_cast<XIDeviceEvent*>(xcookie->data);
+
+    Window root;
+    Window child;
+    double root_x;
+    double root_y;
+    double win_x;
+    double win_y;
+    XIButtonState buttons;
+    XIModifierState modifiers;
+    XIGroupState group;
+    ASSERT_TRUE(XIQueryPointer(Display(), device_event->deviceid,
+                               DefaultRootWindow(Display()), &root, &child,
+                               &root_x, &root_y, &win_x, &win_y, &buttons,
+                               &modifiers, &group));
+
+    /* Test if button 1 is pressed */
+    ASSERT_GE(buttons.mask_len, XIMaskLen(1));
+    if (GetParam() < 2)
+        EXPECT_TRUE(XIMaskIsSet(buttons.mask, 0));
+    else
+        EXPECT_FALSE(XIMaskIsSet(buttons.mask, 0));
+
+    XFreeEventData(Display(), xcookie);
+}
+
+INSTANTIATE_TEST_CASE_P(, XInput2Test, ::testing::Range(0, 3));
-- 
1.7.9.1



More information about the xorg-devel mailing list