Commit 5f15f3b4 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

wayland: Move output-management-v2 implementation in wayland/

Since both output-management-v2 protocol implementation and the rest of
kwin live in the same place and the fact that kde-output-management-v2
is very plasma specific, we can move Platform::requestOutputsChange() to
the implementation of kde-output-management-v2 protocol, it simplifies
the code a bit and improve code encapsulation.

In order to further simplify kde-output-management-v2 protocol, this
change alters the behavior of the protocol so an output configuration
can be applied only once, which is a very reasonable behavior.
parent a9267bdc
......@@ -25,11 +25,6 @@
#include "qpainterbackend.h"
#include "scene.h"
#include "screenedge.h"
#include "screens.h"
#include "wayland/outputchangeset_v2.h"
#include "wayland/outputconfiguration_v2_interface.h"
#include "wayland_server.h"
#include "workspace.h"
#include <KCoreAddons>
......@@ -124,81 +119,6 @@ void Platform::createPlatformCursor(QObject *parent)
new InputRedirectionCursor(parent);
}
void Platform::requestOutputsChange(KWaylandServer::OutputConfigurationV2Interface *config)
{
if (!m_supportsOutputChanges) {
qCWarning(KWIN_CORE) << "This backend does not support configuration changes.";
config->setFailed();
return;
}
OutputConfiguration cfg;
const auto changes = config->changes();
for (auto it = changes.begin(); it != changes.end(); it++) {
const KWaylandServer::OutputChangeSetV2 *changeset = it.value();
auto output = findOutput(it.key()->uuid());
if (!output) {
qCWarning(KWIN_CORE) << "Could NOT find output matching " << it.key()->uuid();
continue;
}
const auto modes = output->modes();
const auto modeIt = std::find_if(modes.begin(), modes.end(), [&changeset](const auto &mode) {
return mode->size() == changeset->size() && mode->refreshRate() == changeset->refreshRate();
});
if (modeIt == modes.end()) {
qCWarning(KWIN_CORE).nospace() << "Could not find mode " << changeset->size() << "@" << changeset->refreshRate() << " for output " << this;
config->setFailed();
return;
}
auto props = cfg.changeSet(output);
props->enabled = changeset->enabled();
props->pos = changeset->position();
props->scale = changeset->scale();
props->mode = *modeIt;
props->transform = static_cast<Output::Transform>(changeset->transform());
props->overscan = changeset->overscan();
props->rgbRange = static_cast<Output::RgbRange>(changeset->rgbRange());
props->vrrPolicy = static_cast<RenderLoop::VrrPolicy>(changeset->vrrPolicy());
}
const auto allOutputs = outputs();
bool allDisabled = !std::any_of(allOutputs.begin(), allOutputs.end(), [&cfg](const auto &output) {
return cfg.changeSet(output)->enabled;
});
if (allDisabled) {
qCWarning(KWIN_CORE) << "Disabling all outputs through configuration changes is not allowed";
config->setFailed();
return;
}
if (applyOutputChanges(cfg)) {
if (config->primaryChanged() || !primaryOutput()->isEnabled()) {
auto requestedPrimaryOutput = findOutput(config->primary()->uuid());
if (requestedPrimaryOutput && requestedPrimaryOutput->isEnabled()) {
setPrimaryOutput(requestedPrimaryOutput);
} else {
Output *defaultPrimaryOutput = nullptr;
const auto candidates = outputs();
for (Output *output : candidates) {
if (output->isEnabled()) {
defaultPrimaryOutput = output;
break;
}
}
qCWarning(KWIN_CORE) << "Requested invalid primary screen, using" << defaultPrimaryOutput;
setPrimaryOutput(defaultPrimaryOutput);
}
}
Q_EMIT workspace()->screens()->changed();
config->setApplied();
} else {
qCDebug(KWIN_CORE) << "Applying config failed";
config->setFailed();
}
}
bool Platform::applyOutputChanges(const OutputConfiguration &config)
{
const auto availableOutputs = outputs();
......
......@@ -21,11 +21,6 @@
class QAction;
namespace KWaylandServer
{
class OutputConfigurationV2Interface;
}
namespace KWin
{
......@@ -105,15 +100,6 @@ public:
*/
void setSceneEglGlobalShareContext(EGLContext context);
/**
* Implement this method to receive configuration change requests through KWayland's
* OutputManagement interface.
*
* Base implementation warns that the current backend does not implement this
* functionality.
*/
void requestOutputsChange(KWaylandServer::OutputConfigurationV2Interface *config);
/**
* Whether the Platform requires compositing for rendering.
* Default implementation returns @c true. If the implementing Platform allows to be used
......
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2015 Sebastian Kügler <sebas@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
// Qt
#include <QtTest>
// KWin
#include "wayland/compositor_interface.h"
#include "wayland/display.h"
#include "wayland/outputconfiguration_interface.h"
#include "wayland/outputdevice_interface.h"
#include "wayland/outputmanagement_interface.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/output.h"
#include "KWayland/Client/outputconfiguration.h"
#include "KWayland/Client/outputdevice.h"
#include "KWayland/Client/outputmanagement.h"
#include "KWayland/Client/registry.h"
// Wayland
#include <wayland-client-protocol.h>
using namespace KWayland::Client;
using namespace KWaylandServer;
class TestWaylandOutputManagement : public QObject
{
Q_OBJECT
public:
explicit TestWaylandOutputManagement(QObject *parent = nullptr);
private Q_SLOTS:
void init();
void cleanup();
void createConfig();
void testBasicMemoryManagement();
void testMultipleSettings();
void testConfigFailed();
void testApplied();
void testFailed();
void testExampleConfig();
void testScale();
void testRemoval();
private:
void createOutputDevices();
void testEnable();
void applyPendingChanges(KWaylandServer::OutputConfigurationInterface *configurationInterface);
KWaylandServer::Display *m_display;
KWaylandServer::OutputManagementInterface *m_outputManagementInterface;
QList<KWaylandServer::OutputDeviceInterface *> m_serverOutputs;
KWayland::Client::Registry *m_registry = nullptr;
KWayland::Client::OutputDevice *m_outputDevice = nullptr;
KWayland::Client::OutputManagement *m_outputManagement = nullptr;
KWayland::Client::OutputConfiguration *m_outputConfiguration = nullptr;
QList<KWayland::Client::OutputDevice *> m_clientOutputs;
QList<KWaylandServer::OutputDeviceInterface::Mode> m_modes;
KWayland::Client::ConnectionThread *m_connection = nullptr;
KWayland::Client::EventQueue *m_queue = nullptr;
QThread *m_thread;
QSignalSpy *m_announcedSpy;
QSignalSpy *m_omSpy;
};
static const QString s_socketName = QStringLiteral("kwin-test-wayland-output-0");
TestWaylandOutputManagement::TestWaylandOutputManagement(QObject *parent)
: QObject(parent)
, m_display(nullptr)
, m_outputManagementInterface(nullptr)
, m_connection(nullptr)
, m_queue(nullptr)
, m_thread(nullptr)
, m_announcedSpy(nullptr)
{
qRegisterMetaType<KWaylandServer::OutputConfigurationInterface *>();
}
void TestWaylandOutputManagement::init()
{
using namespace KWaylandServer;
delete m_display;
m_display = new KWaylandServer::Display(this);
m_display->addSocketName(s_socketName);
m_display->start();
QVERIFY(m_display->isRunning());
new CompositorInterface(m_display, this);
auto outputDeviceInterface = new OutputDeviceInterface(m_display, this);
OutputDeviceInterface::Mode m0;
m0.id = 0;
m0.size = QSize(800, 600);
m0.flags = OutputDeviceInterface::ModeFlags(OutputDeviceInterface::ModeFlag::Preferred);
outputDeviceInterface->addMode(m0);
OutputDeviceInterface::Mode m1;
m1.id = 1;
m1.size = QSize(1024, 768);
outputDeviceInterface->addMode(m1);
OutputDeviceInterface::Mode m2;
m2.id = 2;
m2.size = QSize(1280, 1024);
m2.refreshRate = 90000;
outputDeviceInterface->addMode(m2);
OutputDeviceInterface::Mode m3;
m3.id = 3;
m3.size = QSize(1920, 1080);
m3.flags = OutputDeviceInterface::ModeFlags();
m3.refreshRate = 100000;
outputDeviceInterface->addMode(m3);
m_modes << m0 << m1 << m2 << m3;
outputDeviceInterface->setCurrentMode(1);
outputDeviceInterface->setGlobalPosition(QPoint(0, 1920));
m_serverOutputs << outputDeviceInterface;
m_outputManagementInterface = new OutputManagementInterface(m_display, this);
// setup connection
m_connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::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());
m_registry = new Registry();
m_announcedSpy = new QSignalSpy(m_registry, &KWayland::Client::Registry::outputManagementAnnounced);
m_omSpy = new QSignalSpy(m_registry, &KWayland::Client::Registry::outputDeviceAnnounced);
QVERIFY(m_announcedSpy->isValid());
QVERIFY(m_omSpy->isValid());
m_registry->create(m_connection->display());
QVERIFY(m_registry->isValid());
m_registry->setEventQueue(m_queue);
m_registry->setup();
wl_display_flush(m_connection->display());
QVERIFY(m_announcedSpy->wait());
QCOMPARE(m_announcedSpy->count(), 1);
m_outputManagement = m_registry->createOutputManagement(m_announcedSpy->first().first().value<quint32>(), m_announcedSpy->first().last().value<quint32>());
createOutputDevices();
}
void TestWaylandOutputManagement::cleanup()
{
if (m_outputConfiguration) {
delete m_outputConfiguration;
m_outputConfiguration = nullptr;
}
if (m_outputManagement) {
delete m_outputManagement;
m_outputManagement = nullptr;
}
if (m_registry) {
delete m_registry;
m_registry = nullptr;
}
if (m_queue) {
delete m_queue;
m_queue = nullptr;
}
if (m_connection) {
m_connection->deleteLater();
m_connection = nullptr;
}
if (m_thread) {
m_thread->quit();
m_thread->wait();
delete m_thread;
m_thread = nullptr;
}
delete m_display;
m_display = nullptr;
m_serverOutputs.clear();
m_clientOutputs.clear();
// these are the children of the display
m_outputManagementInterface = nullptr;
}
void TestWaylandOutputManagement::applyPendingChanges(KWaylandServer::OutputConfigurationInterface *configurationInterface)
{
auto changes = configurationInterface->changes();
for (auto outputdevice : changes.keys()) {
auto c = changes[outputdevice];
if (c->enabledChanged()) {
outputdevice->setEnabled(c->enabled());
}
if (c->modeChanged()) {
outputdevice->setCurrentMode(c->mode());
}
if (c->transformChanged()) {
outputdevice->setTransform(c->transform());
}
if (c->positionChanged()) {
outputdevice->setGlobalPosition(c->position());
}
if (c->scaleChanged()) {
outputdevice->setScaleF(c->scaleF());
}
if (c->colorCurvesChanged()) {
outputdevice->setColorCurves(c->colorCurves());
}
}
}
void TestWaylandOutputManagement::createOutputDevices()
{
QCOMPARE(m_omSpy->count(), 1);
QCOMPARE(m_registry->interfaces(KWayland::Client::Registry::Interface::OutputDevice).count(), m_serverOutputs.count());
auto output = new KWayland::Client::OutputDevice();
QVERIFY(!output->isValid());
QCOMPARE(output->geometry(), QRect());
QCOMPARE(output->globalPosition(), QPoint());
QCOMPARE(output->manufacturer(), QString());
QCOMPARE(output->model(), QString());
QCOMPARE(output->physicalSize(), QSize());
QCOMPARE(output->pixelSize(), QSize());
QCOMPARE(output->refreshRate(), 0);
QCOMPARE(output->scaleF(), 1.0);
QCOMPARE(output->colorCurves().red, QVector<quint16>());
QCOMPARE(output->colorCurves().green, QVector<quint16>());
QCOMPARE(output->colorCurves().blue, QVector<quint16>());
QCOMPARE(output->subPixel(), KWayland::Client::OutputDevice::SubPixel::Unknown);
QCOMPARE(output->transform(), KWayland::Client::OutputDevice::Transform::Normal);
QCOMPARE(output->enabled(), OutputDevice::Enablement::Enabled);
QCOMPARE(output->edid(), QByteArray());
QCOMPARE(output->uuid(), QString());
QSignalSpy outputChanged(output, &KWayland::Client::OutputDevice::changed);
QVERIFY(outputChanged.isValid());
output->setup(m_registry->bindOutputDevice(m_omSpy->first().first().value<quint32>(), m_omSpy->first().last().value<quint32>()));
wl_display_flush(m_connection->display());
QVERIFY(outputChanged.wait());
QCOMPARE(output->globalPosition(), QPoint(0, 1920));
QCOMPARE(output->enabled(), OutputDevice::Enablement::Enabled);
m_clientOutputs << output;
m_outputDevice = output;
QVERIFY(m_outputManagement->isValid());
}
void TestWaylandOutputManagement::testBasicMemoryManagement()
{
createConfig();
QSignalSpy serverApplySpy(m_outputManagementInterface, &OutputManagementInterface::configurationChangeRequested);
KWaylandServer::OutputConfigurationInterface *configurationInterface = nullptr;
connect(m_outputManagementInterface,
&OutputManagementInterface::configurationChangeRequested,
[=, &configurationInterface](KWaylandServer::OutputConfigurationInterface *c) {
configurationInterface = c;
});
m_outputConfiguration->apply();
QVERIFY(serverApplySpy.wait());
QVERIFY(configurationInterface);
QSignalSpy interfaceDeletedSpy(configurationInterface, &QObject::destroyed);
delete m_outputConfiguration;
m_outputConfiguration = nullptr;
QVERIFY(interfaceDeletedSpy.wait());
}
void TestWaylandOutputManagement::testRemoval()
{
QSignalSpy outputManagementRemovedSpy(m_registry, &KWayland::Client::Registry::outputManagementRemoved);
QVERIFY(outputManagementRemovedSpy.isValid());
delete m_outputManagementInterface;
m_outputManagementInterface = nullptr;
QVERIFY(outputManagementRemovedSpy.wait(200));
QCOMPARE(outputManagementRemovedSpy.first().first(), m_announcedSpy->first().first());
QVERIFY(!m_registry->hasInterface(KWayland::Client::Registry::Interface::OutputManagement));
QVERIFY(m_registry->interfaces(KWayland::Client::Registry::Interface::OutputManagement).isEmpty());
}
void TestWaylandOutputManagement::createConfig()
{
m_outputConfiguration = m_outputManagement->createConfiguration();
}
void TestWaylandOutputManagement::testApplied()
{
createConfig();
QVERIFY(m_outputConfiguration->isValid());
QSignalSpy appliedSpy(m_outputConfiguration, &KWayland::Client::OutputConfiguration::applied);
connect(m_outputManagementInterface,
&OutputManagementInterface::configurationChangeRequested,
[=](KWaylandServer::OutputConfigurationInterface *configurationInterface) {
configurationInterface->setApplied();
});
m_outputConfiguration->apply();
QVERIFY(appliedSpy.wait(200));
QCOMPARE(appliedSpy.count(), 1);
}
void TestWaylandOutputManagement::testFailed()
{
createConfig();
QVERIFY(m_outputConfiguration->isValid());
QSignalSpy failedSpy(m_outputConfiguration, &KWayland::Client::OutputConfiguration::failed);
connect(m_outputManagementInterface,
&OutputManagementInterface::configurationChangeRequested,
[=](KWaylandServer::OutputConfigurationInterface *configurationInterface) {
configurationInterface->setFailed();
});
m_outputConfiguration->apply();
QVERIFY(failedSpy.wait(200));
QCOMPARE(failedSpy.count(), 1);
}
void TestWaylandOutputManagement::testEnable()
{
createConfig();
auto config = m_outputConfiguration;
QVERIFY(config->isValid());
KWayland::Client::OutputDevice *output = m_clientOutputs.first();
QCOMPARE(output->enabled(), OutputDevice::Enablement::Enabled);
QSignalSpy enabledChanged(output, &KWayland::Client::OutputDevice::enabledChanged);
QVERIFY(enabledChanged.isValid());
config->setEnabled(output, OutputDevice::Enablement::Disabled);
QVERIFY(!enabledChanged.wait(200));
QCOMPARE(enabledChanged.count(), 0);
// Reset
config->setEnabled(output, OutputDevice::Enablement::Disabled);
config->apply();
}
void TestWaylandOutputManagement::testMultipleSettings()
{
createConfig();
auto config = m_outputConfiguration;
QVERIFY(config->isValid());
KWayland::Client::OutputDevice *output = m_clientOutputs.first();
QSignalSpy outputChangedSpy(output, &KWayland::Client::OutputDevice::changed);
KWaylandServer::OutputConfigurationInterface *configurationInterface;
connect(m_outputManagementInterface,
&OutputManagementInterface::configurationChangeRequested,
[=, &configurationInterface](KWaylandServer::OutputConfigurationInterface *c) {
applyPendingChanges(c);
configurationInterface = c;
});
QSignalSpy serverApplySpy(m_outputManagementInterface, &OutputManagementInterface::configurationChangeRequested);
QVERIFY(serverApplySpy.isValid());
config->setMode(output, m_modes.first().id);
config->setTransform(output, OutputDevice::Transform::Rotated90);
config->setPosition(output, QPoint(13, 37));
config->setScaleF(output, 2.0);
const auto zeroVector = QVector<quint16>(256, 0);
config->setColorCurves(output, zeroVector, zeroVector, zeroVector);
config->setEnabled(output, OutputDevice::Enablement::Disabled);
config->apply();
QVERIFY(serverApplySpy.wait(200));
QCOMPARE(serverApplySpy.count(), 1);
configurationInterface->setApplied();
QSignalSpy configAppliedSpy(config, &OutputConfiguration::applied);
QVERIFY(configAppliedSpy.isValid());
QVERIFY(configAppliedSpy.wait(200));
QCOMPARE(configAppliedSpy.count(), 1);
QCOMPARE(outputChangedSpy.count(), 5);
config->setMode(output, m_modes.at(1).id);
config->setTransform(output, OutputDevice::Transform::Normal);
config->setPosition(output, QPoint(0, 1920));
config->setScaleF(output, 1.0);
const auto oneVector = QVector<quint16>(256, 1);
config->setColorCurves(output, oneVector, oneVector, oneVector);
config->setEnabled(output, OutputDevice::Enablement::Enabled);
config->apply();
QVERIFY(serverApplySpy.wait(200));
QCOMPARE(serverApplySpy.count(), 2);
configurationInterface->setApplied();
QVERIFY(configAppliedSpy.wait(200));
QCOMPARE(configAppliedSpy.count(), 2);
QCOMPARE(outputChangedSpy.count(), 10);
}
void TestWaylandOutputManagement::testConfigFailed()
{
createConfig();
auto config = m_outputConfiguration;
KWayland::Client::OutputDevice *output = m_clientOutputs.first();
QVERIFY(config->isValid());
QVERIFY(output->isValid());
QSignalSpy serverApplySpy(m_outputManagementInterface, &OutputManagementInterface::configurationChangeRequested);
QVERIFY(serverApplySpy.isValid());
QSignalSpy outputChangedSpy(output, &KWayland::Client::OutputDevice::changed);
QVERIFY(outputChangedSpy.isValid());
QSignalSpy configAppliedSpy(config, &OutputConfiguration::applied);
QVERIFY(configAppliedSpy.isValid());
QSignalSpy configFailedSpy(config, &KWayland::Client::OutputConfiguration::failed);
QVERIFY(configFailedSpy.isValid());
config->setMode(output, m_modes.last().id);
config->setTransform(output, OutputDevice::Transform::Normal);
config->setPosition(output, QPoint(-1, -1));
config->apply();
connect(m_outputManagementInterface, &OutputManagementInterface::configurationChangeRequested, [=](KWaylandServer::OutputConfigurationInterface *c) {
c->setFailed();
});
QVERIFY(serverApplySpy.wait(200));
// Artificially make the server fail to apply the settings
// Make sure the applied signal never comes, and that failed has been received
QVERIFY(!configAppliedSpy.wait(200));
QCOMPARE(configFailedSpy.count(), 1);
QCOMPARE(configAppliedSpy.count(), 0);
}