Create a new type of mock compositor for client tests

There are a number of issues with the current client testing:

- Adding new compositor functionality is cumbersome (need to add compositor
  send method, command, implementation, not to mention creating new wrapper
  objects.
- Customizing available globals and their versions is not possible and would be
  hard to implement. I.e. how to test that functionality works with old and new
  versions of an interface? Handle globals being destroyed. We did this with
  wl_output, but it was painfully cumbersome.
- Hard to verify that the compositor state is clean between tests. It is
  currently done in some tests, but requires boiler plate code which needs to
  be added and maintained for each test.
- In general lots of boiler-plate for new tests. (We have to have separate
  tests as long as Qt has global/static state. I.e. if one shell extension has
  been initialized, we can't deinitialize and initialize another one, so tests
  have to be separate.)
- Dispatching server events tied to the client event loop sometimes makes it
  hard to write tests without deadlocks.
- Abstraction, encapsulation and automatic behavior that can't be disabled
  makes it hard to test low-level functionality like surface exposure.

So, in an attempt to mitigate these issues, I wrote a new testing framework.

- Compositor dispatch is running continuously in it's own thread, access to
  compositor state is guarded by a mutex on the compositor, locking this will
  make dispatching stop, so the test can safely access internals. Although a
  bit cumbersome at first this makes it much easier to directly use server
  protocol commands from the test itself, i.e. no need to create commands for
  every single thing we want to test.
- The CoreCompositor::exec template method can accept a lambda that will be run
  with dispatching stopped. It can also return a value, conveniently letting us
  safely extract or modify compositor state from tests.
- This framework also takes full advantage of the qtwaylandscanner, using
  wrapper classes for everything, reducing boiler plate considerably.
- The compositor parts are designed to do as little as possible automatically,
  but still provide easy ways to enable common functionality, like releasing
  buffers automatically, configuring shell surfaces etc.
- Compositor globals are pluggable, use add<GlobalClass>() and
  remove<GlobalClass>() to add new global interfaces. I.e. easy to create a
  compositor with or without data_device_manager for instance.
- DefaultCompositor provides a sensible default set of functionality and
  convenience methods for most test-cases. Custom ones can still be made by
  inheriting from CoreCompositor directly instead or by removing or adding
  globals to DefaultCompositor.
- Globals have an isClean() method. Implement it to verify that the client
  didn't leave any objects lying around from the previous test.
  CoreCompositor::isClean calls isClean on the globals so a single call is all
  that's needed.

In short, we've traded mock compositor encapsulation and thread safety
guarantees for less boiler-plate, easier and more convenient access to
internals.

Anything accessing compositor state should go into a exec() call, or through
the wrapper macros QCOMPOSITOR_VERIFY and QCOMPOSITOR_COMPARE (or the TRY
versions). I've also tried to make the compositor print warnings if compositor
state is accessed in an unsafe way.

The mock compositor is currently built once per test due to CI limitations
(same thing as with the old tests).

Change-Id: Ia3feb80ce175d3814292b7f4768a0cc719f8b0e8
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
Johan Klokkhammer Helsing 2018-10-24 08:46:06 +02:00 committed by Johan Helsing
parent 6297c0aa7f
commit f6dd4ab120
37 changed files with 2960 additions and 759 deletions

View File

@ -4,5 +4,8 @@ SUBDIRS += \
client \
fullscreenshellv1 \
iviapplication \
xdgshellv6 \
wl_connect
seatv4 \
surface \
wl_connect \
xdgshell \
xdgshellv6

View File

@ -1,4 +1,4 @@
include (../shared/shared.pri)
include (../shared_old/shared_old.pri)
TARGET = tst_client
SOURCES += tst_client.cpp

View File

@ -1,4 +1,4 @@
include (../shared/shared.pri)
include (../shared_old/shared_old.pri)
TARGET = tst_client_fullscreenshell1
SOURCES += tst_fullscreenshellv1.cpp

View File

@ -1,4 +1,4 @@
include (../shared/shared.pri)
include (../shared_old/shared_old.pri)
TARGET = tst_client_iviapplication
SOURCES += tst_iviapplication.cpp

View File

@ -0,0 +1,4 @@
include (../shared/shared.pri)
TARGET = tst_seatv4
SOURCES += tst_seatv4.cpp

View File

@ -0,0 +1,266 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mockcompositor.h"
#include <QtGui/QRasterWindow>
#include <QtGui/QOpenGLWindow>
using namespace MockCompositor;
// wl_seat version 5 was introduced in wayland 1.10, and although that's pretty old,
// there are still compositors that have yet to update their implementation to support
// the new version (most importantly our own QtWaylandCompositor).
// As long as that's the case, this test makes sure input events still works on version 4.
class SeatV4Compositor : public DefaultCompositor {
public:
explicit SeatV4Compositor()
{
exec([this] {
m_config.autoConfigure = true;
removeAll<Seat>();
uint capabilities = MockCompositor::Seat::capability_pointer;
int version = 4;
add<Seat>(capabilities, version);
});
}
};
class tst_seatv4 : public QObject, private SeatV4Compositor
{
Q_OBJECT
private slots:
void cleanup();
void bindsToSeat();
void createsPointer();
void setsCursorOnEnter();
void usesEnterSerial();
void simpleAxis_data();
void simpleAxis();
void invalidPointerEvents();
void scaledCursor();
};
void tst_seatv4::cleanup()
{
QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage()));
QCOMPOSITOR_COMPARE(getAll<Output>().size(), 1); // No extra outputs left
}
void tst_seatv4::bindsToSeat()
{
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1);
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 4);
}
void tst_seatv4::createsPointer()
{
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1);
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 4);
}
void tst_seatv4::setsCursorOnEnter()
{
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
}
void tst_seatv4::usesEnterSerial()
{
QSignalSpy setCursorSpy(exec([=] { return pointer(); }), &Pointer::setCursor);
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
uint enterSerial = exec([=] {
return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32});
});
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
QTRY_COMPARE(setCursorSpy.count(), 1);
QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial);
}
void tst_seatv4::simpleAxis_data()
{
QTest::addColumn<uint>("axis");
QTest::addColumn<qreal>("value");
QTest::addColumn<Qt::Orientation>("orientation");
QTest::addColumn<QPoint>("angleDelta");
// Directions in regular windows/linux terms (no "natural" scrolling)
QTest::newRow("down") << uint(Pointer::axis_vertical_scroll) << 1.0 << Qt::Vertical << QPoint{0, -12};
QTest::newRow("up") << uint(Pointer::axis_vertical_scroll) << -1.0 << Qt::Vertical << QPoint{0, 12};
QTest::newRow("left") << uint(Pointer::axis_horizontal_scroll) << 1.0 << Qt::Horizontal << QPoint{-12, 0};
QTest::newRow("right") << uint(Pointer::axis_horizontal_scroll) << -1.0 << Qt::Horizontal << QPoint{12, 0};
QTest::newRow("up big") << uint(Pointer::axis_vertical_scroll) << -10.0 << Qt::Vertical << QPoint{0, 120};
}
void tst_seatv4::simpleAxis()
{
QFETCH(uint, axis);
QFETCH(qreal, value);
QFETCH(Qt::Orientation, orientation);
QFETCH(QPoint, angleDelta);
class WheelWindow : QRasterWindow {
public:
explicit WheelWindow()
{
resize(64, 64);
show();
}
void wheelEvent(QWheelEvent *event) override
{
QRasterWindow::wheelEvent(event);
// Angle delta should always be provided (says docs)
QVERIFY(!event->angleDelta().isNull());
// There are now scroll phases on Wayland prior to v5
QCOMPARE(event->phase(), Qt::NoScrollPhase);
// Pixel delta should only be set if we know it's a high-res input device (which we don't)
QCOMPARE(event->pixelDelta(), QPoint(0, 0));
// The axis vector of the event is already in surface space, so there is now way to tell
// whether it is inverted or not.
QCOMPARE(event->inverted(), false);
// We didn't press any buttons
QCOMPARE(event->buttons(), Qt::NoButton);
if (event->orientation() == Qt::Horizontal)
QCOMPARE(event->delta(), event->angleDelta().x());
else
QCOMPARE(event->delta(), event->angleDelta().y());
// There has been no information about what created the event.
// Documentation says not synthesized is appropriate in such cases
QCOMPARE(event->source(), Qt::MouseEventNotSynthesized);
m_events.append(Event(event->pixelDelta(), event->angleDelta(), event->orientation()));
}
struct Event // Because I didn't find a convenient way to copy it entirely
{
// TODO: Constructors can be removed when we start supporting brace-initializers
Event() = default;
Event(const QPoint &pixelDelta, const QPoint &angleDelta, Qt::Orientation orientation)
: pixelDelta(pixelDelta), angleDelta(angleDelta), orientation(orientation)
{}
const QPoint pixelDelta;
const QPoint angleDelta; // eights of a degree, positive is upwards, left
const Qt::Orientation orientation{};
};
QVector<Event> m_events;
};
WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
Surface *surface = xdgSurface()->m_surface;
pointer()->sendEnter(surface, {32, 32});
wl_client *client = surface->resource()->client();
// Length of vector in surface-local space. i.e. positive is downwards
pointer()->sendAxis(
client,
Pointer::axis(axis),
value // Length of vector in surface-local space. i.e. positive is downwards
);
});
QTRY_COMPARE(window.m_events.size(), 1);
auto event = window.m_events.takeFirst();
QCOMPARE(event.angleDelta, angleDelta);
QCOMPARE(event.orientation, orientation);
}
void tst_seatv4::invalidPointerEvents()
{
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *p = pointer();
auto *c = client();
// Purposefully send events without a wl_pointer.enter
p->sendMotion(c, {32, 32});
p->sendButton(c, BTN_LEFT, Pointer::button_state_pressed);
p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0);
});
// Make sure we get here without crashing
xdgPingAndWaitForPong();
}
void tst_seatv4::scaledCursor()
{
QSKIP("Currently broken and should be fixed");
// Add a highdpi output
exec([&] {
int scale = 2;
add<Output>(scale);
});
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface()->m_committed.buffer);
QCOMPOSITOR_TRY_COMPARE(pointer()->cursorSurface()->m_committed.bufferScale, 1);
QSize unscaledPixelSize = exec([=] {
return pointer()->cursorSurface()->m_committed.buffer->size();
});
exec([=] {
auto *surface = pointer()->cursorSurface();
surface->sendEnter(getAll<Output>()[1]);
surface->sendLeave(getAll<Output>()[0]);
});
QCOMPOSITOR_TRY_COMPARE(pointer()->cursorSurface()->m_committed.buffer->size(), unscaledPixelSize * 2);
// Remove the extra output to clean up for the next test
exec([&] { remove(getAll<Output>()[1]); });
}
QCOMPOSITOR_TEST_MAIN(tst_seatv4)
#include "tst_seatv4.moc"

View File

@ -0,0 +1,140 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "corecompositor.h"
namespace MockCompositor {
CoreCompositor::CoreCompositor()
: m_display(wl_display_create())
, m_socketName(wl_display_add_socket_auto(m_display))
, m_eventLoop(wl_display_get_event_loop(m_display))
// Start dispatching
, m_dispatchThread([this](){
while (m_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
dispatch();
}
})
{
m_timer.start();
Q_ASSERT(isClean());
}
CoreCompositor::~CoreCompositor()
{
m_running = false;
m_dispatchThread.join();
wl_display_destroy(m_display);
}
bool CoreCompositor::isClean()
{
Lock lock(this);
for (auto *global : qAsConst(m_globals)) {
if (!global->isClean())
return false;
}
return true;
}
QString CoreCompositor::dirtyMessage()
{
Lock lock(this);
QStringList messages;
for (auto *global : qAsConst(m_globals)) {
if (!global->isClean())
messages << (global->metaObject()->className() % QLatin1String(": ") % global->dirtyMessage());
}
return messages.join(", ");
}
void CoreCompositor::dispatch()
{
Lock lock(this);
wl_display_flush_clients(m_display);
constexpr int timeout = 0; // immediate return
wl_event_loop_dispatch(m_eventLoop, timeout);
}
/*!
* \brief Adds a new global interface for the compositor
*
* Takes ownership of \a global
*/
void CoreCompositor::add(Global *global)
{
warnIfNotLockedByThread(Q_FUNC_INFO);
m_globals.append(global);
}
void CoreCompositor::remove(Global *global)
{
warnIfNotLockedByThread(Q_FUNC_INFO);
//TODO: Need to delete global as well!
m_globals.removeAll(global);
}
uint CoreCompositor::nextSerial()
{
warnIfNotLockedByThread(Q_FUNC_INFO);
return wl_display_next_serial(m_display);
}
uint CoreCompositor::currentTimeMilliseconds()
{
warnIfNotLockedByThread(Q_FUNC_INFO);
return uint(m_timer.elapsed());
}
wl_client *CoreCompositor::client(int index)
{
warnIfNotLockedByThread(Q_FUNC_INFO);
wl_list *clients = wl_display_get_client_list(m_display);
wl_client *client = nullptr;
int i = 0;
wl_client_for_each(client, clients) {
if (i++ == index)
return client;
}
return nullptr;
}
void CoreCompositor::warnIfNotLockedByThread(const char *caller)
{
if (!m_lock || !m_lock->isOwnedByCurrentThread()) {
qWarning() << caller << "called without locking the compositor to the current thread."
<< "This means the compositor can start dispatching at any moment,"
<< "potentially leading to threading issues."
<< "Unless you know what you are doing you should probably fix the test"
<< "by locking the compositor before accessing it (see mutex()).";
}
}
} // namespace MockCompositor

View File

