Commit 8e0524f3 authored by Marco Martin's avatar Marco Martin
Browse files

ScreenPool as the source of truth of QScreen info

index desktop views by qscreen instead of id, making ScreenPool
the single source of truth for the mapping between screen names
and ids. This is less error prone and easier to consistency check
(if view->screenToFollow() is ever different to its has key it will
assert)
The whole logic of screen management is moved to ScreenPool.
ShellCorona will have to never call QGuiApp->screens, but only trust what ScreenPool it's telling it

Also adds an autotests on screenpool which makes a fake wayland server which sends screen added/removed/changed events
parent 3e32e78e
......@@ -130,7 +130,7 @@ find_package(QtWaylandScanner REQUIRED)
find_package(Qt5WaylandClient)
find_package(Qt5XkbCommonSupport)
find_package(PlasmaWaylandProtocols 1.6 REQUIRED)
find_package(Wayland REQUIRED COMPONENTS Client)
find_package(Wayland REQUIRED COMPONENTS Client Server) # Server is used in autotests
if(FONTCONFIG_FOUND)
# kfontinst
......
......@@ -23,6 +23,10 @@ ecm_qt_declare_logging_category(plasmashell HEADER debug.h
CATEGORY_NAME kde.plasmashell
DEFAULT_SEVERITY Info)
ecm_qt_declare_logging_category(plasmashell HEADER screenpool-debug.h
IDENTIFIER SCREENPOOL
CATEGORY_NAME kde.plasmashell.screenpool
DEFAULT_SEVERITY Info)
set (plasma_shell_SRCS
alternativeshelper.cpp
main.cpp
......@@ -39,6 +43,7 @@ set (plasma_shell_SRCS
coronatesthelper.cpp
strutmanager.cpp
debug.cpp
screenpool-debug.cpp
screenpool.cpp
softwarerendernotifier.cpp
${scripting_SRC}
......@@ -116,4 +121,5 @@ install(FILES
add_subdirectory(packageplugins)
if(BUILD_TESTING)
add_subdirectory(autotests)
add_subdirectory(tests)
endif()
add_subdirectory(mockserver)
include(ECMAddTests)
include_directories(${CMAKE_CURRENT_BINARY_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/..)
MACRO(PLASMASHELL_UNIT_TESTS)
FOREACH(_testname ${ARGN})
add_executable(${_testname} ${_testname}.cpp ../screenpool.cpp )
set(test_SRCS
${_testname}.cpp ../screenpool.cpp ${CMAKE_CURRENT_BINARY_DIR}/../screenpool-debug.cpp ../primaryoutputwatcher.cpp
)
include_directories(${CMAKE_CURRENT_BINARY_DIR}/../mockserver)
add_executable(${_testname} ${test_SRCS})
target_link_libraries(${_testname}
Qt::Test
Qt::Gui
KF5::Service
KF5::WaylandClient
KF5::WindowSystem
Wayland::Client
Wayland::Server
SharedClientTest
)
if(HAVE_X11)
target_link_libraries(${_testname} XCB::XCB XCB::RANDR)
......
project(waylandmockservertest)
find_package(WaylandProtocols 1.24)
set_package_properties(WaylandProtocols PROPERTIES TYPE REQUIRED)
find_package(Threads)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_USE_NANOSLEEP" )
set(SharedClientTest_LIB_SRCS
corecompositor.cpp corecompositor.h
coreprotocol.cpp coreprotocol.h
mockcompositor.cpp mockcompositor.h
xdgoutputv1.cpp xdgoutputv1.h
primaryoutput.cpp primaryoutput.h
)
ecm_add_qtwayland_server_protocol(SharedClientTest_LIB_SRCS
PROTOCOL ${Wayland_DATADIR}/wayland.xml
BASENAME wayland
)
ecm_add_qtwayland_server_protocol(SharedClientTest_LIB_SRCS
PROTOCOL ${WaylandProtocols_DATADIR}/unstable/xdg-output/xdg-output-unstable-v1.xml
BASENAME xdg-output-unstable-v1
)
ecm_add_qtwayland_client_protocol(SharedClientTest_LIB_SRCS
PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-primary-output-v1.xml
BASENAME kde-primary-output-v1
)
ecm_add_qtwayland_server_protocol(SharedClientTest_LIB_SRCS
PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-primary-output-v1.xml
BASENAME kde-primary-output-v1
)
add_library(SharedClientTest OBJECT ${SharedClientTest_LIB_SRCS})
target_link_libraries(SharedClientTest
PUBLIC
Qt::Test
Qt::Gui
Qt::WaylandClientPrivate
Wayland::Server
Threads::Threads
)
target_include_directories(SharedClientTest PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
/****************************************************************************
**
** 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"
#include <thread>
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();
}
})
{
qputenv("WAYLAND_DISPLAY", m_socketName);
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);
m_globals.removeAll(global);
delete global;
}
uint CoreCompositor::nextSerial()
{
warnIfNotLockedByThread(Q_FUNC_INFO);
return wl_display_next_serial(m_display);
}
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
/****************************************************************************
**
** 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)...);
}
// 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 the nth global with the given type, if any
*/
template<typename global_type>
global_type *get(int index)
{
warnIfNotLockedByThread(Q_FUNC_INFO);
for (auto *global : qAsConst(m_globals)) {
if (auto *casted = qobject_cast<global_type *>(global)) {
if (index--)
continue;
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();
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;
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
/****************************************************************************
**
** 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 Output::sendGeometry()
{
const auto resources = resourceMap().values();
for (auto r : resources)
sendGeometry(r);
}
void Output::sendGeometry(Resource *resource)
{
wl_output::send_geometry(resource->handle,
m_data.position.x(),
m_data.position.y(),
m_data.physicalSize.width(),
m_data.physicalSize.height(),
m_data.subpixel,
m_data.make,
m_data.model,
m_data.transform);
}
void Output::sendScale(int factor)
{
m_data.scale = factor;
const auto resources = resourceMap().values();
for (auto r : resources)
sendScale(r);
}
void Output::sendScale(Resource *resource)
{
wl_output::send_scale(resource->handle, m_data.scale);
}
void Output::sendDone(wl_client *client)
{
auto resources = resourceMap().values(client);
for (auto *r : resources)
wl_output::send_done(r->handle);
}
void Output::sendDone()
{
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)
{
sendGeometry(resource);
send_mode(resource->handle, mode_preferred | mode_current, m_data.mode.resolution.width(), m_data.mode.resolution.height(), m_data.mode.refreshRate);
sendScale(resource);
wl_output::send_done(resource->handle);
}
} // namespace MockCompositor
/****************************************************************************
**
** 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 Touch;
class Keyboard;
class CursorRole;
class ShmPool;
class ShmBuffer;