diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index b8f0f515fd..5a2e2ca614 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -54,6 +54,7 @@ set(SERVER_LIB_SRCS xdgforeign_v2_interface.cpp xdgforeign_interface.cpp xdgshell_v6_interface.cpp + xdgoutput_interface.cpp ) ecm_add_wayland_server_protocol(SERVER_LIB_SRCS @@ -180,6 +181,11 @@ ecm_add_wayland_server_protocol(SERVER_LIB_SRCS BASENAME remote-access ) +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/xdg-output-unstable-v1.xml + BASENAME xdg-output +) + set(SERVER_GENERATED_SRCS ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-server-protocol.h @@ -227,6 +233,8 @@ set(SERVER_GENERATED_SRCS ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-foreign-unstable-v2-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-idle-inhibit-unstable-v1-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-idle-inhibit-unstable-v1-server-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-unstable-v1-client-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-unstable-v1-server-protocol.h ) set_source_files_properties(${SERVER_GENERATED_SRCS} PROPERTIES SKIP_AUTOMOC ON) @@ -306,6 +314,7 @@ set(SERVER_LIB_HEADERS touch_interface.h xdgshell_interface.h xdgforeign_interface.h + xdgoutput_interface.h ) install(FILES diff --git a/src/wayland/autotests/client/CMakeLists.txt b/src/wayland/autotests/client/CMakeLists.txt index b71fc8d18a..25ca557c79 100644 --- a/src/wayland/autotests/client/CMakeLists.txt +++ b/src/wayland/autotests/client/CMakeLists.txt @@ -429,3 +429,14 @@ add_executable(testRemoteAccess ${testRemoteAccess_SRCS}) target_link_libraries( testRemoteAccess Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(NAME kwayland-testRemoteAccess COMMAND testRemoteAccess) ecm_mark_as_test(testRemoteAccess) + +######################################################## +# Test XDG Output +######################################################## +set( testXdgOutput_SRCS + test_xdg_output.cpp + ) +add_executable(testXdgOutput ${testXdgOutput_SRCS}) +target_link_libraries( testXdgOutput Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) +add_test(NAME kwayland-testXdgOutput COMMAND testXdgOutput) +ecm_mark_as_test(testXdgOutput) diff --git a/src/wayland/autotests/client/test_xdg_output.cpp b/src/wayland/autotests/client/test_xdg_output.cpp new file mode 100644 index 0000000000..2ea9f8f266 --- /dev/null +++ b/src/wayland/autotests/client/test_xdg_output.cpp @@ -0,0 +1,174 @@ +/******************************************************************** +Copyright 2018 David Edmundson + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +// Qt +#include +// KWin +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/dpms.h" +#include "../../src/client/output.h" +#include "../../src/client/xdgoutput.h" +#include "../../src/client/registry.h" +#include "../../src/server/display.h" +#include "../../src/server/dpms_interface.h" +#include "../../src/server/output_interface.h" +#include "../../src/server/xdgoutput_interface.h" + +// Wayland + +class TestXdgOutput : public QObject +{ + Q_OBJECT +public: + explicit TestXdgOutput(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + void testChanges(); +private: + KWayland::Server::Display *m_display; + KWayland::Server::OutputInterface *m_serverOutput; + KWayland::Server::XdgOutputManagerInterface *m_serverXdgOutputManager; + KWayland::Server::XdgOutputInterface *m_serverXdgOutput; + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::EventQueue *m_queue; + QThread *m_thread; +}; + +static const QString s_socketName = QStringLiteral("kwin-test-xdg-output-0"); + +TestXdgOutput::TestXdgOutput(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_serverOutput(nullptr) + , m_connection(nullptr) + , m_thread(nullptr) +{ +} + +void TestXdgOutput::init() +{ + using namespace KWayland::Server; + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + + m_serverOutput = m_display->createOutput(this); + m_serverOutput->addMode(QSize(1920, 1080), OutputInterface::ModeFlags(OutputInterface::ModeFlag::Preferred)); + m_serverOutput->setCurrentMode(QSize(1920, 1080)); + m_serverOutput->create(); + + m_serverXdgOutputManager = m_display->createXdgOutputManager(this); + m_serverXdgOutputManager->create(); + m_serverXdgOutput = m_serverXdgOutputManager->createXdgOutput(m_serverOutput, this); + m_serverXdgOutput->setLogicalSize(QSize(1280, 720)); //a 1.5 scale factor + m_serverXdgOutput->setLogicalPosition(QPoint(11,12)); //not a sensible value for one monitor, but works for this test + m_serverXdgOutput->done(); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, SIGNAL(connected())); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new KWayland::Client::EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); +} + +void TestXdgOutput::cleanup() +{ + if (m_queue) { + delete m_queue; + m_queue = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + delete m_connection; + m_connection = nullptr; + + delete m_serverOutput; + m_serverOutput = nullptr; + + delete m_display; + m_display = nullptr; +} + +void TestXdgOutput::testChanges() +{ + // verify the server modes + using namespace KWayland::Server; + using namespace KWayland::Client; + KWayland::Client::Registry registry; + QSignalSpy announced(®istry, SIGNAL(outputAnnounced(quint32,quint32))); + QSignalSpy xdgOutputAnnounced(®istry, SIGNAL(xdgOutputAnnounced(quint32,quint32))); + + registry.setEventQueue(m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(announced.wait()); + if (xdgOutputAnnounced.count() != 1) { + QVERIFY(xdgOutputAnnounced.wait()); + } + + KWayland::Client::Output output; + QSignalSpy outputChanged(&output, SIGNAL(changed())); + + output.setup(registry.bindOutput(announced.first().first().value(), announced.first().last().value())); + QVERIFY(outputChanged.wait()); + + QScopedPointer xdgOutputManager(registry.createXdgOutputManager(xdgOutputAnnounced.first().first().value(), xdgOutputAnnounced.first().last().value(), this)); + + QScopedPointer xdgOutput(xdgOutputManager->getXdgOutput(&output, this)); + QSignalSpy xdgOutputChanged(xdgOutput.data(), SIGNAL(changed())); + + //check details are sent on client bind + QVERIFY(xdgOutputChanged.wait()); + xdgOutputChanged.clear(); + QCOMPARE(xdgOutput->logicalPosition(), QPoint(11,12)); + QCOMPARE(xdgOutput->logicalSize(), QSize(1280,720)); + + //dynamic updates + m_serverXdgOutput->setLogicalPosition(QPoint(1000, 2000)); + m_serverXdgOutput->setLogicalSize(QSize(100,200)); + m_serverXdgOutput->done(); + + QVERIFY(xdgOutputChanged.wait()); + QCOMPARE(xdgOutputChanged.count(), 1); + QCOMPARE(xdgOutput->logicalPosition(), QPoint(1000, 2000)); + QCOMPARE(xdgOutput->logicalSize(), QSize(100,200)); +} + +QTEST_GUILESS_MAIN(TestXdgOutput) +#include "test_xdg_output.moc" diff --git a/src/wayland/display.cpp b/src/wayland/display.cpp index 1ba6db92bf..2bb1e60b33 100644 --- a/src/wayland/display.cpp +++ b/src/wayland/display.cpp @@ -50,6 +50,7 @@ License along with this library. If not, see . #include "xdgshell_v6_interface_p.h" #include "appmenu_interface.h" #include "server_decoration_palette_interface.h" +#include "xdgoutput_interface.h" #include #include @@ -457,6 +458,13 @@ ServerSideDecorationPaletteManagerInterface *Display::createServerSideDecoration return b; } +XdgOutputManagerInterface *Display::createXdgOutputManager(QObject *parent) +{ + auto b = new XdgOutputManagerInterface(this, parent); + connect(this, &Display::aboutToTerminate, b, [this, b] { delete b; }); + return b; +} + void Display::createShm() { Q_ASSERT(d->display); diff --git a/src/wayland/display.h b/src/wayland/display.h index c1ff2615fa..6e39e458f6 100644 --- a/src/wayland/display.h +++ b/src/wayland/display.h @@ -86,6 +86,7 @@ class PointerConstraintsInterface; class XdgForeignInterface; class AppMenuManagerInterface; class ServerSideDecorationPaletteManagerInterface; +class XdgOutputManagerInterface; /** * @brief Class holding the Wayland server display loop. @@ -258,6 +259,14 @@ public: **/ ServerSideDecorationPaletteManagerInterface *createServerSideDecorationPaletteManager(QObject *parent = nullptr); + /** + * Creates the XdgOutputManagerInterface + * + * @return the created manager + * @since 5.XDGOUTPUTVERSION + */ + XdgOutputManagerInterface *createXdgOutputManager(QObject *parent = nullptr); + /** * Gets the ClientConnection for the given @p client. diff --git a/src/wayland/server/xdgoutput_interface.cpp b/src/wayland/server/xdgoutput_interface.cpp new file mode 100644 index 0000000000..82c9dfdeb5 --- /dev/null +++ b/src/wayland/server/xdgoutput_interface.cpp @@ -0,0 +1,307 @@ +/**************************************************************************** +Copyright 2018 David Edmundson + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +****************************************************************************/ +#include "xdgoutput_interface.h" +#include "display.h" +#include "global_p.h" +#include "resource_p.h" +#include "output_interface.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class XdgOutputManagerInterface::Private : public Global::Private +{ +public: + Private(XdgOutputManagerInterface *q, Display *d); + QHash outputs; + +private: + void bind(wl_client *client, uint32_t version, uint32_t id) override; + + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + static void destroyCallback(wl_client *client, wl_resource *resource); + static void getXdgOutputCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * output); + + XdgOutputManagerInterface *q; + static const struct zxdg_output_manager_v1_interface s_interface; + static const quint32 s_version; +}; + +const quint32 XdgOutputManagerInterface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_output_manager_v1_interface XdgOutputManagerInterface::Private::s_interface = { + destroyCallback, + getXdgOutputCallback +}; +#endif + +class XdgOutputV1Interface: public Resource +{ +public: + XdgOutputV1Interface(XdgOutputManagerInterface *parent, wl_resource *parentResource); + ~XdgOutputV1Interface(); + void setLogicalSize(const QSize &size); + void setLogicalPosition(const QPoint &pos); + void done(); +private: + class Private; +}; + +class XdgOutputInterface::Private +{ +public: + void resourceConnected(XdgOutputV1Interface *resource); + void resourceDisconnected(XdgOutputV1Interface *resource); + QPoint pos; + QSize size; + bool doneOnce = false; + QList resources; +}; + + +XdgOutputManagerInterface::XdgOutputManagerInterface(Display *display, QObject *parent) + : Global(new XdgOutputManagerInterface::Private(this, display)) +{ +} + +XdgOutputManagerInterface::~XdgOutputManagerInterface() +{} + +XdgOutputInterface* XdgOutputManagerInterface::createXdgOutput(OutputInterface *output, QObject *parent) +{ + Q_D(); + if (!d->outputs.contains(output)) { + auto xdgOutput = new XdgOutputInterface(parent); + d->outputs[output] = xdgOutput; + //as XdgOutput lifespan is managed by user, delete our mapping when either + //it or the relevant Output gets deleted + connect(output, &QObject::destroyed, this, [this, output]() { + Q_D(); + d->outputs.remove(output); + }); + connect(xdgOutput, &QObject::destroyed, this, [this, output]() { + Q_D(); + d->outputs.remove(output); + }); + + } + return d->outputs[output]; +} + +XdgOutputManagerInterface::Private* XdgOutputManagerInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +void XdgOutputManagerInterface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + wl_resource_destroy(resource); +} + +void XdgOutputManagerInterface::Private::getXdgOutputCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * outputResource) +{ + auto d = cast(resource); + auto output = OutputInterface::get(outputResource); + if (!output) { // output client is requesting XdgOutput for an Output that doesn't exist + return; + } + if (!d->outputs.contains(output)) { + return; //server hasn't created an XdgOutput for this output yet, give the client nothing + } + auto iface = new XdgOutputV1Interface(d->q, resource); + iface->create(d->display->getConnection(client), wl_resource_get_version(resource), id); + if (!iface->resource()) { + wl_resource_post_no_memory(resource); + delete iface; + return; + } + + auto xdgOutput = d->outputs[output]; + xdgOutput->d->resourceConnected(iface); + connect(iface, &XdgOutputV1Interface::unbound, xdgOutput, [xdgOutput, iface]() { + xdgOutput->d->resourceDisconnected(iface); + }); +} + +XdgOutputManagerInterface::Private::Private(XdgOutputManagerInterface *q, Display *d) + : Global::Private(d, &zxdg_output_manager_v1_interface, s_version) + , q(q) +{ +} + +void XdgOutputManagerInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + auto c = display->getConnection(client); + wl_resource *resource = c->createResource(&zxdg_output_manager_v1_interface, qMin(version, s_version), id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &s_interface, this, unbind); +} + +void XdgOutputManagerInterface::Private::unbind(wl_resource *resource) +{ + Q_UNUSED(resource) +} + +XdgOutputInterface::XdgOutputInterface(QObject *parent): + QObject(parent), + d(new XdgOutputInterface::Private) +{ +} + +XdgOutputInterface::~XdgOutputInterface() +{} + +void XdgOutputInterface::setLogicalSize(const QSize &size) +{ + if (size == d->size) { + return; + } + d->size = size; + for(auto resource: d->resources) { + resource->setLogicalSize(size); + } +} + +QSize XdgOutputInterface::logicalSize() const +{ + return d->size; +} + +void XdgOutputInterface::setLogicalPosition(const QPoint &pos) +{ + if (pos == d->pos) { + return; + } + d->pos = pos; + for(auto resource: d->resources) { + resource->setLogicalPosition(pos); + } +} + +QPoint XdgOutputInterface::logicalPosition() const +{ + return d->pos; +} + +void XdgOutputInterface::done() +{ + d->doneOnce = true; + for(auto resource: d->resources) { + resource->done(); + } +} + +void XdgOutputInterface::Private::resourceConnected(XdgOutputV1Interface *resource) +{ + resource->setLogicalPosition(pos); + resource->setLogicalSize(size); + if (doneOnce) { + resource->done(); + } + resources << resource; +} + +void XdgOutputInterface::Private::resourceDisconnected(XdgOutputV1Interface *resource) +{ + resources.removeOne(resource); +} + + +class XdgOutputV1Interface::Private : public Resource::Private +{ +public: + Private(XdgOutputV1Interface *q, XdgOutputManagerInterface *c, wl_resource *parentResource); + ~Private(); + +private: + + XdgOutputV1Interface *q_func() { + return reinterpret_cast(q); + } + + static const struct zxdg_output_v1_interface s_interface; +}; + +XdgOutputV1Interface::XdgOutputV1Interface(XdgOutputManagerInterface *parent, wl_resource *parentResource) + :Resource(new XdgOutputV1Interface::Private(this, parent, parentResource)) +{} + +XdgOutputV1Interface::~XdgOutputV1Interface() +{} + +void XdgOutputV1Interface::setLogicalSize(const QSize &size) +{ + if (!d->resource) { + return; + } + zxdg_output_v1_send_logical_size(d->resource, size.width(), size.height()); +} + +void XdgOutputV1Interface::setLogicalPosition(const QPoint &pos) +{ + if (!d->resource) { + return; + } + zxdg_output_v1_send_logical_position(d->resource, pos.x(), pos.y()); +} + +void XdgOutputV1Interface::done() +{ + if (!d->resource) { + return; + } + zxdg_output_v1_send_done(d->resource); +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_output_v1_interface XdgOutputV1Interface::Private::s_interface = { + resourceDestroyedCallback +}; +#endif + +XdgOutputV1Interface::Private::Private(XdgOutputV1Interface *q, XdgOutputManagerInterface *c, wl_resource *parentResource) + : Resource::Private(q, c, parentResource, &zxdg_output_v1_interface, &s_interface) +{ +} + +XdgOutputV1Interface::Private::~Private() +{ + if (resource) { + wl_resource_destroy(resource); + resource = nullptr; + } +} + +} +} + diff --git a/src/wayland/server/xdgoutput_interface.h b/src/wayland/server/xdgoutput_interface.h new file mode 100644 index 0000000000..237bb00fa2 --- /dev/null +++ b/src/wayland/server/xdgoutput_interface.h @@ -0,0 +1,121 @@ +/**************************************************************************** +Copyright 2018 David Edmundson + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_XDGOUTPUT_H +#define KWAYLAND_SERVER_XDGOUTPUT_H + +#include "global.h" +#include "resource.h" + + +#include + + +/* + * In terms of protocol XdgOutputInterface are a resource + * but for the sake of sanity, we should treat XdgOutputs as globals like Output is + * Hence this doesn't match most of kwayland API paradigms. + */ + +namespace KWayland +{ +namespace Server +{ + +class Display; +class OutputInterface; +class XdgOutputInterface; + +/** + * Global manager for XdgOutputs + * @since 5.XDGOUTPUT + */ +class KWAYLANDSERVER_EXPORT XdgOutputManagerInterface : public Global +{ + Q_OBJECT +public: + virtual ~XdgOutputManagerInterface(); + /** + * Creates an XdgOutputInterface object for an existing Output + * which exposes XDG specific properties of outputs + * + * @arg output the wl_output interface this XDG output is for + * @parent the parent of the newly created object + */ + XdgOutputInterface* createXdgOutput(OutputInterface *output, QObject *parent); +private: + explicit XdgOutputManagerInterface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; + Private *d_func() const; +}; + +/** + * Extension to Output + * Users should set all relevant values on creation and on future changes. + * done() should be explicitly called after change batches including initial setting. + * @since 5.XDGOUTPUT + */ +class KWAYLANDSERVER_EXPORT XdgOutputInterface : public QObject +{ + Q_OBJECT +public: + virtual ~XdgOutputInterface(); + + /** + * Sets the size of this output in logical co-ordinates. + * Users should call done() after setting all values + */ + void setLogicalSize(const QSize &size); + + /** + * Returns the last set logical size on this output + */ + QSize logicalSize() const; + + /** + * Sets the topleft position of this output in logical co-ordinates. + * Users should call done() after setting all values + * @see OutputInterface::setPosition + */ + void setLogicalPosition(const QPoint &pos); + + /** + * Returns the last set logical position on this output + */ + QPoint logicalPosition() const; + + /** + * Submit changes to all clients + */ + void done(); + +private: + explicit XdgOutputInterface(QObject *parent); + friend class XdgOutputManagerInterface; + + class Private; + QScopedPointer d; +}; + + +} +} + +#endif diff --git a/src/wayland/tools/mapping.txt b/src/wayland/tools/mapping.txt index 20629a36e5..de0dab45da 100644 --- a/src/wayland/tools/mapping.txt +++ b/src/wayland/tools/mapping.txt @@ -62,3 +62,5 @@ zwp_idle_inhibit_manager_v1;IdleInhibitManager zwp_idle_inhibitor_v1;IdleInhibitor org_kde_kwin_remote_access_manager;RemoteAccessManager org_kde_kwin_remote_buffer;RemoteBuffer +zxdg_output_v1;XdgOutput +zxdg_output_manager_v1;XdgOutputManager