@ -0,0 +1,209 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef MOCKCOMPOSITOR_CORECOMPOSITOR_H
#define MOCKCOMPOSITOR_CORECOMPOSITOR_H
#include <QtTest/QtTest>
#include <wayland-server-core.h>
struct wl_resource;
namespace MockCompositor {
class Global : public QObject
{
Q_OBJECT
public:
virtual bool isClean() { return true; }
virtual QString dirtyMessage() { return isClean() ? "clean" : "dirty"; }
};
class CoreCompositor
{
public:
explicit CoreCompositor();
~CoreCompositor();
bool isClean();
QString dirtyMessage();
void dispatch();
template<typename function_type, typename... arg_types>
auto exec(function_type func, arg_types&&... args) -> decltype(func())
{
Lock lock(this);
return func(std::forward<arg_types>(args)...);
}
template<typename function_type, typename... arg_types>
auto call(function_type func, arg_types&&... args) -> decltype(func())
{
Lock lock(this);
auto boundFunc = std::bind(func, this);
return boundFunc(this, std::forward<arg_types>(args)...);
}
// Unsafe section below, YOU are responsible that the compositor is locked or
// this is run through the mutex() method!
void add(Global *global);
void remove(Global *global);
/*!
* \brief Constructs and adds a new global with the given parameters
*
* Convenience function. i.e.
*
* compositor->add(new MyGlobal(compositor, version);
*
* can be written as:
*
* compositor->add<MyGlobal>(version);
*
* Returns the new global
*/
template<typename global_type, typename... arg_types>
global_type *add(arg_types&&... args)
{
warnIfNotLockedByThread(Q_FUNC_INFO);
auto *global = new global_type(this, std::forward<arg_types>(args)...);
m_globals.append(global);
return global;
}
/*!
* \brief Removes all globals of the given type
*
* Convenience function
*/
template<typename global_type, typename... arg_types>
void removeAll()
{
const auto globals = getAll<global_type>();
for (auto global : globals)
remove(global);
}
/*!
* \brief Returns a global with the given type, if any
*/
template<typename global_type>
global_type *get()
{
warnIfNotLockedByThread(Q_FUNC_INFO);
for (auto *global : qAsConst(m_globals)) {
if (auto *casted = qobject_cast<global_type *>(global))
return casted;
}
return nullptr;
}
/*!
* \brief Returns all globals with the given type, if any
*/
template<typename global_type>
QVector<global_type *> getAll()
{
warnIfNotLockedByThread(Q_FUNC_INFO);
QVector<global_type *> matching;
for (auto *global : qAsConst(m_globals)) {
if (auto *casted = qobject_cast<global_type *>(global))
matching.append(casted);
}
return matching;
}
uint nextSerial();
uint currentTimeMilliseconds();
wl_client *client(int index = 0);
void warnIfNotLockedByThread(const char* caller = "warnIfNotLockedbyThread");
public:
// Only use this carefully from the test thread (i.e. lock first)
wl_display *m_display = nullptr;
protected:
class Lock {
public:
explicit Lock(CoreCompositor *compositor)
: m_compositor(compositor)
, m_threadId(std::this_thread::get_id())
{
// Can't use a QMutexLocker here, as it's not movable
compositor->m_mutex.lock();
Q_ASSERT(compositor->m_lock == nullptr);
compositor->m_lock = this;
}
~Lock()
{
Q_ASSERT(m_compositor->m_lock == this);
m_compositor->m_lock = nullptr;
m_compositor->m_mutex.unlock();
}
// Move semantics
Lock(Lock &&) = default;
Lock &operator=(Lock &&) = default;
// Disable copying
Lock(const Lock &) = delete;
Lock &operator=(const Lock &) = delete;
bool isOwnedByCurrentThread() const { return m_threadId == std::this_thread::get_id(); }
private:
CoreCompositor *m_compositor = nullptr;
std::thread::id m_threadId;
};
QByteArray m_socketName;
wl_event_loop *m_eventLoop = nullptr;
bool m_running = true;
QVector<Global *> m_globals;
QElapsedTimer m_timer;
private:
Lock *m_lock = nullptr;
QMutex m_mutex;
std::thread m_dispatchThread;
};
template<typename container_type>
QByteArray toByteArray(container_type container)
{
return QByteArray(reinterpret_cast<const char *>(container.data()), sizeof (container[0]) * container.size());
}
template<typename return_type>
return_type *fromResource(::wl_resource *resource) {
if (auto *r = return_type::Resource::fromResource(resource))
return static_cast<return_type *>(r->object());
return nullptr;
}
} // namespace MockCompositor
#endif // MOCKCOMPOSITOR_CORECOMPOSITOR_H

View File

@ -0,0 +1,322 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "coreprotocol.h"
namespace MockCompositor {
void Surface::sendFrameCallbacks()
{
uint time = m_wlCompositor->m_compositor->currentTimeMilliseconds();
for (auto *callback : m_waitingFrameCallbacks)
callback->sendDone(time);
m_waitingFrameCallbacks.clear();
}
void Surface::sendEnter(Output *output)
{
m_outputs.append(output);
const auto outputResources = output->resourceMap().values(resource()->client());
for (auto outputResource: outputResources)
wl_surface::send_enter(resource()->handle, outputResource->handle);
}
void Surface::sendLeave(Output *output)
{
m_outputs.removeOne(output);
const auto outputResources = output->resourceMap().values(resource()->client());
for (auto outputResource: outputResources)
wl_surface::send_leave(resource()->handle, outputResource->handle);
}
void Surface::surface_destroy_resource(Resource *resource)
{
Q_UNUSED(resource);
for (auto *commit : m_commits)
delete commit->commitSpecific.frame;
bool removed = m_wlCompositor->m_surfaces.removeOne(this);
Q_ASSERT(removed);
delete this;
}
void Surface::surface_attach(Resource *resource, wl_resource *buffer, int32_t x, int32_t y)
{
Q_UNUSED(resource);
QPoint offset(x, y);
m_pending.buffer = fromResource<Buffer>(buffer);
m_pending.commitSpecific.attachOffset = offset;
m_pending.commitSpecific.attached = true;
emit attach(buffer, offset);
}
void Surface::surface_set_buffer_scale(QtWaylandServer::wl_surface::Resource *resource, int32_t scale)
{
Q_UNUSED(resource);
m_pending.bufferScale = scale;
}
void Surface::surface_commit(Resource *resource)
{
Q_UNUSED(resource);
m_committed = m_pending;
m_commits.append(new DoubleBufferedState(m_committed));
if (auto *frame = m_pending.commitSpecific.frame)
m_waitingFrameCallbacks.append(frame);
m_pending.commitSpecific = PerCommitData();
emit commit();
if (m_committed.commitSpecific.attached)
emit bufferCommitted();
}
void Surface::surface_frame(Resource *resource, uint32_t callback)
{
// Although valid, there is really no point having multiple frame requests in the same commit.
// Make sure we don't do it
QCOMPARE(m_pending.commitSpecific.frame, nullptr);
auto *frame = new Callback(resource->client(), callback, 1);
m_pending.commitSpecific.frame = frame;
}
bool WlCompositor::isClean() {
for (auto *surface : qAsConst(m_surfaces)) {
if (!CursorRole::fromSurface(surface))
return false;
}
return true;
}
QString WlCompositor::dirtyMessage()
{
if (isClean())
return "clean";
QStringList messages;
for (auto *s : qAsConst(m_surfaces)) {
QString role = s->m_role ? s->m_role->staticMetaObject.className(): "none/unknown";
messages << "Surface with role: " + role;
}
return "Dirty, surfaces left:\n\t" + messages.join("\n\t");
}
void Output::sendScale(int factor)
{
Q_ASSERT(m_version >= WL_OUTPUT_SCALE_SINCE_VERSION);
m_scale = factor;
const auto resources = resourceMap().values();
for (auto r: resources)
wl_output::send_scale(r->handle, factor);
}
void Output::sendDone()
{
Q_ASSERT(m_version >= WL_OUTPUT_DONE_SINCE_VERSION);
const auto resources = resourceMap().values();
for (auto r: resources)
wl_output::send_done(r->handle);
}
void Output::output_bind_resource(QtWaylandServer::wl_output::Resource *resource)
{
if (m_version >= WL_OUTPUT_SCALE_SINCE_VERSION)
wl_output::send_scale(resource->handle, m_scale);
//TODO: send other required stuff as well
if (m_version >= WL_OUTPUT_DONE_SINCE_VERSION)
wl_output::send_done(resource->handle);
}
// Seat stuff
Seat::Seat(CoreCompositor *compositor, uint capabilities, int version) //TODO: check version
: QtWaylandServer::wl_seat(compositor->m_display, version)
, m_compositor(compositor)
{
setCapabilities(capabilities);
}
Seat::~Seat()
{
qDeleteAll(m_oldPointers);
delete m_pointer;
}
void Seat::setCapabilities(uint capabilities) {
// TODO: Add support for touch and keyboard
Q_ASSERT(capabilities == 0 || capabilities == capability_pointer);
m_capabilities = capabilities;
if (m_capabilities & capability_pointer) {
if (!m_pointer)
m_pointer = (new Pointer(this));
} else if (m_pointer) {
m_oldPointers << m_pointer;
m_pointer = nullptr;
}
for (auto *resource : resourceMap())
wl_seat::send_capabilities(resource->handle, capabilities);
}
void Seat::seat_get_pointer(Resource *resource, uint32_t id)
{
if (~m_capabilities & capability_pointer) {
qWarning() << "Client requested a wl_pointer without the capability being available."
<< "This Could be a race condition when hotunplugging,"
<< "but is most likely a client error";
Pointer *pointer = new Pointer(this);
pointer->add(resource->client(), id, resource->version());
// TODO: mark as destroyed
m_oldPointers << pointer;
return;
}
m_pointer->add(resource->client(), id, resource->version());
}
Surface *Pointer::cursorSurface()
{
return m_cursorRole ? m_cursorRole->m_surface : nullptr;
}
uint Pointer::sendEnter(Surface *surface, const QPointF &position)
{
wl_fixed_t x = wl_fixed_from_double(position.x());
wl_fixed_t y = wl_fixed_from_double(position.y());
m_enterSerial = m_seat->m_compositor->nextSerial();
wl_client *client = surface->resource()->client();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_enter(r->handle, m_enterSerial, surface->resource()->handle, x ,y);
return m_enterSerial;
}
// Make sure you call enter, frame etc. first
void Pointer::sendMotion(wl_client *client, const QPointF &position)
{
wl_fixed_t x = wl_fixed_from_double(position.x());
wl_fixed_t y = wl_fixed_from_double(position.y());
auto time = m_seat->m_compositor->currentTimeMilliseconds();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_motion(r->handle, time, x, y);
}
// Make sure you call enter, frame etc. first
uint Pointer::sendButton(wl_client *client, uint button, uint state)
{
Q_ASSERT(state == button_state_pressed || state == button_state_released);
auto time = m_seat->m_compositor->currentTimeMilliseconds();
uint serial = m_seat->m_compositor->nextSerial();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_button(r->handle, serial, time, button, state);
return serial;
}
// Make sure you call enter, frame etc. first
void Pointer::sendAxis(wl_client *client, axis axis, qreal value)
{
auto time = m_seat->m_compositor->currentTimeMilliseconds();
wl_fixed_t val = wl_fixed_from_double(value);
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_axis(r->handle, time, axis, val);
}
void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y)
{
Q_UNUSED(resource);
Q_UNUSED(hotspot_x);
Q_UNUSED(hotspot_y);
auto *s = fromResource<Surface>(surface);
QVERIFY(s);
if (s->m_role) {
auto *cursorRole = CursorRole::fromSurface(s);
QVERIFY(cursorRole);
QVERIFY(cursorRole == m_cursorRole);
} else {
m_cursorRole = new CursorRole(s); //TODO: make sure we don't leak CursorRole
s->m_role = m_cursorRole;
}
// QCOMPARE(serial, m_enterSerial); //TODO: uncomment when this bug is fixed
emit setCursor(serial);
}
// Shm implementation
Shm::Shm(CoreCompositor *compositor, QVector<format> formats, int version)
: QtWaylandServer::wl_shm(compositor->m_display, version)
, m_compositor(compositor)
, m_formats(formats)
{
// Some formats are specified as mandatory
Q_ASSERT(m_formats.contains(format_argb8888));
Q_ASSERT(m_formats.contains(format_xrgb8888));
}
bool Shm::isClean()
{
// for (ShmPool *pool : qAsConst(m_pools)) {
// //TODO: return false if not cursor buffer
// if (pool->m_buffers.isEmpty()) {
// return false;
// }
// }
return true;
}
void Shm::shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size)
{
Q_UNUSED(fd);
Q_UNUSED(size);
auto *pool = new ShmPool(this, resource->client(), id, 1);
m_pools.append(pool);
}
ShmPool::ShmPool(Shm *shm, wl_client *client, int id, int version)
: QtWaylandServer::wl_shm_pool(client, id, version)
, m_shm(shm)
{
}
void ShmPool::shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format)
{
QSize size(width, height);
new ShmBuffer(offset, size, stride, Shm::format(format), resource->client(), id);
}
void ShmPool::shm_pool_destroy_resource(Resource *resource)
{
Q_UNUSED(resource);
bool removed = m_shm->m_pools.removeOne(this);
Q_ASSERT(removed);
delete this;
}
} // namespace MockCompositor

View File

