diff --git a/README.md b/README.md index da6e3b89bebe07a7af7dce9d44b8b99e7941b7d9..a77d693b7ef9a22623e6d2463e147d9fbb8b5848 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ This is a test suite for X.Org tests. == Building the code == ``` -$ git submodule init -$ git submodule update $ meson builddir $ ninja -C builddir ``` diff --git a/gtest/include/xorg/gtest/inputtest/xorg-gtest-device.h b/gtest/include/xorg/gtest/inputtest/xorg-gtest-device.h new file mode 100644 index 0000000000000000000000000000000000000000..4c959c2da4a174a809d084ed3cfe2304eeb58354 --- /dev/null +++ b/gtest/include/xorg/gtest/inputtest/xorg-gtest-device.h @@ -0,0 +1,111 @@ +/******************************************************************************* + * + * X testing environment - Google Test environment feat. dummy x server + * + * Copyright (C) 2020 Povilas Kanapickas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ******************************************************************************/ + +#ifndef XORG_INPUTTEST_DEVICE_H_ +#define XORG_INPUTTEST_DEVICE_H_ + +#include +#include +#include "xorg-gtest-valuators.h" + +namespace xorg { +namespace testing { +namespace inputtest { + +enum class DeviceType { + KEYBOARD, + POINTER, + POINTER_ABSOLUTE, + POINTER_ABSOLUTE_PROXIMITY, + TOUCH +}; + +#define XORG_INPUTTEST_DRIVER "inputtest" + +/** + * A device to replay events through the xf86-input-inputtest input driver + */ +class Device { +public: + /** + * Create a new device context. + * + * @param [in] socket_path Path where the UNIX domain socket used for communication with the + * driver will be constructed. + * @param [in] type The type of the device which defines what operations are available. + */ + explicit Device(const std::string& socket_path, DeviceType type); + ~Device(); + + /** + * Returns the options that the server needs to be configured for this device. + */ + std::string GetOptions() const; + + /** + * Waits for the connection to the driver being established. + * Throws an exception if the files are not available after 10 seconds. + */ + void WaitForDriver(); + + /** + * All of the functions below result in an input event being sent to the X server. + * WaitForDriver() must be called before using them. + */ + void RelMotion(double x, double y); + void RelMotion(double x, double y, const Valuators& extra_valuators); + + void AbsMotion(double x, double y); + void AbsMotion(double x, double y, const Valuators& extra_valuators); + + void ProximityIn(const Valuators& valuators); + void ProximityOut(const Valuators& valuators); + + void ButtonDown(std::int32_t button); + void ButtonUp(std::int32_t button); + + void KeyDown(std::int32_t key_code); + void KeyUp(std::int32_t key_code); + + void TouchBegin(double x, double y, std::uint32_t id); + void TouchBegin(double x, double y, std::uint32_t id, const Valuators& extra_valuators); + void TouchUpdate(double x, double y, std::uint32_t id); + void TouchUpdate(double x, double y, std::uint32_t id, const Valuators& extra_valuators); + void TouchEnd(double x, double y, std::uint32_t id); + void TouchEnd(double x, double y, std::uint32_t id, const Valuators& extra_valuators); + +private: + + struct Private; + std::unique_ptr d_; +}; + +} // namespace inputtest +} // namespace testing +} // namespace xorg + +#endif // XORG_INPUTTEST_DEVICE_H_ diff --git a/gtest/include/xorg/gtest/inputtest/xorg-gtest-valuators.h b/gtest/include/xorg/gtest/inputtest/xorg-gtest-valuators.h new file mode 100644 index 0000000000000000000000000000000000000000..b844bd0f43ad4844be84d256a199488dafe86004 --- /dev/null +++ b/gtest/include/xorg/gtest/inputtest/xorg-gtest-valuators.h @@ -0,0 +1,67 @@ +/******************************************************************************* + * + * X testing environment - Google Test environment feat. dummy x server + * + * Copyright (C) 2020 Povilas Kanapickas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ******************************************************************************/ + +#ifndef XORG_INPUTTEST_VALUATORS_H_ +#define XORG_INPUTTEST_VALUATORS_H_ + +#include +#include + +#include + +namespace xorg { +namespace testing { +namespace inputtest { + +/** + * A class that represents the valuator values sent to the X server. + * The axes are hardcoded according to the current implementation of the xf86-input-inputtest + * input driver. + */ + +class Valuators { +public: + Valuators(); + Valuators(const Valuators& other); + Valuators& operator=(const Valuators& other); + ~Valuators(); + + Valuators& Set(unsigned axis, double value); + Valuators& SetUnaccel(unsigned axis, double value_accel, double value_unaccel); + + void RetrieveValuatorData(xf86ITValuatorData* out) const; + +private: + struct Private; + std::unique_ptr d_; +}; + +} // namespace inputtest +} // namespace testing +} // namespace xorg + +#endif // XORG_INPUTTEST_VALUATORS_H_ diff --git a/gtest/include/xorg/gtest/xorg-gtest.h b/gtest/include/xorg/gtest/xorg-gtest.h index 47525e6ca8c297f2d8899bbaa5096ff64a8b2c81..68359c7841318c229cecae6cb78a7badecc11b16 100644 --- a/gtest/include/xorg/gtest/xorg-gtest.h +++ b/gtest/include/xorg/gtest/xorg-gtest.h @@ -34,6 +34,9 @@ #include "xorg-gtest-test.h" #include "evemu/xorg-gtest-device.h" +#include "inputtest/xorg-gtest-device.h" +#include "inputtest/xorg-gtest-valuators.h" + #define XORG_TESTCASE(message) \ SCOPED_TRACE("\n::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n" \ "TESTCASE:\n" \ diff --git a/gtest/src/inputtest-device.cpp b/gtest/src/inputtest-device.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b66b0729d2f889c1f35281b8d979ae16293b0921 --- /dev/null +++ b/gtest/src/inputtest-device.cpp @@ -0,0 +1,251 @@ +/******************************************************************************* + * + * X testing environment - Google Test environment feat. dummy x server + * + * Copyright (C) 2020 Povilas Kanapickas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ******************************************************************************/ + +#include "xorg/gtest/inputtest/xorg-gtest-device.h" +#include "inputtest-driver-connection.h" + +#include + +#include +#include + +#include +#include +#include + +namespace xorg { +namespace testing { +namespace inputtest { + +namespace { + +std::string DeviceTypeToOptionString(DeviceType type) +{ + switch (type) { + case DeviceType::KEYBOARD: return "Keyboard"; + case DeviceType::POINTER: return "Pointer"; + case DeviceType::POINTER_ABSOLUTE: return "PointerAbsolute"; + case DeviceType::POINTER_ABSOLUTE_PROXIMITY: return "PointerAbsoluteProximity"; + case DeviceType::TOUCH: return "Touch"; + } + return ""; +} + +void EnsureDeviceTypeForEvent(DeviceType type, std::initializer_list allowed_types) +{ + for (auto allowed_type : allowed_types) { + if (type == allowed_type) + return; + } + throw std::runtime_error("Unsupported event for the device type"); +} + +} // namespace + +struct Device::Private { + Private(const std::string& socket_path) : + connection(socket_path) {} + + DriverConnection connection; + DeviceType device_type; +}; + +Device::Device(const std::string& socket_path, DeviceType type) : + d_{std::unique_ptr(new Private(socket_path))} +{ + d_->device_type = type; +} + +Device::~Device() +{ +} + +std::string Device::GetOptions() const +{ + // add a new line before to protect against accidentally missed newline in the rest of + // configuration + return "\n" + "Option \"DeviceType\" \"" + DeviceTypeToOptionString(d_->device_type) + "\"\n" + "Option \"SocketPath\" \"" + d_->connection.SocketPath() + "\"\n"; +} + +void Device::WaitForDriver() +{ + d_->connection.WaitForDriver(); +} + +void Device::RelMotion(double x, double y) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::POINTER}); + Valuators valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.RelMotion(valuators); +} + +void Device::RelMotion(double x, double y, const Valuators& extra_valuators) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::POINTER}); + Valuators valuators = extra_valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.RelMotion(valuators); +} + +void Device::AbsMotion(double x, double y) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::POINTER_ABSOLUTE}); + Valuators valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.AbsMotion(valuators); +} + +void Device::AbsMotion(double x, double y, const Valuators& extra_valuators) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::POINTER_ABSOLUTE}); + Valuators valuators = extra_valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.AbsMotion(valuators); +} + +void Device::ProximityIn(const Valuators& valuators) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::POINTER_ABSOLUTE_PROXIMITY}); + d_->connection.ProximityIn(valuators); +} + +void Device::ProximityOut(const Valuators& valuators) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::POINTER_ABSOLUTE_PROXIMITY}); + d_->connection.ProximityOut(valuators); +} + +void Device::ButtonDown(std::int32_t button) +{ + EnsureDeviceTypeForEvent(d_->device_type, { + DeviceType::POINTER, + DeviceType::POINTER_ABSOLUTE, + DeviceType::POINTER_ABSOLUTE_PROXIMITY, + }); + if (button == 0 || button >= 256) + throw std::runtime_error("Invalid button number"); + + if (d_->device_type == DeviceType::POINTER) { + d_->connection.ButtonDownRel(button, Valuators()); + } else { + d_->connection.ButtonDownAbs(button, Valuators()); + } +} + +void Device::ButtonUp(std::int32_t button) +{ + EnsureDeviceTypeForEvent(d_->device_type, { + DeviceType::POINTER, + DeviceType::POINTER_ABSOLUTE, + DeviceType::POINTER_ABSOLUTE_PROXIMITY, + }); + if (button == 0 || button >= 256) + throw std::runtime_error("Invalid button number"); + + if (d_->device_type == DeviceType::POINTER) { + d_->connection.ButtonUpRel(button, Valuators()); + } else { + d_->connection.ButtonUpAbs(button, Valuators()); + } +} + +void Device::KeyDown(std::int32_t key_code) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::KEYBOARD}); + d_->connection.KeyDown(key_code); +} + +void Device::KeyUp(std::int32_t key_code) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::KEYBOARD}); + d_->connection.KeyUp(key_code); +} + +void Device::TouchBegin(double x, double y, std::uint32_t id) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::TOUCH}); + Valuators valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.TouchBegin(id, valuators); +} + +void Device::TouchBegin(double x, double y, std::uint32_t id, const Valuators& extra_valuators) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::TOUCH}); + Valuators valuators = extra_valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.TouchBegin(id, valuators); +} + +void Device::TouchUpdate(double x, double y, std::uint32_t id) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::TOUCH}); + Valuators valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.TouchUpdate(id, valuators); +} + +void Device::TouchUpdate(double x, double y, std::uint32_t id, const Valuators& extra_valuators) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::TOUCH}); + Valuators valuators = extra_valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.TouchUpdate(id, valuators); +} + +void Device::TouchEnd(double x, double y, std::uint32_t id) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::TOUCH}); + Valuators valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.TouchEnd(id, valuators); +} + +void Device::TouchEnd(double x, double y, std::uint32_t id, const Valuators& extra_valuators) +{ + EnsureDeviceTypeForEvent(d_->device_type, {DeviceType::TOUCH}); + Valuators valuators = extra_valuators; + valuators.Set(0, x); + valuators.Set(1, y); + d_->connection.TouchEnd(id, valuators); +} + +} // namespace xorg +} // namespace testing +} // namespace inputtest diff --git a/gtest/src/inputtest-driver-connection.cpp b/gtest/src/inputtest-driver-connection.cpp new file mode 100644 index 0000000000000000000000000000000000000000..564d100697764c6b9ced165ac4ad002f752e2311 --- /dev/null +++ b/gtest/src/inputtest-driver-connection.cpp @@ -0,0 +1,320 @@ +/******************************************************************************* + * + * X testing environment - Google Test environment feat. dummy x server + * + * Copyright (C) 2020 Povilas Kanapickas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ******************************************************************************/ + +#include "inputtest-driver-connection.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xorg { +namespace testing { +namespace inputtest { + +namespace { + +constexpr std::int32_t XORG_KEY_CODE_OFFSET = 8; + +template +void SendEvent(int fd, const T& ev) +{ + if (write(fd, &ev, ev.header.length) != ev.header.length) + throw std::runtime_error("Failed to send event"); +} + +template +T CreateEvent(xf86ITEventType type) +{ + T event; + std::memset(&event, 0, sizeof(event)); + event.header.length = sizeof(event); + event.header.type = type; + return event; +} + +void SendButtonEvent(int fd, bool is_press, bool is_absolute, std::int32_t button, + const Valuators& valuators) +{ + xf86ITEventButton ev = CreateEvent(XF86IT_EVENT_BUTTON); + ev.is_absolute = is_absolute; + ev.button = button; + ev.is_press = is_press; + valuators.RetrieveValuatorData(&ev.valuators); + SendEvent(fd, ev); +} + +void SendTouchEvent(int fd, std::uint16_t type, std::uint32_t touchid, const Valuators& valuators) +{ + xf86ITEventTouch ev = CreateEvent(XF86IT_EVENT_TOUCH); + ev.touch_type = type; + ev.touchid = touchid; + valuators.RetrieveValuatorData(&ev.valuators); + SendEvent(fd, ev); +} + +template +T ReceiveResponse(int fd, int type) +{ + T response; + std::memset(&response, 0, sizeof(response)); + if (read(fd, &response, sizeof(response)) != sizeof(response)) + throw std::runtime_error("Could not read event " + std::to_string(type)); + if (response.header.type != type) + throw std::runtime_error("Invalid response type: got " + std::to_string(response.header.type) + + " expected " + std::to_string(type)); + if (response.header.length != sizeof(response)) + throw std::runtime_error("Invalid response length"); + return response; +} + +} // namespace + +struct DriverConnection::Private { + std::string socket_path; + std::uint32_t protocol_version_major = 0; + std::uint32_t protocol_version_minor = 0; + int socket_fd = -1; +}; + +DriverConnection::DriverConnection(const std::string& socket_path) : + d_{std::unique_ptr(new Private)} +{ + d_->socket_path = socket_path; +} + +DriverConnection::~DriverConnection() +{ + Close(); +} + +const std::string& DriverConnection::SocketPath() const +{ + return d_->socket_path; +} + +bool DriverConnection::TryConnectToDriver() +{ + if (d_->socket_fd != -1) { + return true; + } + + sockaddr_un address = {}; + + if (d_->socket_path.size() > sizeof(address.sun_path)) + throw std::runtime_error("Too long path to connect to driver"); + + address.sun_family = AF_UNIX; + std::strncpy(address.sun_path, d_->socket_path.c_str(), sizeof(address.sun_path)); + + if (access(d_->socket_path.c_str(), F_OK) == -1) { + return false; + } + int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (socket_fd < 0) + throw std::runtime_error("Can't create socket"); + + if (connect(socket_fd, reinterpret_cast(&address), sizeof(address)) != 0) { + close(socket_fd); + return false; + } + + d_->socket_fd = socket_fd; + return true; +} + +void DriverConnection::WaitForDriver() +{ + unsigned timeout_ms = 10000; + unsigned sleep_ms = 20; + + if (d_->socket_fd != -1) + return; + + for (unsigned i = 0; i < timeout_ms; i += sleep_ms) { + if (!TryConnectToDriver()) { + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); + continue; + } + + xf86ITEventClientVersion ev = CreateEvent(XF86IT_EVENT_CLIENT_VERSION); + ev.major = XF86IT_PROTOCOL_VERSION_MAJOR; + ev.minor = XF86IT_PROTOCOL_VERSION_MINOR; + SendEvent(d_->socket_fd, ev); + + xf86ITResponseServerVersion response = + ReceiveResponse(d_->socket_fd, XF86IT_RESPONSE_SERVER_VERSION); + + d_->protocol_version_major = response.major; + d_->protocol_version_minor = response.minor; + return; + } + + throw std::runtime_error("Timed out waiting for device files to be opened"); +} + +void DriverConnection::Close() +{ + if (d_->socket_fd != -1) { + close(d_->socket_fd); + d_->socket_fd = -1; + } +} + +void DriverConnection::Sync() +{ + xf86ITEventWaitForSync ev = CreateEvent(XF86IT_EVENT_WAIT_FOR_SYNC); + SendEvent(d_->socket_fd, ev); + ReceiveResponse(d_->socket_fd, XF86IT_RESPONSE_SYNC_FINISHED); +} + +void DriverConnection::EnsureConnectedToDriver() +{ + if (d_->socket_fd == -1) + throw std::runtime_error("DriverConnection is not open"); +} + +void DriverConnection::RelMotion(const Valuators& valuators) +{ + EnsureConnectedToDriver(); + xf86ITEventMotion ev = CreateEvent(XF86IT_EVENT_MOTION); + ev.is_absolute = false; + valuators.RetrieveValuatorData(&ev.valuators); + SendEvent(d_->socket_fd, ev); + Sync(); +} + +void DriverConnection::AbsMotion(const Valuators& valuators) +{ + EnsureConnectedToDriver(); + xf86ITEventMotion ev = CreateEvent(XF86IT_EVENT_MOTION); + ev.is_absolute = true; + valuators.RetrieveValuatorData(&ev.valuators); + SendEvent(d_->socket_fd, ev); + Sync(); +} + +void DriverConnection::ProximityIn(const Valuators& valuators) +{ + EnsureConnectedToDriver(); + xf86ITEventProximity ev = CreateEvent(XF86IT_EVENT_PROXIMITY); + ev.is_prox_in = true; + valuators.RetrieveValuatorData(&ev.valuators); + SendEvent(d_->socket_fd, ev); + Sync(); +} + +void DriverConnection::ProximityOut(const Valuators& valuators) +{ + EnsureConnectedToDriver(); + xf86ITEventProximity ev = CreateEvent(XF86IT_EVENT_PROXIMITY); + ev.is_prox_in = false; + valuators.RetrieveValuatorData(&ev.valuators); + SendEvent(d_->socket_fd, ev); + Sync(); +} + +void DriverConnection::ButtonDownAbs(std::int32_t button, const Valuators& valuators) +{ + EnsureConnectedToDriver(); + SendButtonEvent(d_->socket_fd, true, true, button, valuators); + Sync(); +} + +void DriverConnection::ButtonDownRel(std::int32_t button, const Valuators& valuators) +{ + EnsureConnectedToDriver(); + SendButtonEvent(d_->socket_fd, true, false, button, valuators); + Sync(); +} + +void DriverConnection::ButtonUpAbs(std::int32_t button, const Valuators& valuators) +{ + EnsureConnectedToDriver(); + SendButtonEvent(d_->socket_fd, false, true, button, valuators); + Sync(); +} + +void DriverConnection::ButtonUpRel(std::int32_t button, const Valuators& valuators) +{ + EnsureConnectedToDriver(); + SendButtonEvent(d_->socket_fd, false, false, button, valuators); + Sync(); +} + +void DriverConnection::KeyDown(std::int32_t key_code) +{ + EnsureConnectedToDriver(); + xf86ITEventKey ev = CreateEvent(XF86IT_EVENT_KEY); + ev.is_press = true; + ev.key_code = key_code + XORG_KEY_CODE_OFFSET; + SendEvent(d_->socket_fd, ev); + Sync(); +} + +void DriverConnection::KeyUp(std::int32_t key_code) +{ + EnsureConnectedToDriver(); + xf86ITEventKey ev = CreateEvent(XF86IT_EVENT_KEY); + ev.is_press = false; + ev.key_code = key_code + XORG_KEY_CODE_OFFSET; + SendEvent(d_->socket_fd, ev); + Sync(); +} + +void DriverConnection::TouchBegin(std::uint32_t id, const Valuators& valuators) +{ + EnsureConnectedToDriver(); + SendTouchEvent(d_->socket_fd, XI_TouchBegin, id, valuators); + Sync(); +} + +void DriverConnection::TouchUpdate(std::uint32_t id, const Valuators& valuators) +{ + EnsureConnectedToDriver(); + SendTouchEvent(d_->socket_fd, XI_TouchUpdate, id, valuators); + Sync(); +} + +void DriverConnection::TouchEnd(std::uint32_t id, const Valuators& valuators) +{ + EnsureConnectedToDriver(); + SendTouchEvent(d_->socket_fd, XI_TouchEnd, id, valuators); + Sync(); +} + +} // namespace xorg +} // namespace testing +} // namespace inputtest diff --git a/gtest/src/inputtest-driver-connection.h b/gtest/src/inputtest-driver-connection.h new file mode 100644 index 0000000000000000000000000000000000000000..af17cac5e9c7d6d2575fba90b5d940db649468fb --- /dev/null +++ b/gtest/src/inputtest-driver-connection.h @@ -0,0 +1,112 @@ +/******************************************************************************* + * + * X testing environment - Google Test environment feat. dummy x server + * + * Copyright (C) 2020 Povilas Kanapickas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ******************************************************************************/ + +#ifndef XORG_INPUTTEST_DRIVER_CONNECTION_H_ +#define XORG_INPUTTEST_DRIVER_CONNECTION_H_ + +#include +#include +#include "xorg/gtest/inputtest/xorg-gtest-valuators.h" + +namespace xorg { +namespace testing { +namespace inputtest { + +/** + * A connection to replay events through the xf86-input-inputtest input driver + */ + +class DriverConnection { +public: + /** + * Create a new driver connection. + * + * @param [in] socket_path Path to the domain socket that will be used to communicate with the + * driver + */ + explicit DriverConnection(const std::string& socket_path); + ~DriverConnection(); + + /** + * Returns the events input path + */ + const std::string& SocketPath() const; + + /** + * Waits for the connection to the driver being established. + * Throws an exception if this can not be achieved within 10 seconds. + */ + void WaitForDriver(); + + /** + * Closes the connection if it is open + */ + void Close(); + + /** + * Synchronizes with the X server. Sends a synchronization event and waits for response. + * This is done after all input events and is thus unnecessary in regular circumstances. + */ + void Sync(); + + /** + * All of the functions below result in one input event being sent to the X server + * via the FIFO files. If the files are not open yet, an exception is thrown. + */ + void RelMotion(const Valuators& valuators); + + void AbsMotion(const Valuators& valuators); + + void ProximityIn(const Valuators& valuators); + void ProximityOut(const Valuators& valuators); + + void ButtonDownAbs(std::int32_t button, const Valuators& valuators); + void ButtonDownRel(std::int32_t button, const Valuators& valuators); + + void ButtonUpAbs(std::int32_t button, const Valuators& valuators); + void ButtonUpRel(std::int32_t button, const Valuators& valuators); + + void KeyDown(std::int32_t key_code); + void KeyUp(std::int32_t key_code); + + void TouchBegin(std::uint32_t id, const Valuators& valuators); + void TouchUpdate(std::uint32_t id, const Valuators& valuators); + void TouchEnd(std::uint32_t id, const Valuators& valuators); + +private: + struct Private; + std::unique_ptr d_; + + bool TryConnectToDriver(); + void EnsureConnectedToDriver(); +}; + +} // namespace inputtest +} // namespace testing +} // namespace xorg + +#endif // XORG_INPUTTEST_DRIVER_CONNECTION_H_ diff --git a/gtest/src/valuators.cpp b/gtest/src/valuators.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5f6cd7f54fb243a8125cb9dc3fa882f322be151b --- /dev/null +++ b/gtest/src/valuators.cpp @@ -0,0 +1,87 @@ +/******************************************************************************* + * + * X testing environment - Google Test environment feat. dummy x server + * + * Copyright (C) 2020 Povilas Kanapickas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ******************************************************************************/ + +#include "xorg/gtest/inputtest/xorg-gtest-valuators.h" +#include + +namespace xorg { +namespace testing { +namespace inputtest { + +static void ValuatorSetBit(std::uint8_t* ptr, unsigned bit) +{ + ptr[bit >> 3] |= 1 << (bit & 7); +} + +struct Valuators::Private { + Private() + { + std::memset(&valuators, 0, sizeof(valuators)); + } + + xf86ITValuatorData valuators; +}; + +Valuators::Valuators() : d_{std::unique_ptr(new Private())} {} + +Valuators::Valuators(const Valuators& other) : d_{std::unique_ptr(new Private())} +{ + *d_ = *other.d_; +} + +Valuators& Valuators::operator=(const Valuators& other) +{ + *d_ = *other.d_; + return *this; +} + +Valuators::~Valuators() {} + +Valuators& Valuators::Set(unsigned axis, double value) +{ + ValuatorSetBit(d_->valuators.mask, axis); + d_->valuators.valuators[axis] = value; + return *this; +} + +Valuators& Valuators::SetUnaccel(unsigned axis, double value_accel, double value_unaccel) +{ + d_->valuators.has_unaccelerated = true; + ValuatorSetBit(d_->valuators.mask, axis); + d_->valuators.valuators[axis] = value_accel; + d_->valuators.unaccelerated[axis] = value_unaccel; + return *this; +} + +void Valuators::RetrieveValuatorData(xf86ITValuatorData* out) const +{ + *out = d_->valuators; +} + +} // namespace inputtest +} // namespace testing +} // namespace xorg diff --git a/meson.build b/meson.build index b3e1a1e8884faa772c574b71b8885b4c2b39c459..a35c9230297edf5829237d671623a3d1f77437a0 100644 --- a/meson.build +++ b/meson.build @@ -59,6 +59,8 @@ xorg_gtest_sources = files( 'gtest/include/xorg/gtest/xorg-gtest-process.h', 'gtest/include/xorg/gtest/xorg-gtest-test.h', 'gtest/include/xorg/gtest/xorg-gtest-xserver.h', + 'gtest/include/xorg/gtest/inputtest/xorg-gtest-device.h', + 'gtest/include/xorg/gtest/inputtest/xorg-gtest-valuators.h', 'gtest/include/xorg/gtest/evemu/xorg-gtest-device.h', 'gtest/include/xorg/gtest/xorg-gtest.h', @@ -68,6 +70,10 @@ xorg_gtest_sources = files( 'gtest/src/device.cpp', 'gtest/src/process.cpp', 'gtest/src/test.cpp', + 'gtest/src/inputtest-device.cpp', + 'gtest/src/inputtest-driver-connection.cpp', + 'gtest/src/inputtest-driver-connection.h', + 'gtest/src/valuators.cpp', 'gtest/src/xserver.cpp', ) @@ -80,6 +86,7 @@ xorg_gtest_dependencies = [ dep_evemu, dep_x11, dep_xi, + dep_xserver, ] lib_xorg_gtest = static_library('xorg-gtest', @@ -93,6 +100,7 @@ lib_xorg_gtest = static_library('xorg-gtest', ) dep_xorg_gtest = declare_dependency(link_with: lib_xorg_gtest, + dependencies: [dep_xserver], include_directories: [gtest_includes, xorg_gtest_incs]) @@ -158,6 +166,8 @@ common_sources = files( 'tests/common/helpers.h', 'tests/common/device-interface.cpp', 'tests/common/device-interface.h', + 'tests/common/device-inputtest-interface.cpp', + 'tests/common/device-inputtest-interface.h', 'tests/common/video-driver-test.h', ) @@ -193,6 +203,7 @@ lib_xit = static_library('xit', ) dep_xit = declare_dependency(link_with: lib_xit, + dependencies: [dep_xorg_gtest], include_directories: xit_includes) diff --git a/tests/common/device-inputtest-interface.cpp b/tests/common/device-inputtest-interface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fb4c8e2e3db3e9a1f552de8b653a3f750bc4123c --- /dev/null +++ b/tests/common/device-inputtest-interface.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 Povilas Kanapickas + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "device-inputtest-interface.h" +#include +#include + +namespace { + // FIXME: this should be replaced with std::filesystem::temp_directory_path() when we can use + // c++17 + std::string GetTemporaryDirectoryPath() + { + const char* env_variables[] = { + "TMPDIR", + "TMP", + "TEMP", + "TEMPDIR" + }; + for (const char* env : env_variables) { + const char* path = std::getenv(env); + if (path) + return path; + } + return "/tmp"; + } + + std::string GetSocketPath(const std::string& identifier) + { + return GetTemporaryDirectoryPath() + "/" + identifier; + } + + int s_device_counter = 0; +} // namespace + +void DeviceInputTestInterface::AddDevice(xorg::testing::inputtest::DeviceType device_type) +{ + s_device_counter++; + auto path = GetSocketPath("xit_events_socket_" + std::to_string(s_device_counter) + "_" + + std::to_string(devices.size())); + devices.push_back(std::unique_ptr( + new xorg::testing::inputtest::Device(path, device_type))); +} + +xorg::testing::inputtest::Device& DeviceInputTestInterface::Dev(unsigned i) +{ + if (i >= devices.size()) { + throw std::runtime_error("Device does not exist"); + } + if (!devices[i]) { + throw std::runtime_error("Device has been removed"); + } + return *devices[i]; +} + +void DeviceInputTestInterface::WaitForDevices() +{ + for (const auto& dev : devices) { + if (!dev) + continue; + dev->WaitForDriver(); + } +} diff --git a/tests/common/device-inputtest-interface.h b/tests/common/device-inputtest-interface.h new file mode 100644 index 0000000000000000000000000000000000000000..6462e7712c85f61b29d344b1dd840024dcb9208b --- /dev/null +++ b/tests/common/device-inputtest-interface.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Povilas Kanapickas + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef DEVICE_INPUTTEST_INTERFACE_H_ +#define DEVICE_INPUTTEST_INTERFACE_H_ + +#include + +/** + * A test fixture for testing X server input processing. + * + * Do not instantiate this class directly, subclass it from the test case + * instead. + */ +class DeviceInputTestInterface { +protected: + xorg::testing::inputtest::Device& Dev(unsigned i); + + void WaitForDevices(); + + void AddDevice(xorg::testing::inputtest::DeviceType device_type); + +private: + // devices must have unique, non-reused IDs within a single test run. If we ever want to remove + // a device, just clear the stored device. + std::vector> devices; +}; + +#endif diff --git a/tests/common/device-interface.h b/tests/common/device-interface.h index 45588fc82998503c982b46af9920ddc07ef02a6f..4afd71ef0c2585fcb003a4e68e964cd090d38bc8 100644 --- a/tests/common/device-interface.h +++ b/tests/common/device-interface.h @@ -36,7 +36,7 @@ * A test fixture for testing input drivers. This class automates basic * device setup throught the server config file. * - * Do not instanciate this class directly, subclass it from the test case + * Do not instantiate this class directly, subclass it from the test case * instead. For simple test cases, use SimpleInputDriverTest. */ class DeviceInterface { diff --git a/tests/common/xorg-conf.cpp b/tests/common/xorg-conf.cpp index aa1359c8516e46fc139dffc328271d995571dca4..5fb716c86179044d7b4143d2ffb78ef09e75122b 100644 --- a/tests/common/xorg-conf.cpp +++ b/tests/common/xorg-conf.cpp @@ -69,6 +69,8 @@ void XOrgConfig::WriteConfig(const std::string &path) { conffile << " Screen 0 \"" << default_device << " screen\" 0 0\n"; for (it = input_devices.begin(); it != input_devices.end(); it++) conffile << " InputDevice \"" << *it << "\"\n"; + for (it = server_layout_opts.begin(); it != server_layout_opts.end(); ++it) + conffile << *it << "\n"; conffile << "EndSection\n"; } @@ -105,6 +107,11 @@ void XOrgConfig::AddDefaultScreenWithDriver(const std::string &driver, sections.push_back(section.str()); } +void XOrgConfig::AddServerLayoutOption(const std::string& option) +{ + server_layout_opts.push_back(option); +} + void XOrgConfig::AddInputSection(const std::string &driver, const std::string &identifier, const std::string &options, diff --git a/tests/common/xorg-conf.h b/tests/common/xorg-conf.h index d6e8836c31202f43b41acda8fd1d5c8822f163f7..6e8ed67c96b73c4f7590e4f15c0724f952bab4bc 100644 --- a/tests/common/xorg-conf.h +++ b/tests/common/xorg-conf.h @@ -86,6 +86,16 @@ public: */ virtual void RemoveConfig(); + /** + * Add an option to the server layout. + * Options may be specified by the caller and must be a single string. + * It is recommened that multiple options are separated by line-breaks + * to improve the readability of the config file. + * + * @param option Option in the form "