Add XDG Output Protocol

Summary:
Done primarily for XWayland which for legacy reasons doesn't assume the
logical size of a display is pixelSize / outputScale. Meaning xwayland
windows that position themselves are wrong in a scaled environment.

It also allows the possibility for us to support fractional scaling
whilst keeping wl_output::scale as an integer.

The protocol is a bit odd as it operates via the FooManager + Foo
pattern rather than using globals like Output so I've wrapped it so it
behaves more like globals.

Test Plan: #plasma

Reviewers: romangg

Subscribers: #frameworks

Tags: #frameworks

Maniphest Tasks: T8501

Differential Revision: https://phabricator.kde.org/D12235
This commit is contained in:
David Edmundson 2018-05-15 10:45:17 +01:00
parent 8da9a184ed
commit b2f7c6ea02
8 changed files with 641 additions and 0 deletions

View file

@ -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

View file

@ -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)

View file

@ -0,0 +1,174 @@
/********************************************************************
Copyright 2018 David Edmundson <davidedmundson@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
// Qt
#include <QtTest/QtTest>
// 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(&registry, SIGNAL(outputAnnounced(quint32,quint32)));
QSignalSpy xdgOutputAnnounced(&registry, 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<quint32>(), announced.first().last().value<quint32>()));
QVERIFY(outputChanged.wait());
QScopedPointer<KWayland::Client::XdgOutputManager> xdgOutputManager(registry.createXdgOutputManager(xdgOutputAnnounced.first().first().value<quint32>(), xdgOutputAnnounced.first().last().value<quint32>(), this));
QScopedPointer<KWayland::Client::XdgOutput> 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"

View file

@ -50,6 +50,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "xdgshell_v6_interface_p.h"
#include "appmenu_interface.h"
#include "server_decoration_palette_interface.h"
#include "xdgoutput_interface.h"
#include <QCoreApplication>
#include <QDebug>
@ -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);

View file

@ -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.

View file

@ -0,0 +1,307 @@
/****************************************************************************
Copyright 2018 David Edmundson <kde@davidedmundson.co.uk>
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 <http://www.gnu.org/licenses/>.
****************************************************************************/
#include "xdgoutput_interface.h"
#include "display.h"
#include "global_p.h"
#include "resource_p.h"
#include "output_interface.h"
#include <wayland-xdg-output-server-protocol.h>
namespace KWayland
{
namespace Server
{
class XdgOutputManagerInterface::Private : public Global::Private
{
public:
Private(XdgOutputManagerInterface *q, Display *d);
QHash<OutputInterface*, XdgOutputInterface*> 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<Private*>(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<XdgOutputV1Interface*> 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<Private*>(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<XdgOutputV1Interface *>(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;
}
}
}
}

View file

@ -0,0 +1,121 @@
/****************************************************************************
Copyright 2018 David Edmundson <kde@davidedmundson.co.uk>
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 <http://www.gnu.org/licenses/>.
****************************************************************************/
#ifndef KWAYLAND_SERVER_XDGOUTPUT_H
#define KWAYLAND_SERVER_XDGOUTPUT_H
#include "global.h"
#include "resource.h"
#include <KWayland/Server/kwaylandserver_export.h>
/*
* 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<Private> d;
};
}
}
#endif

View file

@ -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