@ -0,0 +1,313 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef MOCKCOMPOSITOR_COREPROTOCOL_H
#define MOCKCOMPOSITOR_COREPROTOCOL_H
#include "corecompositor.h"
#include <qwayland-server-wayland.h>
namespace MockCompositor {
class WlCompositor;
class Output;
class Pointer;
class CursorRole;
class ShmPool;
class ShmBuffer;
class Buffer : public QObject, public QtWaylandServer::wl_buffer
{
Q_OBJECT
public:
explicit Buffer(wl_client *client, int id, int version)
: QtWaylandServer::wl_buffer(client, id, version)
{
}
virtual QSize size() const = 0;
bool m_destroyed = false;
protected:
void buffer_destroy_resource(Resource *resource) override
{
Q_UNUSED(resource);
m_destroyed = true;
// The client side resource has been destroyed, but we keep this object because it may be
// be used as a reference by e.g. surface for the currently committed buffer so it's not
// yet safe to free it.
//TODO: The memory should be freed by its factory
}
void buffer_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); }
};
class Callback : public QObject, public QtWaylandServer::wl_callback
{
Q_OBJECT
public:
explicit Callback(wl_client *client, int id, int version = 1)
: QtWaylandServer::wl_callback(client, id, version)
{
}
~Callback() override { if (!m_destroyed) wl_resource_destroy(resource()->handle); }
void send_done(uint32_t data) = delete; // use state-tracking method below instead
void sendDone(uint data) { Q_ASSERT(!m_done); QtWaylandServer::wl_callback::send_done(data); m_done = true; }
void sendDoneAndDestroy(uint data) { sendDone(data); wl_resource_destroy(resource()->handle); }
bool m_done = false;
bool m_destroyed = false;
protected:
void callback_destroy_resource(Resource *resource) override { Q_UNUSED(resource); m_destroyed = true; }
};
class SurfaceRole : public QObject {
Q_OBJECT
};
class Surface : public QObject, public QtWaylandServer::wl_surface
{
Q_OBJECT
public:
explicit Surface(WlCompositor *wlCompositor, wl_client *client, int id, int version)
: QtWaylandServer::wl_surface(client, id, version)
, m_wlCompositor(wlCompositor)
{
}
~Surface() override { qDeleteAll(m_commits); } // TODO: maybe make sure buffers are released?
void sendFrameCallbacks();
void sendEnter(Output *output);
void send_enter(::wl_resource *output) = delete;
void sendLeave(Output *output);
void send_leave(::wl_resource *output) = delete;
WlCompositor *m_wlCompositor;
struct PerCommitData {
Callback *frame = nullptr;
QPoint attachOffset;
bool attached = false;
};
struct DoubleBufferedState {
PerCommitData commitSpecific;
Buffer *buffer = nullptr;
uint configureSerial = 0;
int bufferScale = 1;
} m_pending, m_committed;
QVector<DoubleBufferedState *> m_commits;
QVector<Callback *> m_waitingFrameCallbacks;
QVector<Output *> m_outputs;
SurfaceRole *m_role = nullptr;
signals:
void attach(void *buffer, QPoint offset);
void commit();
void bufferCommitted();
protected:
void surface_destroy_resource(Resource *resource) override;
void surface_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); }
void surface_attach(Resource *resource, wl_resource *buffer, int32_t x, int32_t y) override;
void surface_set_buffer_scale(Resource *resource, int32_t scale) override;
void surface_commit(Resource *resource) override;
void surface_frame(Resource *resource, uint32_t callback) override;
};
class WlCompositor : public Global, public QtWaylandServer::wl_compositor
{
Q_OBJECT
public:
explicit WlCompositor(CoreCompositor *compositor, int version = 3)
: QtWaylandServer::wl_compositor(compositor->m_display, version)
, m_compositor(compositor)
{}
bool isClean() override;
QString dirtyMessage() override;
QVector<Surface *> m_surfaces;
CoreCompositor *m_compositor = nullptr;
signals:
void surfaceCreated(Surface *surface);
protected:
void compositor_create_surface(Resource *resource, uint32_t id) override
{
auto *surface = new Surface(this, resource->client(), id, resource->version());
m_surfaces.append(surface);
emit surfaceCreated(surface);
}
};
class SubCompositor : public Global, public QtWaylandServer::wl_subcompositor
{
Q_OBJECT
public:
explicit SubCompositor(CoreCompositor *compositor, int version = 1)
: QtWaylandServer::wl_subcompositor(compositor->m_display, version)
{}
// TODO
};
class Output : public Global, public QtWaylandServer::wl_output
{
Q_OBJECT
public:
explicit Output(CoreCompositor *compositor, int scale = 1, int version = 2)
: QtWaylandServer::wl_output(compositor->m_display, version)
, m_scale(scale)
, m_version(version)
{}
void sendScale(int factor);
void send_scale(int32_t factor) = delete;
void send_scale(struct ::wl_resource *resource, int32_t factor) = delete;
void sendDone();
int m_scale = 1;
int m_version = 1; // TODO: remove on libwayland upgrade
protected:
void output_bind_resource(Resource *resource) override;
};
class Seat : public Global, public QtWaylandServer::wl_seat
{
Q_OBJECT
public:
explicit Seat(CoreCompositor *compositor, uint capabilities, int version = 4);
~Seat() override;
void send_capabilities(Resource *resource, uint capabilities) = delete; // Use wrapper instead
void send_capabilities(uint capabilities) = delete; // Use wrapper instead
void setCapabilities(uint capabilities);
CoreCompositor *m_compositor = nullptr;
Pointer* m_pointer = nullptr;
QVector<Pointer *> m_oldPointers;
uint m_capabilities = 0;
protected:
void seat_bind_resource(Resource *resource) override
{
wl_seat::send_capabilities(resource->handle, m_capabilities);
}
void seat_get_pointer(Resource *resource, uint32_t id) override;
// void seat_get_keyboard(Resource *resource, uint32_t id) override;
// void seat_get_touch(Resource *resource, uint32_t id) override;
// void seat_release(Resource *resource) override;
};
class Pointer : public QObject, public QtWaylandServer::wl_pointer
{
Q_OBJECT
public:
explicit Pointer(Seat *seat) : m_seat(seat) {}
Surface *cursorSurface();
CursorRole* m_cursorRole = nullptr; //TODO: cleanup
uint sendEnter(Surface *surface, const QPointF &position);
void sendMotion(wl_client *client, const QPointF &position);
uint sendButton(wl_client *client, uint button, uint state);
void sendAxis(wl_client *client, axis axis, qreal value);
Seat *m_seat = nullptr;
uint m_enterSerial = 0;
signals:
void setCursor(uint serial); //TODO: add arguments?
protected:
void pointer_set_cursor(Resource *resource, uint32_t serial, ::wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y) override;
//TODO
};
class CursorRole : public SurfaceRole {
Q_OBJECT
public:
explicit CursorRole(Surface *surface) // TODO: needs some more args
: m_surface(surface)
{
}
static CursorRole *fromSurface(Surface *surface) { return qobject_cast<CursorRole *>(surface->m_role); }
Surface *m_surface = nullptr;
};
class Shm : public Global, public QtWaylandServer::wl_shm
{
Q_OBJECT
public:
explicit Shm(CoreCompositor *compositor, QVector<format> formats = {format_argb8888, format_xrgb8888, format_rgb888}, int version = 1);
bool isClean() override;
CoreCompositor *m_compositor = nullptr;
QVector<ShmPool *> m_pools;
const QVector<format> m_formats;
protected:
void shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size) override;
void shm_bind_resource(Resource *resource) override
{
for (auto format : qAsConst(m_formats))
send_format(resource->handle, format);
}
};
class ShmPool : QObject, public QtWaylandServer::wl_shm_pool
{
Q_OBJECT
public:
explicit ShmPool(Shm *shm, wl_client *client, int id, int version = 1);
Shm *m_shm = nullptr;
QVector<ShmBuffer *> m_buffers;
protected:
void shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) override;
void shm_pool_destroy_resource(Resource *resource) override;
void shm_pool_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); }
};
class ShmBuffer : public Buffer
{
Q_OBJECT
public:
static ShmBuffer *fromBuffer(Buffer *buffer) { return qobject_cast<ShmBuffer *>(buffer); }
explicit ShmBuffer(int offset, const QSize &size, int stride, Shm::format format, wl_client *client, int id, int version = 1)
: Buffer(client, id, version)
, m_offset(offset)
, m_size(size)
, m_stride(stride)
, m_format(format)
{
}
QSize size() const override { return m_size; }
const int m_offset;
const QSize m_size;
const int m_stride;
const Shm::format m_format;
};
} // namespace MockCompositor
#endif // MOCKCOMPOSITOR_COREPROTOCOL_H

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
@ -27,494 +27,64 @@
****************************************************************************/
#include "mockcompositor.h"
#include "mockinput.h"
#include "mockoutput.h"
#include "mocksurface.h"
#include "mockwlshell.h"
#include "mockxdgshellv6.h"
#include "mockiviapplication.h"
#include <wayland-xdg-shell-unstable-v6-server-protocol.h>
namespace MockCompositor {
#include <stdio.h>
MockCompositor::MockCompositor()
DefaultCompositor::DefaultCompositor()
{
pthread_create(&m_thread, 0, run, this);
{
Lock l(this);
m_mutex.lock();
m_waitCondition.wait(&m_mutex);
m_mutex.unlock();
}
// Globals: Should ideally always be at least the latest versions we support.
// Legacy versions can override in separate tests by removing and adding.
add<WlCompositor>();
add<SubCompositor>();
add<Output>();
add<Seat>(Seat::capability_pointer);
add<XdgWmBase>();
add<Shm>();
// TODO: other shells, viewporter, xdgoutput etc
MockCompositor::~MockCompositor()
{
m_alive = false;
m_waitCondition.wakeOne();
pthread_join(m_thread, 0);
}
QObject::connect(get<WlCompositor>(), &WlCompositor::surfaceCreated, [&] (Surface *surface){
QObject::connect(surface, &Surface::bufferCommitted, [=] {
if (m_config.autoRelease) {
// Pretend we made a copy of the buffer and just release it immediately
surface->m_committed.buffer->send_release();
}
if (m_config.autoEnter && surface->m_outputs.empty())
surface->sendEnter(get<Output>());
wl_display_flush_clients(m_display);
});
});
void MockCompositor::lock()
{
m_mutex.lock();
}
void MockCompositor::unlock()
{
m_mutex.unlock();
}
void MockCompositor::applicationInitialized()
{
m_ready = true;
}
int MockCompositor::waylandFileDescriptor() const
{
return m_compositor->fileDescriptor();
}
void MockCompositor::processWaylandEvents()
{
m_waitCondition.wakeOne();
}
void MockCompositor::setOutputMode(const QSize &size)
{
Command command = makeCommand(Impl::Compositor::setOutputMode, m_compositor);
command.parameters << size;
processCommand(command);
}
void MockCompositor::setKeyboardFocus(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::setKeyboardFocus, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendMousePress(const QSharedPointer<MockSurface> &surface, const QPoint &pos)
{
Command command = makeCommand(Impl::Compositor::sendMousePress, m_compositor);
command.parameters << QVariant::fromValue(surface) << pos;
processCommand(command);
}
void MockCompositor::sendMouseRelease(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendMouseRelease, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendKeyPress(const QSharedPointer<MockSurface> &surface, uint code)
{
Command command = makeCommand(Impl::Compositor::sendKeyPress, m_compositor);
command.parameters << QVariant::fromValue(surface) << code;
processCommand(command);
}
void MockCompositor::sendKeyRelease(const QSharedPointer<MockSurface> &surface, uint code)
{
Command command = makeCommand(Impl::Compositor::sendKeyRelease, m_compositor);
command.parameters << QVariant::fromValue(surface) << code;
processCommand(command);
}
void MockCompositor::sendTouchDown(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id)
{
Command command = makeCommand(Impl::Compositor::sendTouchDown, m_compositor);
command.parameters << QVariant::fromValue(surface) << position << id;
processCommand(command);
}
void MockCompositor::sendTouchMotion(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id)
{
Command command = makeCommand(Impl::Compositor::sendTouchMotion, m_compositor);
command.parameters << QVariant::fromValue(surface) << position << id;
processCommand(command);
}
void MockCompositor::sendTouchUp(const QSharedPointer<MockSurface> &surface, int id)
{
Command command = makeCommand(Impl::Compositor::sendTouchUp, m_compositor);
command.parameters << QVariant::fromValue(surface) << id;
processCommand(command);
}
void MockCompositor::sendTouchFrame(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendTouchFrame, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendDataDeviceDataOffer(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceDataOffer, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendDataDeviceEnter(const QSharedPointer<MockSurface> &surface, const QPoint& position)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceEnter, m_compositor);
command.parameters << QVariant::fromValue(surface) << QVariant::fromValue(position);
processCommand(command);
}
void MockCompositor::sendDataDeviceMotion(const QPoint &position)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceMotion, m_compositor);
command.parameters << QVariant::fromValue(position);
processCommand(command);
}
void MockCompositor::sendDataDeviceDrop(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceDrop, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendDataDeviceLeave(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceLeave, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendAddOutput()
{
Command command = makeCommand(Impl::Compositor::sendAddOutput, m_compositor);
processCommand(command);
}
void MockCompositor::sendRemoveOutput(const QSharedPointer<MockOutput> &output)
{
Command command = makeCommand(Impl::Compositor::sendRemoveOutput, m_compositor);
command.parameters << QVariant::fromValue(output);
processCommand(command);
}
void MockCompositor::sendOutputGeometry(const QSharedPointer<MockOutput> &output, const QRect &geometry)
{
Command command = makeCommand(Impl::Compositor::sendOutputGeometry, m_compositor);
command.parameters << QVariant::fromValue(output);
command.parameters << QVariant::fromValue(geometry);
processCommand(command);
}
void MockCompositor::sendSurfaceEnter(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output)
{
Command command = makeCommand(Impl::Compositor::sendSurfaceEnter, m_compositor);
command.parameters << QVariant::fromValue(surface);
command.parameters << QVariant::fromValue(output);
processCommand(command);
}
void MockCompositor::sendSurfaceLeave(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output)
{
Command command = makeCommand(Impl::Compositor::sendSurfaceLeave, m_compositor);
command.parameters << QVariant::fromValue(surface);
command.parameters << QVariant::fromValue(output);
processCommand(command);
}
void MockCompositor::sendShellSurfaceConfigure(const QSharedPointer<MockSurface> surface, const QSize &size)
{
Command command = makeCommand(Impl::Compositor::sendShellSurfaceConfigure, m_compositor);
command.parameters << QVariant::fromValue(surface);
command.parameters << QVariant::fromValue(size);
processCommand(command);
}
void MockCompositor::sendIviSurfaceConfigure(const QSharedPointer<MockIviSurface> iviSurface, const QSize &size)
{
Command command = makeCommand(Impl::Compositor::sendIviSurfaceConfigure, m_compositor);
command.parameters << QVariant::fromValue(iviSurface);
command.parameters << QVariant::fromValue(size);
processCommand(command);
}
void MockCompositor::sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size, const QVector<uint> &states)
{
Command command = makeCommand(Impl::Compositor::sendXdgToplevelV6Configure, m_compositor);
command.parameters << QVariant::fromValue(toplevel);
command.parameters << QVariant::fromValue(size);
QByteArray statesBytes(reinterpret_cast<const char *>(states.data()),
states.size() * static_cast<int>(sizeof(uint)));
command.parameters << statesBytes;
processCommand(command);
}
void MockCompositor::waitForStartDrag()
{
Command command = makeCommand(Impl::Compositor::waitForStartDrag, m_compositor);
processCommand(command);
}
QSharedPointer<MockSurface> MockCompositor::surface()
{
QSharedPointer<MockSurface> result;
lock();
QVector<Impl::Surface *> surfaces = m_compositor->surfaces();
foreach (Impl::Surface *surface, surfaces) {
// we don't want to mistake the cursor surface for a window surface
if (surface->isMapped()) {
result = surface->mockSurface();
break;
}
QObject::connect(get<XdgWmBase>(), &XdgWmBase::toplevelCreated, [&] (XdgToplevel *toplevel) {
// Needed because lambdas don't support Qt::DirectConnection
exec([&]{
if (m_config.autoConfigure)
toplevel->sendCompleteConfigure();
});
});
}
unlock();
return result;
Q_ASSERT(isClean());
}
QSharedPointer<MockOutput> MockCompositor::output(int index)
uint DefaultCompositor::sendXdgShellPing()
{
QSharedPointer<MockOutput> result;
lock();
if (Impl::Output *output = m_compositor->outputs().value(index, nullptr))
result = output->mockOutput();
unlock();
return result;
warnIfNotLockedByThread(Q_FUNC_INFO);
uint serial = nextSerial();
auto *base = get<XdgWmBase>();
const auto resourceMap = base->resourceMap();
Q_ASSERT(resourceMap.size() == 1); // binding more than once shouldn't be needed
base->send_ping(resourceMap.first()->handle, serial);
return serial;
}
QSharedPointer<MockIviSurface> MockCompositor::iviSurface(int index)
void DefaultCompositor::xdgPingAndWaitForPong()
{
QSharedPointer<MockIviSurface> result;
lock();
if (Impl::IviSurface *toplevel = m_compositor->iviApplication()->iviSurfaces().value(index, nullptr))
result = toplevel->mockIviSurface();
unlock();
return result;
QSignalSpy pongSpy(exec([=] { return get<XdgWmBase>(); }), &XdgWmBase::pong);
uint serial = exec([=] { return sendXdgShellPing(); });
QTRY_COMPARE(pongSpy.count(), 1);
QTRY_COMPARE(pongSpy.first().at(0).toUInt(), serial);
}
QSharedPointer<MockXdgToplevelV6> MockCompositor::xdgToplevelV6(int index)
{
QSharedPointer<MockXdgToplevelV6> result;
lock();
if (Impl::XdgToplevelV6 *toplevel = m_compositor->xdgShellV6()->toplevels().value(index, nullptr))
result = toplevel->mockToplevel();
unlock();
return result;
}
QSharedPointer<MockSurface> MockCompositor::fullScreenShellV1Surface(int index)
{
QSharedPointer<MockSurface> result;
lock();
if (Impl::Surface *surface = m_compositor->fullScreenShellV1()->surfaces().value(index, nullptr))
result = surface->mockSurface();
unlock();
return result;
}
MockCompositor::Command MockCompositor::makeCommand(Command::Callback callback, void *target)
{
Command command;
command.callback = callback;
command.target = target;
return command;
}
void MockCompositor::processCommand(const Command &command)
{
lock();
m_commandQueue << command;
unlock();
m_waitCondition.wakeOne();
}
void MockCompositor::dispatchCommands()
{
lock();
int count = m_commandQueue.length();
unlock();
for (int i = 0; i < count; ++i) {
lock();
const Command command = m_commandQueue.takeFirst();
unlock();
command.callback(command.target, command.parameters);
}
}
void *MockCompositor::run(void *data)
{
MockCompositor *controller = static_cast<MockCompositor *>(data);
Impl::Compositor compositor;
controller->m_compositor = &compositor;
controller->m_waitCondition.wakeOne();
while (!controller->m_ready) {
controller->dispatchCommands();
compositor.dispatchEvents(20);
}
while (controller->m_alive) {
{
QMutexLocker locker(&controller->m_mutex);
if (controller->m_commandQueue.isEmpty())
controller->m_waitCondition.wait(&controller->m_mutex);
}
controller->dispatchCommands();
compositor.dispatchEvents(20);
}
return 0;
}
namespace Impl {
Compositor::Compositor()
: m_display(wl_display_create())
{
if (wl_display_add_socket(m_display, 0)) {
fprintf(stderr, "Fatal: Failed to open server socket\n");
exit(EXIT_FAILURE);
}
wl_global_create(m_display, &wl_compositor_interface, 1, this, bindCompositor);
m_data_device_manager.reset(new DataDeviceManager(this, m_display));
wl_display_init_shm(m_display);
m_seat.reset(new Seat(this, m_display));
m_pointer = m_seat->pointer();
m_keyboard = m_seat->keyboard();
m_touch = m_seat->touch();
m_outputs.append(new Output(m_display, QSize(1920, 1080), QPoint(0, 0)));
m_iviApplication.reset(new IviApplication(m_display));
m_wlShell.reset(new WlShell(m_display));
m_xdgShellV6.reset(new XdgShellV6(m_display));
m_fullScreenShellV1.reset(new FullScreenShellV1(m_display));
m_loop = wl_display_get_event_loop(m_display);
m_fd = wl_event_loop_get_fd(m_loop);
}
Compositor::~Compositor()
{
wl_display_destroy(m_display);
}
void Compositor::dispatchEvents(int timeout)
{
wl_display_flush_clients(m_display);
wl_event_loop_dispatch(m_loop, timeout);
}
static void compositor_create_surface(wl_client *client, wl_resource *compositorResource, uint32_t id)
{
Compositor *compositor = static_cast<Compositor *>(wl_resource_get_user_data(compositorResource));
compositor->addSurface(new Surface(client, id, wl_resource_get_version(compositorResource), compositor));
}
static void compositor_create_region(wl_client *client, wl_resource *compositorResource, uint32_t id)
{
Q_UNUSED(client);
Q_UNUSED(compositorResource);
Q_UNUSED(id);
}
void Compositor::bindCompositor(wl_client *client, void *compositorData, uint32_t version, uint32_t id)
{
static const struct wl_compositor_interface compositorInterface = {
compositor_create_surface,
compositor_create_region
};
wl_resource *resource = wl_resource_create(client, &wl_compositor_interface, static_cast<int>(version), id);
wl_resource_set_implementation(resource, &compositorInterface, compositorData, nullptr);
}
static void unregisterResourceCallback(wl_listener *listener, void *data)
{
struct wl_resource *resource = reinterpret_cast<struct wl_resource *>(data);
wl_list_remove(wl_resource_get_link(resource));
delete listener;
}
void registerResource(wl_list *list, wl_resource *resource)
{
wl_list_insert(list, wl_resource_get_link(resource));
wl_listener *listener = new wl_listener;
listener->notify = unregisterResourceCallback;
wl_resource_add_destroy_listener(resource, listener);
}
QVector<Surface *> Compositor::surfaces() const
{
return m_surfaces;
}
QVector<Output *> Compositor::outputs() const
{
return m_outputs;
}
IviApplication *Compositor::iviApplication() const
{
return m_iviApplication.data();
}
XdgShellV6 *Compositor::xdgShellV6() const
{
return m_xdgShellV6.data();
}
FullScreenShellV1 *Compositor::fullScreenShellV1() const
{
return m_fullScreenShellV1.data();
}
uint32_t Compositor::nextSerial()
{
return wl_display_next_serial(m_display);
}
void Compositor::addSurface(Surface *surface)
{
m_surfaces << surface;
}
void Compositor::removeSurface(Surface *surface)
{
m_surfaces.removeOne(surface);
m_keyboard->handleSurfaceDestroyed(surface);
m_pointer->handleSurfaceDestroyed(surface);
m_fullScreenShellV1->removeSurface(surface);
}
Surface *Compositor::resolveSurface(const QVariant &v)
{
QSharedPointer<MockSurface> mockSurface = v.value<QSharedPointer<MockSurface> >();
return mockSurface ? mockSurface->handle() : nullptr;
}
Output *Compositor::resolveOutput(const QVariant &v)
{
QSharedPointer<MockOutput> mockOutput = v.value<QSharedPointer<MockOutput> >();
return mockOutput ? mockOutput->handle() : nullptr;
}
IviSurface *Compositor::resolveIviSurface(const QVariant &v)
{
QSharedPointer<MockIviSurface> mockIviSurface = v.value<QSharedPointer<MockIviSurface>>();
return mockIviSurface ? mockIviSurface->handle() : nullptr;
}
XdgToplevelV6 *Compositor::resolveToplevel(const QVariant &v)
{
QSharedPointer<MockXdgToplevelV6> mockToplevel = v.value<QSharedPointer<MockXdgToplevelV6>>();
return mockToplevel ? mockToplevel->handle() : nullptr;
}
}
} // namespace MockCompositor

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
@ -29,262 +29,55 @@
#ifndef MOCKCOMPOSITOR_H
#define MOCKCOMPOSITOR_H
#include "mockxdgshellv6.h"
#include "mockiviapplication.h"
#include "mockfullscreenshellv1.h"
#include "corecompositor.h"
#include "coreprotocol.h"
#include "xdgshell.h"
#include <pthread.h>
#include <qglobal.h>
#include <wayland-server-core.h>
#include <QtGui/QGuiApplication>
#include <QImage>
#include <QMutex>
#include <QRect>
#include <QSharedPointer>
#include <QVariant>
#include <QVector>
#include <QWaitCondition>
#ifndef BTN_LEFT
// As defined in linux/input-event-codes.h
#define BTN_LEFT 0x110
#endif
namespace Impl {
namespace MockCompositor {
typedef void (**Implementation)(void);
class Keyboard;
class Pointer;
class Touch;
class Seat;
class DataDeviceManager;
class Surface;
class Output;
class IviApplication;
class WlShell;
class XdgShellV6;
class Compositor
class DefaultCompositor : public CoreCompositor
{
public:
Compositor();
~Compositor();
int fileDescriptor() const { return m_fd; }
void dispatchEvents(int timeout = 0);
uint32_t nextSerial();
uint32_t time() { return ++m_time; }
QVector<Surface *> surfaces() const;
QVector<Output *> outputs() const;
IviApplication *iviApplication() const;
XdgShellV6 *xdgShellV6() const;
FullScreenShellV1 *fullScreenShellV1() const;
void addSurface(Surface *surface);
void removeSurface(Surface *surface);
static void setKeyboardFocus(void *data, const QList<QVariant> &parameters);
static void sendMousePress(void *data, const QList<QVariant> &parameters);
static void sendMouseRelease(void *data, const QList<QVariant> &parameters);
static void sendKeyPress(void *data, const QList<QVariant> &parameters);
static void sendKeyRelease(void *data, const QList<QVariant> &parameters);
static void sendTouchDown(void *data, const QList<QVariant> &parameters);
static void sendTouchUp(void *data, const QList<QVariant> &parameters);
static void sendTouchMotion(void *data, const QList<QVariant> &parameters);
static void sendTouchFrame(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceDataOffer(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceEnter(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceMotion(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceDrop(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceLeave(void *data, const QList<QVariant> &parameters);
static void waitForStartDrag(void *data, const QList<QVariant> &parameters);
static void setOutputMode(void *compositor, const QList<QVariant> &parameters);
static void sendAddOutput(void *data, const QList<QVariant> &parameters);
static void sendRemoveOutput(void *data, const QList<QVariant> &parameters);
static void sendOutputGeometry(void *data, const QList<QVariant> &parameters);
static void sendSurfaceEnter(void *data, const QList<QVariant> &parameters);
static void sendSurfaceLeave(void *data, const QList<QVariant> &parameters);
static void sendShellSurfaceConfigure(void *data, const QList<QVariant> &parameters);
static void sendIviSurfaceConfigure(void *data, const QList<QVariant> &parameters);
static void sendXdgToplevelV6Configure(void *data, const QList<QVariant> &parameters);
public:
bool m_startDragSeen = false;
private:
static void bindCompositor(wl_client *client, void *data, uint32_t version, uint32_t id);
static Surface *resolveSurface(const QVariant &v);
static Output *resolveOutput(const QVariant &v);
static IviSurface *resolveIviSurface(const QVariant &v);
static XdgToplevelV6 *resolveToplevel(const QVariant &v);
void initShm();
QRect m_outputGeometry;
wl_display *m_display = nullptr;
wl_event_loop *m_loop = nullptr;
int m_fd = -1;
uint32_t m_time = 0;
QScopedPointer<Seat> m_seat;
Pointer *m_pointer = nullptr;
Keyboard *m_keyboard = nullptr;
Touch *m_touch = nullptr;
QScopedPointer<DataDeviceManager> m_data_device_manager;
QVector<Surface *> m_surfaces;
QVector<Output *> m_outputs;
QScopedPointer<IviApplication> m_iviApplication;
QScopedPointer<WlShell> m_wlShell;
QScopedPointer<XdgShellV6> m_xdgShellV6;
QScopedPointer<FullScreenShellV1> m_fullScreenShellV1;
explicit DefaultCompositor();
// Convenience functions
Surface *surface(int i = 0) { return get<WlCompositor>()->m_surfaces.value(i, nullptr); }
XdgSurface *xdgSurface(int i = 0) { return get<XdgWmBase>()->m_xdgSurfaces.value(i, nullptr); }
XdgToplevel *xdgToplevel(int i = 0) { return get<XdgWmBase>()->toplevel(i); }
XdgPopup *xdgPopup(int i = 0) { return get<XdgWmBase>()->popup(i); }
Pointer *pointer() { auto *seat = get<Seat>(); Q_ASSERT(seat); return seat->m_pointer; }
uint sendXdgShellPing();
void xdgPingAndWaitForPong();
// Things that can be changed run-time without confusing the client (i.e. don't require separate tests)
struct Config {
bool autoEnter = true;
bool autoRelease = true;
bool autoConfigure = false;
} m_config;
void resetConfig() { exec([&] { m_config = Config{}; }); }
};
void registerResource(wl_list *list, wl_resource *resource);
} // namespace MockCompositor
}
#define QCOMPOSITOR_VERIFY(expr) QVERIFY(exec([&]{ return expr; }))
#define QCOMPOSITOR_TRY_VERIFY(expr) QTRY_VERIFY(exec([&]{ return expr; }))
#define QCOMPOSITOR_COMPARE(expr, expr2) QCOMPARE(exec([&]{ return expr; }), expr2)
#define QCOMPOSITOR_TRY_COMPARE(expr, expr2) QTRY_COMPARE(exec([&]{ return expr; }), expr2)
class MockSurface
{
public:
Impl::Surface *handle() const { return m_surface; }
QImage image;
private:
MockSurface(Impl::Surface *surface);
friend class Impl::Compositor;
friend class Impl::Surface;
Impl::Surface *m_surface = nullptr;
};
Q_DECLARE_METATYPE(QSharedPointer<MockSurface>)
class MockIviSurface
{
public:
Impl::IviSurface *handle() const { return m_iviSurface; }
const uint iviId;
private:
MockIviSurface(Impl::IviSurface *iviSurface) : iviId(iviSurface->iviId()), m_iviSurface(iviSurface) {}
friend class Impl::Compositor;
friend class Impl::IviSurface;
Impl::IviSurface *m_iviSurface;
};
Q_DECLARE_METATYPE(QSharedPointer<MockIviSurface>)
class MockXdgToplevelV6 : public QObject
{
Q_OBJECT
public:
Impl::XdgToplevelV6 *handle() const { return m_toplevel; }
void sendConfigure(const QSharedPointer<MockXdgToplevelV6> toplevel);
signals:
uint setMinimizedRequested();
uint setMaximizedRequested();
uint unsetMaximizedRequested();
uint setFullscreenRequested();
uint unsetFullscreenRequested();
void windowGeometryRequested(QRect geometry); // NOTE: This is really an xdg surface event
private:
MockXdgToplevelV6(Impl::XdgToplevelV6 *toplevel) : m_toplevel(toplevel) {}
friend class Impl::Compositor;
friend class Impl::XdgToplevelV6;
Impl::XdgToplevelV6 *m_toplevel;
};
Q_DECLARE_METATYPE(QSharedPointer<MockXdgToplevelV6>)
class MockOutput {
public:
Impl::Output *handle() const { return m_output; }
MockOutput(Impl::Output *output);
private:
Impl::Output *m_output = nullptr;
};
Q_DECLARE_METATYPE(QSharedPointer<MockOutput>)
class MockCompositor
{
public:
MockCompositor();
~MockCompositor();
void applicationInitialized();
int waylandFileDescriptor() const;
void processWaylandEvents();
void setOutputMode(const QSize &size);
void setKeyboardFocus(const QSharedPointer<MockSurface> &surface);
void sendMousePress(const QSharedPointer<MockSurface> &surface, const QPoint &pos);
void sendMouseRelease(const QSharedPointer<MockSurface> &surface);
void sendKeyPress(const QSharedPointer<MockSurface> &surface, uint code);
void sendKeyRelease(const QSharedPointer<MockSurface> &surface, uint code);
void sendTouchDown(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id);
void sendTouchMotion(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id);
void sendTouchUp(const QSharedPointer<MockSurface> &surface, int id);
void sendTouchFrame(const QSharedPointer<MockSurface> &surface);
void sendDataDeviceDataOffer(const QSharedPointer<MockSurface> &surface);
void sendDataDeviceEnter(const QSharedPointer<MockSurface> &surface, const QPoint &position);
void sendDataDeviceMotion(const QPoint &position);
void sendDataDeviceDrop(const QSharedPointer<MockSurface> &surface);
void sendDataDeviceLeave(const QSharedPointer<MockSurface> &surface);
void sendAddOutput();
void sendRemoveOutput(const QSharedPointer<MockOutput> &output);
void sendOutputGeometry(const QSharedPointer<MockOutput> &output, const QRect &geometry);
void sendSurfaceEnter(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output);
void sendSurfaceLeave(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output);
void sendShellSurfaceConfigure(const QSharedPointer<MockSurface> surface, const QSize &size = QSize(0, 0));
void sendIviSurfaceConfigure(const QSharedPointer<MockIviSurface> iviSurface, const QSize &size);
void sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size = QSize(0, 0),
const QVector<uint> &states = { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED });
void waitForStartDrag();
QSharedPointer<MockSurface> surface();
QSharedPointer<MockOutput> output(int index = 0);
QSharedPointer<MockIviSurface> iviSurface(int index = 0);
QSharedPointer<MockXdgToplevelV6> xdgToplevelV6(int index = 0);
QSharedPointer<MockSurface> fullScreenShellV1Surface(int index = 0);
void lock();
void unlock();
private:
struct Command
{
typedef void (*Callback)(void *target, const QList<QVariant> &parameters);
Callback callback;
void *target = nullptr;
QList<QVariant> parameters;
};
static Command makeCommand(Command::Callback callback, void *target);
void processCommand(const Command &command);
void dispatchCommands();
static void *run(void *data);
bool m_alive = true;
bool m_ready = false;
pthread_t m_thread;
QMutex m_mutex;
QWaitCondition m_waitCondition;
Impl::Compositor *m_compositor = nullptr;
QList<Command> m_commandQueue;
};
#define QCOMPOSITOR_TEST_MAIN(test) \
int main(int argc, char **argv) \
{ \
setenv("XDG_RUNTIME_DIR", ".", 1); \
setenv("QT_QPA_PLATFORM", "wayland", 1); \
test tc; \
QGuiApplication app(argc, argv); \
return QTest::qExec(&tc, argc, argv); \
} \
#endif

View File

@ -1,34 +1,21 @@
CONFIG += testcase link_pkgconfig
QT += testlib
QT += core-private gui-private waylandclient-private
QT += testlib waylandclient-private
CONFIG += testcase wayland-scanner
QMAKE_USE += wayland-server
QMAKE_USE += wayland-client wayland-server
CONFIG += wayland-scanner
WAYLANDSERVERSOURCES += \
../../../../src/3rdparty/protocol/ivi-application.xml \
../../../../src/3rdparty/protocol/wayland.xml \
../../../../src/3rdparty/protocol/xdg-shell-unstable-v6.xml \
../../../../src/3rdparty/protocol/fullscreen-shell-unstable-v1.xml
$$PWD/../../../../src/3rdparty/protocol/wayland.xml \
$$PWD/../../../../src/3rdparty/protocol/xdg-shell.xml
INCLUDEPATH += ../shared
SOURCES += \
../shared/mockcompositor.cpp \
../shared/mockfullscreenshellv1.cpp \
../shared/mockinput.cpp \
../shared/mockiviapplication.cpp \
../shared/mockwlshell.cpp \
../shared/mockxdgshellv6.cpp \
../shared/mocksurface.cpp \
../shared/mockoutput.cpp
HEADERS += \
../shared/mockcompositor.h \
../shared/mockfullscreenshellv1.h \
../shared/mockinput.h \
../shared/mockiviapplication.h \
../shared/mockwlshell.h \
../shared/mockxdgshellv6.h \
../shared/mocksurface.h \
../shared/mockoutput.h
$$PWD/corecompositor.h \
$$PWD/coreprotocol.h \
$$PWD/mockcompositor.h \
$$PWD/xdgshell.h
SOURCES += \
$$PWD/corecompositor.cpp \
$$PWD/coreprotocol.cpp \
$$PWD/mockcompositor.cpp \
$$PWD/xdgshell.cpp

View File

@ -0,0 +1,186 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "xdgshell.h"
namespace MockCompositor {
XdgWmBase::XdgWmBase(CoreCompositor *compositor, int version)
: QtWaylandServer::xdg_wm_base(compositor->m_display, version)
, m_compositor(compositor)
{
}
XdgToplevel *XdgWmBase::toplevel(int i)
{
int j = 0;
for (auto *xdgSurface : qAsConst(m_xdgSurfaces)) {
if (auto *toplevel = xdgSurface->m_toplevel) {
if (j == i)
return toplevel;
++j;
}
}
return nullptr;
}
XdgPopup *XdgWmBase::popup(int i)
{
int j = 0;
for (auto *xdgSurface : qAsConst(m_xdgSurfaces)) {
if (auto *popup = xdgSurface->m_popup) {
if (j == i)
return popup;
++j;
}
}
return nullptr;
}
void XdgWmBase::xdg_wm_base_get_xdg_surface(Resource *resource, uint32_t id, wl_resource *surface)
{
auto *s = fromResource<Surface>(surface);
auto *xdgSurface = new XdgSurface(this, s, resource->client(), id, resource->version());
m_xdgSurfaces << xdgSurface;
emit xdgSurfaceCreated(xdgSurface);
}
void XdgWmBase::xdg_wm_base_pong(Resource *resource, uint32_t serial)
{
Q_UNUSED(resource);
emit pong(serial);
}
XdgSurface::XdgSurface(XdgWmBase *xdgWmBase, Surface *surface, wl_client *client, int id, int version)
: QtWaylandServer::xdg_surface(client, id, version)
, m_xdgWmBase(xdgWmBase)
, m_surface(surface)
{
QVERIFY(!surface->m_pending.buffer);
QVERIFY(!surface->m_committed.buffer);
connect(this, &XdgSurface::toplevelCreated, xdgWmBase, &XdgWmBase::toplevelCreated);
connect(surface, &Surface::attach, this, &XdgSurface::verifyConfigured);
connect(surface, &Surface::commit, this, [this] {
if (m_ackedConfigureSerial != m_committedConfigureSerial) {
m_committedConfigureSerial = m_ackedConfigureSerial;
emit configureCommitted(m_committedConfigureSerial);
}
});
}
void XdgSurface::sendConfigure(uint serial)
{
Q_ASSERT(serial);
m_pendingConfigureSerials.append(serial);
m_configureSent = true;
xdg_surface::send_configure(serial);
}
uint XdgSurface::sendConfigure()
{
const uint serial = m_xdgWmBase->m_compositor->nextSerial();
sendConfigure(serial);
return serial;
}
void XdgSurface::xdg_surface_get_toplevel(Resource *resource, uint32_t id)
{
QVERIFY(!m_toplevel);
QVERIFY(!m_popup);
m_toplevel = new XdgToplevel(this, id, resource->version());
emit toplevelCreated(m_toplevel);
}
void XdgSurface::xdg_surface_get_popup(Resource *resource, uint32_t id, wl_resource *parent, wl_resource *positioner)
{
Q_UNUSED(parent);
Q_UNUSED(positioner);
QVERIFY(!m_toplevel);
QVERIFY(!m_popup);
m_popup = new XdgPopup(this, id, resource->version());
}
void XdgSurface::xdg_surface_destroy_resource(Resource *resource)
{
Q_UNUSED(resource);
bool removed = m_xdgWmBase->m_xdgSurfaces.removeOne(this);
Q_ASSERT(removed);
delete this;
}
void XdgSurface::xdg_surface_ack_configure(Resource *resource, uint32_t serial)
{
Q_UNUSED(resource);
QVERIFY2(m_pendingConfigureSerials.contains(serial), qPrintable(QString::number(serial)));
m_ackedConfigureSerial = serial;
while (!m_pendingConfigureSerials.empty()) {
uint s = m_pendingConfigureSerials.takeFirst();
if (s == serial)
return;
}
}
XdgToplevel::XdgToplevel(XdgSurface *xdgSurface, int id, int version)
: QtWaylandServer::xdg_toplevel(xdgSurface->resource()->client(), id, version)
, m_xdgSurface(xdgSurface)
{
}
void XdgToplevel::sendConfigure(const QSize &size, const QVector<uint> &states)
{
send_configure(size.width(), size.height(), toByteArray(states));
}
uint XdgToplevel::sendCompleteConfigure(const QSize &size, const QVector<uint> &states)
{
sendConfigure(size, states);
return m_xdgSurface->sendConfigure();
}
XdgPopup::XdgPopup(XdgSurface *xdgSurface, int id, int version)
: QtWaylandServer::xdg_popup(xdgSurface->resource()->client(), id, version)
, m_xdgSurface(xdgSurface)
{
}
void XdgPopup::sendConfigure(const QRect &geometry)
{
send_configure(geometry.x(), geometry.y(), geometry.width(), geometry.height());
}
void XdgPopup::xdg_popup_grab(QtWaylandServer::xdg_popup::Resource *resource, wl_resource *seat, uint32_t serial)
{
Q_UNUSED(resource);
Q_UNUSED(seat); // TODO: verify correct seat as well
//TODO: verify no other popup has grabbed
QVERIFY(!m_grabbed);
m_grabbed = true;
m_grabSerial = serial;
}
} // namespace MockCompositor

View File

@ -0,0 +1,127 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef MOCKCOMPOSITOR_XDGSHELL_H
#define MOCKCOMPOSITOR_XDGSHELL_H
#include "coreprotocol.h"
#include <qwayland-server-xdg-shell.h>
namespace MockCompositor {
class XdgSurface;
class XdgToplevel;
class XdgPopup;
using XdgPositioner = QtWaylandServer::xdg_positioner;
class XdgWmBase : public Global, public QtWaylandServer::xdg_wm_base
{
Q_OBJECT
public:
explicit XdgWmBase(CoreCompositor *compositor, int version = 1);
using QtWaylandServer::xdg_wm_base::send_ping;
void send_ping(uint32_t) = delete; // It's a global, use resource specific instead
bool isClean() override { return m_xdgSurfaces.empty(); }
QString dirtyMessage() override { return m_xdgSurfaces.empty() ? "clean" : "remaining xdg surfaces"; }
QVector<XdgSurface *> m_xdgSurfaces;
XdgToplevel *toplevel(int i = 0);
XdgPopup *popup(int i = 0);
CoreCompositor *m_compositor = nullptr;
signals:
void pong(uint serial);
void xdgSurfaceCreated(XdgSurface *xdgSurface);
void toplevelCreated(XdgToplevel *toplevel);
protected:
void xdg_wm_base_get_xdg_surface(Resource *resource, uint32_t id, ::wl_resource *surface) override;
void xdg_wm_base_pong(Resource *resource, uint32_t serial) override;
void xdg_wm_base_create_positioner(Resource *resource, uint32_t id) override
{
new XdgPositioner(resource->client(), id, resource->version());
}
};
class XdgSurface : public QObject, public QtWaylandServer::xdg_surface
{
Q_OBJECT
public:
explicit XdgSurface(XdgWmBase *xdgWmBase, Surface *surface, wl_client *client, int id, int version);
void send_configure(uint serial) = delete; // Use the one below instead, as it tracks state
void sendConfigure(uint serial);
uint sendConfigure();
XdgToplevel *m_toplevel = nullptr;
XdgPopup *m_popup = nullptr;
XdgWmBase *m_xdgWmBase = nullptr;
Surface *m_surface = nullptr;
bool m_configureSent = false;
QVector<uint> m_pendingConfigureSerials;
uint m_ackedConfigureSerial = 0;
uint m_committedConfigureSerial = 0;
public slots:
void verifyConfigured() { QVERIFY(m_configureSent); }
signals:
void configureCommitted(uint);
void toplevelCreated(XdgToplevel *toplevel);
protected:
void xdg_surface_get_toplevel(Resource *resource, uint32_t id) override;
void xdg_surface_get_popup(Resource *resource, uint32_t id, ::wl_resource *parent, ::wl_resource *positioner) override;
void xdg_surface_destroy_resource(Resource *resource) override;
void xdg_surface_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); }
void xdg_surface_ack_configure(Resource *resource, uint32_t serial) override;
};
class XdgToplevel : public QtWaylandServer::xdg_toplevel
{
public:
explicit XdgToplevel(XdgSurface *xdgSurface, int id, int version = 1);
void sendConfigure(const QSize &size = {0, 0}, const QVector<uint> &states = {});
uint sendCompleteConfigure(const QSize &size = {0, 0}, const QVector<uint> &states = {});
Surface *surface() { return m_xdgSurface->m_surface; }
XdgSurface *m_xdgSurface = nullptr;
};
class XdgPopup : public QtWaylandServer::xdg_popup
{
public:
explicit XdgPopup(XdgSurface *xdgSurface, int id, int version = 1);
void sendConfigure(const QRect &geometry);
Surface *surface() { return m_xdgSurface->m_surface; }
XdgSurface *m_xdgSurface = nullptr;
bool m_grabbed = false;
uint m_grabSerial = 0;
protected:
void xdg_popup_grab(Resource *resource, ::wl_resource *seat, uint32_t serial) override;
};
} // namespace MockCompositor
#endif // MOCKCOMPOSITOR_XDGSHELL_H

View File

@ -0,0 +1,520 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mockcompositor.h"
#include "mockinput.h"
#include "mockoutput.h"
#include "mocksurface.h"
#include "mockwlshell.h"
#include "mockxdgshellv6.h"
#include "mockiviapplication.h"
#include <wayland-xdg-shell-unstable-v6-server-protocol.h>
#include <stdio.h>
MockCompositor::MockCompositor()
{
pthread_create(&m_thread, 0, run, this);
m_mutex.lock();
m_waitCondition.wait(&m_mutex);
m_mutex.unlock();
}
MockCompositor::~MockCompositor()
{
m_alive = false;
m_waitCondition.wakeOne();
pthread_join(m_thread, 0);
}
void MockCompositor::lock()
{
m_mutex.lock();
}
void MockCompositor::unlock()
{
m_mutex.unlock();
}
void MockCompositor::applicationInitialized()
{
m_ready = true;
}
int MockCompositor::waylandFileDescriptor() const
{
return m_compositor->fileDescriptor();
}
void MockCompositor::processWaylandEvents()
{
m_waitCondition.wakeOne();
}
void MockCompositor::setOutputMode(const QSize &size)
{
Command command = makeCommand(Impl::Compositor::setOutputMode, m_compositor);
command.parameters << size;
processCommand(command);
}
void MockCompositor::setKeyboardFocus(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::setKeyboardFocus, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendMousePress(const QSharedPointer<MockSurface> &surface, const QPoint &pos)
{
Command command = makeCommand(Impl::Compositor::sendMousePress, m_compositor);
command.parameters << QVariant::fromValue(surface) << pos;
processCommand(command);
}
void MockCompositor::sendMouseRelease(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendMouseRelease, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendKeyPress(const QSharedPointer<MockSurface> &surface, uint code)
{
Command command = makeCommand(Impl::Compositor::sendKeyPress, m_compositor);
command.parameters << QVariant::fromValue(surface) << code;
processCommand(command);
}
void MockCompositor::sendKeyRelease(const QSharedPointer<MockSurface> &surface, uint code)
{
Command command = makeCommand(Impl::Compositor::sendKeyRelease, m_compositor);
command.parameters << QVariant::fromValue(surface) << code;
processCommand(command);
}
void MockCompositor::sendTouchDown(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id)
{
Command command = makeCommand(Impl::Compositor::sendTouchDown, m_compositor);
command.parameters << QVariant::fromValue(surface) << position << id;
processCommand(command);
}
void MockCompositor::sendTouchMotion(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id)
{
Command command = makeCommand(Impl::Compositor::sendTouchMotion, m_compositor);
command.parameters << QVariant::fromValue(surface) << position << id;
processCommand(command);
}
void MockCompositor::sendTouchUp(const QSharedPointer<MockSurface> &surface, int id)
{
Command command = makeCommand(Impl::Compositor::sendTouchUp, m_compositor);
command.parameters << QVariant::fromValue(surface) << id;
processCommand(command);
}
void MockCompositor::sendTouchFrame(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendTouchFrame, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendDataDeviceDataOffer(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceDataOffer, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendDataDeviceEnter(const QSharedPointer<MockSurface> &surface, const QPoint& position)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceEnter, m_compositor);
command.parameters << QVariant::fromValue(surface) << QVariant::fromValue(position);
processCommand(command);
}
void MockCompositor::sendDataDeviceMotion(const QPoint &position)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceMotion, m_compositor);
command.parameters << QVariant::fromValue(position);
processCommand(command);
}
void MockCompositor::sendDataDeviceDrop(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceDrop, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendDataDeviceLeave(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceLeave, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendAddOutput()
{
Command command = makeCommand(Impl::Compositor::sendAddOutput, m_compositor);
processCommand(command);
}
void MockCompositor::sendRemoveOutput(const QSharedPointer<MockOutput> &output)
{
Command command = makeCommand(Impl::Compositor::sendRemoveOutput, m_compositor);
command.parameters << QVariant::fromValue(output);
processCommand(command);
}
void MockCompositor::sendOutputGeometry(const QSharedPointer<MockOutput> &output, const QRect &geometry)
{
Command command = makeCommand(Impl::Compositor::sendOutputGeometry, m_compositor);
command.parameters << QVariant::fromValue(output);
command.parameters << QVariant::fromValue(geometry);
processCommand(command);
}
void MockCompositor::sendSurfaceEnter(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output)
{
Command command = makeCommand(Impl::Compositor::sendSurfaceEnter, m_compositor);
command.parameters << QVariant::fromValue(surface);
command.parameters << QVariant::fromValue(output);
processCommand(command);
}
void MockCompositor::sendSurfaceLeave(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output)
{
Command command = makeCommand(Impl::Compositor::sendSurfaceLeave, m_compositor);
command.parameters << QVariant::fromValue(surface);
command.parameters << QVariant::fromValue(output);
processCommand(command);
}
void MockCompositor::sendShellSurfaceConfigure(const QSharedPointer<MockSurface> surface, const QSize &size)
{
Command command = makeCommand(Impl::Compositor::sendShellSurfaceConfigure, m_compositor);
command.parameters << QVariant::fromValue(surface);
command.parameters << QVariant::fromValue(size);
processCommand(command);
}
void MockCompositor::sendIviSurfaceConfigure(const QSharedPointer<MockIviSurface> iviSurface, const QSize &size)
{
Command command = makeCommand(Impl::Compositor::sendIviSurfaceConfigure, m_compositor);
command.parameters << QVariant::fromValue(iviSurface);
command.parameters << QVariant::fromValue(size);
processCommand(command);
}
void MockCompositor::sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size, const QVector<uint> &states)
{
Command command = makeCommand(Impl::Compositor::sendXdgToplevelV6Configure, m_compositor);
command.parameters << QVariant::fromValue(toplevel);
command.parameters << QVariant::fromValue(size);
QByteArray statesBytes(reinterpret_cast<const char *>(states.data()),
states.size() * static_cast<int>(sizeof(uint)));
command.parameters << statesBytes;
processCommand(command);
}
void MockCompositor::waitForStartDrag()
{
Command command = makeCommand(Impl::Compositor::waitForStartDrag, m_compositor);
processCommand(command);
}
QSharedPointer<MockSurface> MockCompositor::surface()
{
QSharedPointer<MockSurface> result;
lock();
QVector<Impl::Surface *> surfaces = m_compositor->surfaces();
foreach (Impl::Surface *surface, surfaces) {
// we don't want to mistake the cursor surface for a window surface
if (surface->isMapped()) {
result = surface->mockSurface();
break;
}
}
unlock();
return result;
}
QSharedPointer<MockOutput> MockCompositor::output(int index)
{
QSharedPointer<MockOutput> result;
lock();
if (Impl::Output *output = m_compositor->outputs().value(index, nullptr))
result = output->mockOutput();
unlock();
return result;
}
QSharedPointer<MockIviSurface> MockCompositor::iviSurface(int index)
{
QSharedPointer<MockIviSurface> result;
lock();
if (Impl::IviSurface *toplevel = m_compositor->iviApplication()->iviSurfaces().value(index, nullptr))
result = toplevel->mockIviSurface();
unlock();
return result;
}
QSharedPointer<MockXdgToplevelV6> MockCompositor::xdgToplevelV6(int index)
{
QSharedPointer<MockXdgToplevelV6> result;
lock();
if (Impl::XdgToplevelV6 *toplevel = m_compositor->xdgShellV6()->toplevels().value(index, nullptr))
result = toplevel->mockToplevel();
unlock();
return result;
}
QSharedPointer<MockSurface> MockCompositor::fullScreenShellV1Surface(int index)
{
QSharedPointer<MockSurface> result;
lock();
if (Impl::Surface *surface = m_compositor->fullScreenShellV1()->surfaces().value(index, nullptr))
result = surface->mockSurface();
unlock();
return result;
}
MockCompositor::Command MockCompositor::makeCommand(Command::Callback callback, void *target)
{
Command command;
command.callback = callback;
command.target = target;
return command;
}
void MockCompositor::processCommand(const Command &command)
{
lock();
m_commandQueue << command;
unlock();
m_waitCondition.wakeOne();
}
void MockCompositor::dispatchCommands()
{
lock();
int count = m_commandQueue.length();
unlock();
for (int i = 0; i < count; ++i) {
lock();
const Command command = m_commandQueue.takeFirst();
unlock();
command.callback(command.target, command.parameters);
}
}
void *MockCompositor::run(void *data)
{
MockCompositor *controller = static_cast<MockCompositor *>(data);
Impl::Compositor compositor;
controller->m_compositor = &compositor;
controller->m_waitCondition.wakeOne();
while (!controller->m_ready) {
controller->dispatchCommands();
compositor.dispatchEvents(20);
}
while (controller->m_alive) {
{
QMutexLocker locker(&controller->m_mutex);
if (controller->m_commandQueue.isEmpty())
controller->m_waitCondition.wait(&controller->m_mutex);
}
controller->dispatchCommands();
compositor.dispatchEvents(20);
}
return 0;
}
namespace Impl {
Compositor::Compositor()
: m_display(wl_display_create())
{
if (wl_display_add_socket(m_display, 0)) {
fprintf(stderr, "Fatal: Failed to open server socket\n");
exit(EXIT_FAILURE);
}
wl_global_create(m_display, &wl_compositor_interface, 1, this, bindCompositor);
m_data_device_manager.reset(new DataDeviceManager(this, m_display));
wl_display_init_shm(m_display);
m_seat.reset(new Seat(this, m_display));
m_pointer = m_seat->pointer();
m_keyboard = m_seat->keyboard();
m_touch = m_seat->touch();
m_outputs.append(new Output(m_display, QSize(1920, 1080), QPoint(0, 0)));
m_iviApplication.reset(new IviApplication(m_display));
m_wlShell.reset(new WlShell(m_display));
m_xdgShellV6.reset(new XdgShellV6(m_display));
m_fullScreenShellV1.reset(new FullScreenShellV1(m_display));
m_loop = wl_display_get_event_loop(m_display);
m_fd = wl_event_loop_get_fd(m_loop);
}
Compositor::~Compositor()
{
wl_display_destroy(m_display);
}
void Compositor::dispatchEvents(int timeout)
{
wl_display_flush_clients(m_display);
wl_event_loop_dispatch(m_loop, timeout);
}
static void compositor_create_surface(wl_client *client, wl_resource *compositorResource, uint32_t id)
{
Compositor *compositor = static_cast<Compositor *>(wl_resource_get_user_data(compositorResource));
compositor->addSurface(new Surface(client, id, wl_resource_get_version(compositorResource), compositor));
}
static void compositor_create_region(wl_client *client, wl_resource *compositorResource, uint32_t id)
{
Q_UNUSED(client);
Q_UNUSED(compositorResource);
Q_UNUSED(id);
}
void Compositor::bindCompositor(wl_client *client, void *compositorData, uint32_t version, uint32_t id)
{
static const struct wl_compositor_interface compositorInterface = {
compositor_create_surface,
compositor_create_region
};
wl_resource *resource = wl_resource_create(client, &wl_compositor_interface, static_cast<int>(version), id);
wl_resource_set_implementation(resource, &compositorInterface, compositorData, nullptr);
}
static void unregisterResourceCallback(wl_listener *listener, void *data)
{
struct wl_resource *resource = reinterpret_cast<struct wl_resource *>(data);
wl_list_remove(wl_resource_get_link(resource));
delete listener;
}
void registerResource(wl_list *list, wl_resource *resource)
{
wl_list_insert(list, wl_resource_get_link(resource));
wl_listener *listener = new wl_listener;
listener->notify = unregisterResourceCallback;
wl_resource_add_destroy_listener(resource, listener);
}
QVector<Surface *> Compositor::surfaces() const
{
return m_surfaces;
}
QVector<Output *> Compositor::outputs() const
{
return m_outputs;
}
IviApplication *Compositor::iviApplication() const
{
return m_iviApplication.data();
}
XdgShellV6 *Compositor::xdgShellV6() const
{
return m_xdgShellV6.data();
}
FullScreenShellV1 *Compositor::fullScreenShellV1() const
{
return m_fullScreenShellV1.data();
}
uint32_t Compositor::nextSerial()
{
return wl_display_next_serial(m_display);
}
void Compositor::addSurface(Surface *surface)
{
m_surfaces << surface;
}
void Compositor::removeSurface(Surface *surface)
{
m_surfaces.removeOne(surface);
m_keyboard->handleSurfaceDestroyed(surface);
m_pointer->handleSurfaceDestroyed(surface);
m_fullScreenShellV1->removeSurface(surface);
}
Surface *Compositor::resolveSurface(const QVariant &v)
{
QSharedPointer<MockSurface> mockSurface = v.value<QSharedPointer<MockSurface> >();
return mockSurface ? mockSurface->handle() : nullptr;
}
Output *Compositor::resolveOutput(const QVariant &v)
{
QSharedPointer<MockOutput> mockOutput = v.value<QSharedPointer<MockOutput> >();
return mockOutput ? mockOutput->handle() : nullptr;
}
IviSurface *Compositor::resolveIviSurface(const QVariant &v)
{
QSharedPointer<MockIviSurface> mockIviSurface = v.value<QSharedPointer<MockIviSurface>>();
return mockIviSurface ? mockIviSurface->handle() : nullptr;
}
XdgToplevelV6 *Compositor::resolveToplevel(const QVariant &v)
{
QSharedPointer<MockXdgToplevelV6> mockToplevel = v.value<QSharedPointer<MockXdgToplevelV6>>();
return mockToplevel ? mockToplevel->handle() : nullptr;
}
}

View File

@ -0,0 +1,290 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef MOCKCOMPOSITOR_H
#define MOCKCOMPOSITOR_H
#include "mockxdgshellv6.h"
#include "mockiviapplication.h"
#include "mockfullscreenshellv1.h"
#include <pthread.h>
#include <qglobal.h>
#include <wayland-server-core.h>
#include <QImage>
#include <QMutex>
#include <QRect>
#include <QSharedPointer>
#include <QVariant>
#include <QVector>
#include <QWaitCondition>
namespace Impl {
typedef void (**Implementation)(void);
class Keyboard;
class Pointer;
class Touch;
class Seat;
class DataDeviceManager;
class Surface;
class Output;
class IviApplication;
class WlShell;
class XdgShellV6;
class Compositor
{
public:
Compositor();
~Compositor();
int fileDescriptor() const { return m_fd; }
void dispatchEvents(int timeout = 0);
uint32_t nextSerial();
uint32_t time() { return ++m_time; }
QVector<Surface *> surfaces() const;
QVector<Output *> outputs() const;
IviApplication *iviApplication() const;
XdgShellV6 *xdgShellV6() const;
FullScreenShellV1 *fullScreenShellV1() const;
void addSurface(Surface *surface);
void removeSurface(Surface *surface);
static void setKeyboardFocus(void *data, const QList<QVariant> &parameters);
static void sendMousePress(void *data, const QList<QVariant> &parameters);
static void sendMouseRelease(void *data, const QList<QVariant> &parameters);
static void sendKeyPress(void *data, const QList<QVariant> &parameters);
static void sendKeyRelease(void *data, const QList<QVariant> &parameters);
static void sendTouchDown(void *data, const QList<QVariant> &parameters);
static void sendTouchUp(void *data, const QList<QVariant> &parameters);
static void sendTouchMotion(void *data, const QList<QVariant> &parameters);
static void sendTouchFrame(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceDataOffer(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceEnter(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceMotion(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceDrop(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceLeave(void *data, const QList<QVariant> &parameters);
static void waitForStartDrag(void *data, const QList<QVariant> &parameters);
static void setOutputMode(void *compositor, const QList<QVariant> &parameters);
static void sendAddOutput(void *data, const QList<QVariant> &parameters);
static void sendRemoveOutput(void *data, const QList<QVariant> &parameters);
static void sendOutputGeometry(void *data, const QList<QVariant> &parameters);
static void sendSurfaceEnter(void *data, const QList<QVariant> &parameters);
static void sendSurfaceLeave(void *data, const QList<QVariant> &parameters);
static void sendShellSurfaceConfigure(void *data, const QList<QVariant> &parameters);
static void sendIviSurfaceConfigure(void *data, const QList<QVariant> &parameters);
static void sendXdgToplevelV6Configure(void *data, const QList<QVariant> &parameters);
public:
bool m_startDragSeen = false;
private:
static void bindCompositor(wl_client *client, void *data, uint32_t version, uint32_t id);
static Surface *resolveSurface(const QVariant &v);
static Output *resolveOutput(const QVariant &v);
static IviSurface *resolveIviSurface(const QVariant &v);
static XdgToplevelV6 *resolveToplevel(const QVariant &v);
void initShm();
QRect m_outputGeometry;
wl_display *m_display = nullptr;
wl_event_loop *m_loop = nullptr;
int m_fd = -1;
uint32_t m_time = 0;
QScopedPointer<Seat> m_seat;
Pointer *m_pointer = nullptr;
Keyboard *m_keyboard = nullptr;
Touch *m_touch = nullptr;
QScopedPointer<DataDeviceManager> m_data_device_manager;
QVector<Surface *> m_surfaces;
QVector<Output *> m_outputs;
QScopedPointer<IviApplication> m_iviApplication;
QScopedPointer<WlShell> m_wlShell;
QScopedPointer<XdgShellV6> m_xdgShellV6;
QScopedPointer<FullScreenShellV1> m_fullScreenShellV1;
};
void registerResource(wl_list *list, wl_resource *resource);
}
class MockSurface
{
public:
Impl::Surface *handle() const { return m_surface; }
QImage image;
private:
MockSurface(Impl::Surface *surface);
friend class Impl::Compositor;
friend class Impl::Surface;
Impl::Surface *m_surface = nullptr;
};
Q_DECLARE_METATYPE(QSharedPointer<MockSurface>)
class MockIviSurface
{
public:
Impl::IviSurface *handle() const { return m_iviSurface; }
const uint iviId;
private:
MockIviSurface(Impl::IviSurface *iviSurface) : iviId(iviSurface->iviId()), m_iviSurface(iviSurface) {}
friend class Impl::Compositor;
friend class Impl::IviSurface;
Impl::IviSurface *m_iviSurface;
};
Q_DECLARE_METATYPE(QSharedPointer<MockIviSurface>)
class MockXdgToplevelV6 : public QObject
{
Q_OBJECT
public:
Impl::XdgToplevelV6 *handle() const { return m_toplevel; }
void sendConfigure(const QSharedPointer<MockXdgToplevelV6> toplevel);
signals:
uint setMinimizedRequested();
uint setMaximizedRequested();
uint unsetMaximizedRequested();
uint setFullscreenRequested();
uint unsetFullscreenRequested();
void windowGeometryRequested(QRect geometry); // NOTE: This is really an xdg surface event
private:
MockXdgToplevelV6(Impl::XdgToplevelV6 *toplevel) : m_toplevel(toplevel) {}
friend class Impl::Compositor;
friend class Impl::XdgToplevelV6;
Impl::XdgToplevelV6 *m_toplevel;
};
Q_DECLARE_METATYPE(QSharedPointer<MockXdgToplevelV6>)
class MockOutput {
public:
Impl::Output *handle() const { return m_output; }
MockOutput(Impl::Output *output);
private:
Impl::Output *m_output = nullptr;
};
Q_DECLARE_METATYPE(QSharedPointer<MockOutput>)
class MockCompositor
{
public:
MockCompositor();
~MockCompositor();
void applicationInitialized();
int waylandFileDescriptor() const;
void processWaylandEvents();
void setOutputMode(const QSize &size);
void setKeyboardFocus(const QSharedPointer<MockSurface> &surface);
void sendMousePress(const QSharedPointer<MockSurface> &surface, const QPoint &pos);
void sendMouseRelease(const QSharedPointer<MockSurface> &surface);
void sendKeyPress(const QSharedPointer<MockSurface> &surface, uint code);
void sendKeyRelease(const QSharedPointer<MockSurface> &surface, uint code);
void sendTouchDown(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id);
void sendTouchMotion(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id);
void sendTouchUp(const QSharedPointer<MockSurface> &surface, int id);
void sendTouchFrame(const QSharedPointer<MockSurface> &surface);
void sendDataDeviceDataOffer(const QSharedPointer<MockSurface> &surface);
void sendDataDeviceEnter(const QSharedPointer<MockSurface> &surface, const QPoint &position);
void sendDataDeviceMotion(const QPoint &position);
void sendDataDeviceDrop(const QSharedPointer<MockSurface> &surface);
void sendDataDeviceLeave(const QSharedPointer<MockSurface> &surface);
void sendAddOutput();
void sendRemoveOutput(const QSharedPointer<MockOutput> &output);
void sendOutputGeometry(const QSharedPointer<MockOutput> &output, const QRect &geometry);
void sendSurfaceEnter(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output);
void sendSurfaceLeave(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output);
void sendShellSurfaceConfigure(const QSharedPointer<MockSurface> surface, const QSize &size = QSize(0, 0));
void sendIviSurfaceConfigure(const QSharedPointer<MockIviSurface> iviSurface, const QSize &size);
void sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size = QSize(0, 0),
const QVector<uint> &states = { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED });
void waitForStartDrag();
QSharedPointer<MockSurface> surface();
QSharedPointer<MockOutput> output(int index = 0);
QSharedPointer<MockIviSurface> iviSurface(int index = 0);
QSharedPointer<MockXdgToplevelV6> xdgToplevelV6(int index = 0);
QSharedPointer<MockSurface> fullScreenShellV1Surface(int index = 0);
void lock();
void unlock();
private:
struct Command
{
typedef void (*Callback)(void *target, const QList<QVariant> &parameters);
Callback callback;
void *target = nullptr;
QList<QVariant> parameters;
};
static Command makeCommand(Command::Callback callback, void *target);
void processCommand(const Command &command);
void dispatchCommands();
static void *run(void *data);
bool m_alive = true;
bool m_ready = false;
pthread_t m_thread;
QMutex m_mutex;
QWaitCondition m_waitCondition;
Impl::Compositor *m_compositor = nullptr;
QList<Command> m_commandQueue;
};
#endif

View File

@ -0,0 +1,34 @@
CONFIG += testcase link_pkgconfig
QT += testlib
QT += core-private gui-private waylandclient-private
QMAKE_USE += wayland-client wayland-server
CONFIG += wayland-scanner
WAYLANDSERVERSOURCES += \
../../../../src/3rdparty/protocol/ivi-application.xml \
../../../../src/3rdparty/protocol/wayland.xml \
../../../../src/3rdparty/protocol/xdg-shell-unstable-v6.xml \
../../../../src/3rdparty/protocol/fullscreen-shell-unstable-v1.xml
INCLUDEPATH += ../shared_old
SOURCES += \
../shared_old/mockcompositor.cpp \
../shared_old/mockfullscreenshellv1.cpp \
../shared_old/mockinput.cpp \
../shared_old/mockiviapplication.cpp \
../shared_old/mockwlshell.cpp \
../shared_old/mockxdgshellv6.cpp \
../shared_old/mocksurface.cpp \
../shared_old/mockoutput.cpp
HEADERS += \
../shared_old/mockcompositor.h \
../shared_old/mockfullscreenshellv1.h \
../shared_old/mockinput.h \
../shared_old/mockiviapplication.h \
../shared_old/mockwlshell.h \
../shared_old/mockxdgshellv6.h \
../shared_old/mocksurface.h \
../shared_old/mockoutput.h

View File

@ -0,0 +1,5 @@
include (../shared/shared.pri)
TARGET = tst_surface
SOURCES += tst_surface.cpp

View File

@ -0,0 +1,158 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mockcompositor.h"
#include <QtGui/QRasterWindow>
#include <QtGui/QOpenGLWindow>
using namespace MockCompositor;
class tst_surface : public QObject, private DefaultCompositor
{
Q_OBJECT
private slots:
void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); }
void createDestroySurface();
void waitForFrameCallbackRaster();
void waitForFrameCallbackGl();
void negotiateShmFormat();
};
void tst_surface::createDestroySurface()
{
QWindow window;
window.show();
QCOMPOSITOR_TRY_VERIFY(surface());
window.destroy();
QCOMPOSITOR_TRY_VERIFY(!surface());
}
void tst_surface::waitForFrameCallbackRaster()
{
QSKIP("TODO: This currently fails, needs a fix");
class TestWindow : public QRasterWindow {
public:
explicit TestWindow() { resize(40, 40); }
void paintEvent(QPaintEvent *event) override
{
Q_UNUSED(event);
update();
}
};
TestWindow window;
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgToplevel());
QSignalSpy bufferSpy(exec([=] { return xdgSurface()->m_surface; }), &Surface::bufferCommitted);
exec([=] { xdgToplevel()->sendCompleteConfigure(); });
// We should get the first buffer without waiting for a frame callback
QTRY_COMPARE(bufferSpy.count(), 1);
bufferSpy.removeFirst();
// Make sure we follow frame callbacks for some frames
for (int i = 0; i < 5; ++i) {
xdgPingAndWaitForPong(); // Make sure things have happened on the client
exec([&] {
QVERIFY(bufferSpy.empty()); // Make sure no extra buffers have arrived
QVERIFY(!xdgToplevel()->surface()->m_waitingFrameCallbacks.empty());
xdgToplevel()->surface()->sendFrameCallbacks();
});
QTRY_COMPARE(bufferSpy.count(), 1);
bufferSpy.removeFirst();
}
}
void tst_surface::waitForFrameCallbackGl()
{
QSKIP("TODO: This currently fails, needs a fix");
class TestWindow : public QOpenGLWindow {
public:
explicit TestWindow()
{
resize(40, 40);
connect(this, &QOpenGLWindow::frameSwapped,
this, QOverload<>::of(&QPaintDeviceWindow::update));
update();
}
void paintGL() override
{
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
};
TestWindow window;
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgToplevel());
QSignalSpy bufferSpy(exec([=] { return xdgSurface()->m_surface; }), &Surface::bufferCommitted);
exec([=] { xdgToplevel()->sendCompleteConfigure(); });
// We should get the first buffer without waiting for a frame callback
QTRY_COMPARE(bufferSpy.count(), 1);
bufferSpy.removeFirst();
// Make sure we follow frame callbacks for some frames
for (int i = 0; i < 5; ++i) {
xdgPingAndWaitForPong(); // Make sure things have happened on the client
exec([&] {
QVERIFY(bufferSpy.empty()); // Make sure no extra buffers have arrived
QVERIFY(!xdgToplevel()->surface()->m_waitingFrameCallbacks.empty());
xdgToplevel()->surface()->sendFrameCallbacks();
});
QTRY_COMPARE(bufferSpy.count(), 1);
bufferSpy.removeFirst();
}
}
void tst_surface::negotiateShmFormat()
{
QSKIP("TODO: I'm not sure why we're choosing xrgb over argb in this case...");
QRasterWindow window;
window.setFlag(Qt::FramelessWindowHint); // decorations force alpha
QSurfaceFormat format;
format.setAlphaBufferSize(0);
window.setFormat(format);
window.resize(64, 48);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgToplevel());
QSignalSpy bufferSpy(exec([=] { return xdgSurface()->m_surface; }), &Surface::bufferCommitted);
const uint serial = exec([=] { return xdgToplevel()->sendCompleteConfigure(); });
QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, serial);
exec([&] {
Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer;
QVERIFY(buffer);
auto *shmBuffer = ShmBuffer::fromBuffer(buffer);
QVERIFY(shmBuffer);
qDebug() << "shmBuffer->m_format" << shmBuffer->m_format;
QCOMPARE(shmBuffer->m_format, Shm::format_xrgb8888);
});
}
QCOMPOSITOR_TEST_MAIN(tst_surface)
#include "tst_surface.moc"

View File

@ -0,0 +1,269 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mockcompositor.h"
#include <QtGui/QRasterWindow>
#include <QtGui/QOpenGLWindow>
using namespace MockCompositor;
class tst_xdgshell : public QObject, private DefaultCompositor
{
Q_OBJECT
private slots:
void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); }
void showMinimized();
void basicConfigure();
void configureSize();
void configureStates();
void popup();
void pongs();
};
void tst_xdgshell::showMinimized()
{
QSKIP("TODO: This currently fails, needs a fix");
// On xdg-shell there's really no way for the compositor to tell the window if it's minimized
// There are wl_surface.enter events and so on, but there's really no way to differentiate
// between a window preview and an unminimized window.
QWindow window;
window.showMinimized();
QCOMPARE(window.windowStates(), Qt::WindowMinimized); // should return minimized until
QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); // rejected by handleWindowStateChanged
// Make sure the window on the compositor side is/was created here, and not after the test
// finishes, as that may mess up for later tests.
QCOMPOSITOR_TRY_VERIFY(surface());
QVERIFY(!window.isExposed());
}
void tst_xdgshell::basicConfigure()
{
QRasterWindow window;
window.resize(64, 48);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgToplevel());
QSignalSpy configureSpy(exec([=] { return xdgSurface(); }), &XdgSurface::configureCommitted);
QTRY_VERIFY(window.isVisible());
// The window should not be exposed before the first xdg_surface configure event
QTRY_VERIFY(!window.isExposed());
exec([=] {
xdgToplevel()->sendConfigure({0, 0}, {}); // Let the window decide the size
});
// Nothing should happen before the *xdg_surface* configure
QTRY_VERIFY(!window.isExposed()); //Window should not be exposed before the first configure event
QVERIFY(configureSpy.isEmpty());
const uint serial = exec([=] { return nextSerial(); });
exec([=] {
xdgSurface()->sendConfigure(serial);
});
// Finally, we're exposed
QTRY_VERIFY(window.isExposed());
// The client is now going to ack the configure
QTRY_COMPARE(configureSpy.count(), 1);
QCOMPARE(configureSpy.takeFirst().at(0).toUInt(), serial);
// And attach a buffer
exec([&] {
Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer;
QVERIFY(buffer);
QCOMPARE(buffer->size(), window.frameGeometry().size());
});
}
void tst_xdgshell::configureSize()
{
QRasterWindow window;
window.resize(64, 48);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgToplevel());
QSignalSpy configureSpy(exec([=] { return xdgSurface(); }), &XdgSurface::configureCommitted);
const QSize configureSize(60, 40);
exec([=] {
xdgToplevel()->sendCompleteConfigure(configureSize);
});
QTRY_COMPARE(configureSpy.count(), 1);
exec([=] {
Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer;
QVERIFY(buffer);
QCOMPARE(buffer->size(), configureSize);
});
}
void tst_xdgshell::configureStates()
{
QRasterWindow window;
window.resize(64, 48);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgToplevel());
const QSize windowedSize(320, 240);
const uint windowedSerial = exec([=] {
return xdgToplevel()->sendCompleteConfigure(windowedSize, { XdgToplevel::state_activated });
});
QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, windowedSerial);
QCOMPARE(window.visibility(), QWindow::Windowed);
QCOMPARE(window.windowStates(), Qt::WindowNoState);
QCOMPARE(window.frameGeometry().size(), windowedSize);
// Toplevel windows don't know their position on xdg-shell
// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled
// QEXPECT_FAIL("", "configure has already been acked, we shouldn't have to wait for isActive", Continue);
// QVERIFY(window.isActive());
QTRY_VERIFY(window.isActive()); // Just make sure it eventually get's set correctly
const QSize screenSize(640, 480);
const uint maximizedSerial = exec([=] {
return xdgToplevel()->sendCompleteConfigure(screenSize, { XdgToplevel::state_activated, XdgToplevel::state_maximized });
});
QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, maximizedSerial);
QCOMPARE(window.visibility(), QWindow::Maximized);
QCOMPARE(window.windowStates(), Qt::WindowMaximized);
QCOMPARE(window.frameGeometry().size(), screenSize);
// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled
const uint fullscreenSerial = exec([=] {
return xdgToplevel()->sendCompleteConfigure(screenSize, { XdgToplevel::state_activated, XdgToplevel::state_fullscreen });
});
QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, fullscreenSerial);
QCOMPARE(window.visibility(), QWindow::FullScreen);
QCOMPARE(window.windowStates(), Qt::WindowFullScreen);
QCOMPARE(window.frameGeometry().size(), screenSize);
// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled
// The window should remember its original size
const uint restoreSerial = exec([=] {
return xdgToplevel()->sendCompleteConfigure({0, 0}, { XdgToplevel::state_activated });
});
QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, restoreSerial);
QCOMPARE(window.visibility(), QWindow::Windowed);
QCOMPARE(window.windowStates(), Qt::WindowNoState);
QCOMPARE(window.frameGeometry().size(), windowedSize);
// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled
}
void tst_xdgshell::popup()
{
class Window : public QRasterWindow {
public:
void mousePressEvent(QMouseEvent *event) override
{
QRasterWindow::mousePressEvent(event);
m_popup.reset(new QRasterWindow);
m_popup->setTransientParent(this);
m_popup->setFlags(Qt::Popup);
m_popup->resize(100, 100);
m_popup->show();
}
QScopedPointer<QRasterWindow> m_popup;
};
Window window;
window.resize(200, 200);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgToplevel());
QSignalSpy toplevelConfigureSpy(exec([=] { return xdgSurface(); }), &XdgSurface::configureCommitted);
exec([=] { xdgToplevel()->sendCompleteConfigure(); });
QTRY_COMPARE(toplevelConfigureSpy.count(), 1);
uint clickSerial = exec([=] {
auto *surface = xdgToplevel()->surface();
auto *p = pointer();
p->sendEnter(surface, {100, 100});
// p->sendFrame(); //TODO: uncomment when we support seat v5
uint serial = p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed);
p->sendButton(client(), BTN_LEFT, Pointer::button_state_released);
return serial;
// p->sendFrame(); //TODO: uncomment when we support seat v5
});
QTRY_VERIFY(window.m_popup);
QCOMPOSITOR_TRY_VERIFY(xdgPopup());
QSignalSpy popupConfigureSpy(exec([=] { return xdgPopup()->m_xdgSurface; }), &XdgSurface::configureCommitted);
QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed);
QCOMPOSITOR_TRY_COMPARE(xdgPopup()->m_grabSerial, clickSerial);
QRasterWindow *popup = window.m_popup.get();
QVERIFY(!popup->isExposed()); // wait for configure
//TODO: Verify it works with a different configure window geometry
exec([=] { xdgPopup()->sendConfigure(QRect(100, 100, 100, 100)); });
// Nothing should happen before the *xdg_surface* configure
QTRY_VERIFY(!popup->isExposed()); // Popup shouldn't be exposed before the first configure event
QVERIFY(popupConfigureSpy.isEmpty());
const uint configureSerial = exec([=] {
return xdgPopup()->m_xdgSurface->sendConfigure();
});
// Finally, we're exposed
QTRY_VERIFY(popup->isExposed());
// The client is now going to ack the configure
QTRY_COMPARE(popupConfigureSpy.count(), 1);
QCOMPARE(popupConfigureSpy.takeFirst().at(0).toUInt(), configureSerial);
// And attach a buffer
exec([&] {
Buffer *buffer = xdgPopup()->surface()->m_committed.buffer;
QVERIFY(buffer);
QCOMPARE(buffer->size(), popup->frameGeometry().size());
});
}
void tst_xdgshell::pongs()
{
QSignalSpy pongSpy(exec([=] { return get<XdgWmBase>(); }), &XdgWmBase::pong);
// Verify that the client has bound to the global
QCOMPOSITOR_TRY_COMPARE(get<XdgWmBase>()->resourceMap().size(), 1);
const uint serial = exec([=] { return nextSerial(); });
exec([=] {
auto *base = get<XdgWmBase>();
wl_resource *resource = base->resourceMap().first()->handle;
base->send_ping(resource, serial);
});
QTRY_COMPARE(pongSpy.count(), 1);
QCOMPARE(pongSpy.first().at(0).toUInt(), serial);
}
QCOMPOSITOR_TEST_MAIN(tst_xdgshell)
#include "tst_xdgshell.moc"

View File

@ -0,0 +1,5 @@
include (../shared/shared.pri)
TARGET = tst_xdgshell
SOURCES += tst_xdgshell.cpp

View File

@ -1,4 +1,4 @@
include (../shared/shared.pri)
include (../shared_old/shared_old.pri)
TARGET = tst_client_xdgshellv6
SOURCES += tst_xdgshellv6.cpp