diff --git a/KF5WaylandConfig.cmake.in b/KF5WaylandConfig.cmake.in --- a/KF5WaylandConfig.cmake.in +++ b/KF5WaylandConfig.cmake.in @@ -3,3 +3,10 @@ find_dependency(Qt5Gui @REQUIRED_QT_VERSION@) include("${CMAKE_CURRENT_LIST_DIR}/KF5WaylandTargets.cmake") + +function(kwaylandtest testBinaryName) + + add_test(NAME ${testBinaryName}-kwayland-test COMMAND + @CMAKE_INSTALL_FULL_LIBEXECDIR@/org-kde-kf5-kwayland-testserver ${CMAKE_CURRENT_BINARY_DIR}/${testBinaryName} + ) +endfunction() diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(testserver) + include(ECMMarkAsTest) find_package(Qt5Concurrent ${QT_MIN_VERSION} CONFIG QUIET) diff --git a/src/tools/testserver/CMakeLists.txt b/src/tools/testserver/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/tools/testserver/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(org-kde-kf5-kwayland-testserver main.cpp testserver.cpp) +target_link_libraries(org-kde-kf5-kwayland-testserver Qt5::Core KF5::WaylandServer) +install(TARGETS org-kde-kf5-kwayland-testserver DESTINATION ${LIBEXEC_INSTALL_DIR} ) diff --git a/src/tools/testserver/main.cpp b/src/tools/testserver/main.cpp new file mode 100644 --- /dev/null +++ b/src/tools/testserver/main.cpp @@ -0,0 +1,39 @@ +/******************************************************************** +Copyright 2016 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#include "testserver.h" + +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + auto arguments = QCoreApplication::arguments(); + // get rid of our own application path + arguments.removeFirst(); + if (arguments.size() < 1) { + return 1; + } + + TestServer *server = new TestServer(&a); + server->init(); + server->startTestApp(arguments.takeFirst(), arguments); + + return a.exec(); +} diff --git a/src/tools/testserver/testserver.h b/src/tools/testserver/testserver.h new file mode 100644 --- /dev/null +++ b/src/tools/testserver/testserver.h @@ -0,0 +1,65 @@ +/******************************************************************** +Copyright 2016 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#ifndef TESTSERVER_H +#define TESTSERVER_H + +#include +#include +#include +#include + +class QElapsedTimer; +class QTimer; + +namespace KWayland +{ +namespace Server +{ +class Display; +class SeatInterface; +class ShellInterface; +class ShellSurfaceInterface; +} +} + +class TestServer : public QObject +{ + Q_OBJECT +public: + explicit TestServer(QObject *parent); + virtual ~TestServer(); + + void init(); + void startTestApp(const QString &app, const QStringList &arguments); + +private: + void repaint(); + + KWayland::Server::Display *m_display = nullptr; + KWayland::Server::ShellInterface *m_shell = nullptr; + KWayland::Server::SeatInterface *m_seat = nullptr; + QVector m_shellSurfaces; + QTimer *m_repaintTimer; + QScopedPointer m_timeSinceStart; + QPointF m_cursorPos; + QHash m_touchIdMapper; +}; + +#endif diff --git a/src/tools/testserver/testserver.cpp b/src/tools/testserver/testserver.cpp new file mode 100644 --- /dev/null +++ b/src/tools/testserver/testserver.cpp @@ -0,0 +1,200 @@ +/******************************************************************** +Copyright 2016 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#include "testserver.h" +#include "../../server/display.h" +#include "../../server/compositor_interface.h" +#include "../../server/datadevicemanager_interface.h" +#include "../../server/idle_interface.h" +#include "../../server/fakeinput_interface.h" +#include "../../server/seat_interface.h" +#include "../../server/shell_interface.h" +#include "../../server/surface_interface.h" +#include "../../server/subcompositor_interface.h" + +#include +#include +#include +#include +// system +#include +#include +#include + +using namespace KWayland::Server; + +TestServer::TestServer(QObject *parent) + : QObject(parent) + , m_repaintTimer(new QTimer(this)) + , m_timeSinceStart(new QElapsedTimer) + , m_cursorPos(QPointF(0, 0)) +{ +} + +TestServer::~TestServer() = default; + +void TestServer::init() +{ + Q_ASSERT(!m_display); + m_display = new Display(this); + m_display->start(Display::StartMode::ConnectClientsOnly); + m_display->createShm(); + m_display->createCompositor()->create(); + m_shell = m_display->createShell(m_display); + connect(m_shell, &ShellInterface::surfaceCreated, this, + [this] (ShellSurfaceInterface *surface) { + m_shellSurfaces << surface; + // TODO: pass keyboard/pointer/touch focus on mapped + connect(surface, &QObject::destroyed, this, + [this, surface] { + m_shellSurfaces.removeOne(surface); + } + ); + } + ); + + m_shell->create(); + m_seat = m_display->createSeat(m_display); + m_seat->setHasKeyboard(true); + m_seat->setHasPointer(true); + m_seat->setHasTouch(true); + m_seat->create(); + m_display->createDataDeviceManager(m_display)->create(); + m_display->createIdle(m_display)->create(); + m_display->createSubCompositor(m_display)->create(); + // output + auto output = m_display->createOutput(m_display); + const QSize size(1280, 1024); + output->setGlobalPosition(QPoint(0, 0)); + output->setPhysicalSize(size / 3.8); + output->addMode(size); + output->create(); + + auto fakeInput = m_display->createFakeInput(m_display); + fakeInput->create(); + connect(fakeInput, &FakeInputInterface::deviceCreated, this, + [this] (FakeInputDevice *device) { + device->setAuthentication(true); + connect(device, &FakeInputDevice::pointerMotionRequested, this, + [this] (const QSizeF &delta) { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + m_cursorPos = m_cursorPos + QPointF(delta.width(), delta.height()); + m_seat->setPointerPos(m_cursorPos); + } + ); + connect(device, &FakeInputDevice::pointerButtonPressRequested, this, + [this] (quint32 button) { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + m_seat->pointerButtonPressed(button); + } + ); + connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, + [this] (quint32 button) { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + m_seat->pointerButtonReleased(button); + } + ); + connect(device, &FakeInputDevice::pointerAxisRequested, this, + [this] (Qt::Orientation orientation, qreal delta) { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + m_seat->pointerAxis(orientation, delta); + } + ); + connect(device, &FakeInputDevice::touchDownRequested, this, + [this] (quint32 id, const QPointF &pos) { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + m_touchIdMapper.insert(id, m_seat->touchDown(pos)); + } + ); + connect(device, &FakeInputDevice::touchMotionRequested, this, + [this] (quint32 id, const QPointF &pos) { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + const auto it = m_touchIdMapper.constFind(id); + if (it != m_touchIdMapper.constEnd()) { + m_seat->touchMove(it.value(), pos); + } + } + ); + connect(device, &FakeInputDevice::touchUpRequested, this, + [this] (quint32 id) { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + const auto it = m_touchIdMapper.find(id); + if (it != m_touchIdMapper.end()) { + m_seat->touchUp(it.value()); + m_touchIdMapper.erase(it); + } + } + ); + connect(device, &FakeInputDevice::touchCancelRequested, this, + [this] { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + m_seat->cancelTouchSequence(); + } + ); + connect(device, &FakeInputDevice::touchFrameRequested, this, + [this] { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + m_seat->touchFrame(); + } + ); + } + ); + + m_repaintTimer->setInterval(1000 / 60); + connect(m_repaintTimer, &QTimer::timeout, this, &TestServer::repaint); + m_repaintTimer->start(); + m_timeSinceStart->start(); +} + +void TestServer::startTestApp(const QString &app, const QStringList &arguments) +{ + int sx[2]; + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { + QCoreApplication::instance()->exit(1); + return; + } + m_display->createClient(sx[0]); + int socket = dup(sx[1]); + if (socket == -1) { + QCoreApplication::instance()->exit(1); + return; + } + QProcess *p = new QProcess(this); + p->setProcessChannelMode(QProcess::ForwardedChannels); + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); + environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland")); + environment.insert(QStringLiteral("WAYLAND_SOCKET"), QString::fromUtf8(QByteArray::number(socket))); + p->setProcessEnvironment(environment); + auto finishedSignal = static_cast(&QProcess::finished); + connect(p, finishedSignal, QCoreApplication::instance(), &QCoreApplication::exit); + auto errorSignal = static_cast(&QProcess::error); + connect(p, errorSignal, this, + [] { + QCoreApplication::instance()->exit(1); + } + ); + p->start(app, arguments); +} + +void TestServer::repaint() +{ + for (auto it = m_shellSurfaces.constBegin(), end = m_shellSurfaces.constEnd(); it != end; ++it) { + (*it)->surface()->frameRendered(m_timeSinceStart->elapsed()); + } +}