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.
This commit is contained in:
Vlad Zahorodnii 2022-08-12 10:09:59 +03:00
parent a9267bdcdc
commit 5f15f3b47c
10 changed files with 127 additions and 829 deletions

View file

@ -25,11 +25,6 @@
#include "qpainterbackend.h" #include "qpainterbackend.h"
#include "scene.h" #include "scene.h"
#include "screenedge.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> #include <KCoreAddons>
@ -124,81 +119,6 @@ void Platform::createPlatformCursor(QObject *parent)
new InputRedirectionCursor(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) bool Platform::applyOutputChanges(const OutputConfiguration &config)
{ {
const auto availableOutputs = outputs(); const auto availableOutputs = outputs();

View file

@ -21,11 +21,6 @@
class QAction; class QAction;
namespace KWaylandServer
{
class OutputConfigurationV2Interface;
}
namespace KWin namespace KWin
{ {
@ -105,15 +100,6 @@ public:
*/ */
void setSceneEglGlobalShareContext(EGLContext context); 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. * Whether the Platform requires compositing for rendering.
* Default implementation returns @c true. If the implementing Platform allows to be used * Default implementation returns @c true. If the implementing Platform allows to be used

View file

@ -1,507 +0,0 @@
/*
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);
}
void TestWaylandOutputManagement::testExampleConfig()
{
createConfig();
auto config = m_outputConfiguration;
KWayland::Client::OutputDevice *output = m_clientOutputs.first();
config->setMode(output, m_clientOutputs.first()->modes().last().id);
config->setTransform(output, OutputDevice::Transform::Normal);
config->setPosition(output, QPoint(-1, -1));
QSignalSpy configAppliedSpy(config, &OutputConfiguration::applied);
connect(m_outputManagementInterface, &OutputManagementInterface::configurationChangeRequested, [=](KWaylandServer::OutputConfigurationInterface *c) {
c->setApplied();
});
config->apply();
QVERIFY(configAppliedSpy.isValid());
QVERIFY(configAppliedSpy.wait(200));
}
void TestWaylandOutputManagement::testScale()
{
createConfig();
auto config = m_outputConfiguration;
KWayland::Client::OutputDevice *output = m_clientOutputs.first();
config->setScaleF(output, 2.3);
config->apply();
QSignalSpy configAppliedSpy(config, &OutputConfiguration::applied);
connect(m_outputManagementInterface, &OutputManagementInterface::configurationChangeRequested, [=](KWaylandServer::OutputConfigurationInterface *c) {
applyPendingChanges(c);
c->setApplied();
});
QVERIFY(configAppliedSpy.isValid());
QVERIFY(configAppliedSpy.wait(200));
QCOMPARE(wl_fixed_from_double(output->scaleF()), wl_fixed_from_double(2.3));
}
QTEST_GUILESS_MAIN(TestWaylandOutputManagement)
#include "test_wayland_outputmanagement.moc"

View file

@ -9,7 +9,6 @@
#include "wayland/clientconnection.h" #include "wayland/clientconnection.h"
#include "wayland/display.h" #include "wayland/display.h"
#include "wayland/output_interface.h" #include "wayland/output_interface.h"
#include "wayland/outputmanagement_v2_interface.h"
// Wayland // Wayland
#include <wayland-server.h> #include <wayland-server.h>
// system // system
@ -28,7 +27,6 @@ private Q_SLOTS:
void testAddRemoveOutput(); void testAddRemoveOutput();
void testClientConnection(); void testClientConnection();
void testConnectNoSocket(); void testConnectNoSocket();
void testOutputManagement();
void testAutoSocketName(); void testAutoSocketName();
}; };
@ -178,14 +176,6 @@ void TestWaylandServerDisplay::testConnectNoSocket()
close(sv[1]); close(sv[1]);
} }
void TestWaylandServerDisplay::testOutputManagement()
{
KWaylandServer::Display display;
display.addSocketName("kwayland-test-0");
display.start();
new OutputManagementV2Interface(&display, this);
}
void TestWaylandServerDisplay::testAutoSocketName() void TestWaylandServerDisplay::testAutoSocketName()
{ {
QTemporaryDir runtimeDir; QTemporaryDir runtimeDir;

View file

@ -106,7 +106,7 @@ public:
OutputDeviceV2Interface::RgbRange rgbRange() const; OutputDeviceV2Interface::RgbRange rgbRange() const;
private: private:
friend class OutputConfigurationV2InterfacePrivate; friend class OutputConfigurationV2Interface;
explicit OutputChangeSetV2(OutputDeviceV2Interface *outputdevice, QObject *parent = nullptr); explicit OutputChangeSetV2(OutputDeviceV2Interface *outputdevice, QObject *parent = nullptr);
std::unique_ptr<OutputChangeSetV2Private> d; std::unique_ptr<OutputChangeSetV2Private> d;

View file

@ -11,49 +11,30 @@
#include "outputdevice_v2_interface.h" #include "outputdevice_v2_interface.h"
#include "utils/common.h" #include "utils/common.h"
#include "main.h"
#include "outputconfiguration.h"
#include "platform.h"
#include "screens.h"
#include "workspace.h"
#include "qwayland-server-kde-output-device-v2.h" #include "qwayland-server-kde-output-device-v2.h"
#include "qwayland-server-kde-output-management-v2.h"
#include <wayland-client-protocol.h> using namespace KWin;
#include <optional>
namespace KWaylandServer namespace KWaylandServer
{ {
class OutputConfigurationV2InterfacePrivate : public QtWaylandServer::kde_output_configuration_v2
OutputConfigurationV2Interface::OutputConfigurationV2Interface(wl_resource *resource)
: QtWaylandServer::kde_output_configuration_v2(resource)
{ {
public: }
OutputConfigurationV2InterfacePrivate(OutputConfigurationV2Interface *q, OutputManagementV2Interface *outputManagement, wl_resource *resource);
void sendApplied(); OutputConfigurationV2Interface::~OutputConfigurationV2Interface()
void sendFailed(); {
void emitConfigurationChangeRequested() const; qDeleteAll(changes.begin(), changes.end());
void clearPendingChanges(); }
bool hasPendingChanges(OutputDeviceV2Interface *outputdevice) const; void OutputConfigurationV2Interface::kde_output_configuration_v2_enable(Resource *resource, wl_resource *outputdevice, int32_t enable)
OutputChangeSetV2 *pendingChanges(OutputDeviceV2Interface *outputdevice);
OutputManagementV2Interface *outputManagement;
QHash<OutputDeviceV2Interface *, OutputChangeSetV2 *> changes;
std::optional<OutputDeviceV2Interface *> primaryOutput;
OutputConfigurationV2Interface *q;
protected:
void kde_output_configuration_v2_enable(Resource *resource, wl_resource *outputdevice, int32_t enable) override;
void kde_output_configuration_v2_mode(Resource *resource, struct ::wl_resource *outputdevice, struct ::wl_resource *mode) override;
void kde_output_configuration_v2_transform(Resource *resource, wl_resource *outputdevice, int32_t transform) override;
void kde_output_configuration_v2_position(Resource *resource, wl_resource *outputdevice, int32_t x, int32_t y) override;
void kde_output_configuration_v2_scale(Resource *resource, wl_resource *outputdevice, wl_fixed_t scale) override;
void kde_output_configuration_v2_apply(Resource *resource) override;
void kde_output_configuration_v2_destroy(Resource *resource) override;
void kde_output_configuration_v2_destroy_resource(Resource *resource) override;
void kde_output_configuration_v2_overscan(Resource *resource, wl_resource *outputdevice, uint32_t overscan) override;
void kde_output_configuration_v2_set_vrr_policy(Resource *resource, struct ::wl_resource *outputdevice, uint32_t policy) override;
void kde_output_configuration_v2_set_rgb_range(Resource *resource, wl_resource *outputdevice, uint32_t rgbRange) override;
void kde_output_configuration_v2_set_primary_output(Resource *resource, struct ::wl_resource *output) override;
};
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_enable(Resource *resource, wl_resource *outputdevice, int32_t enable)
{ {
Q_UNUSED(resource) Q_UNUSED(resource)
@ -61,7 +42,7 @@ void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_enable(R
pendingChanges(output)->d->enabled = enable == 1; pendingChanges(output)->d->enabled = enable == 1;
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_mode(Resource *resource, wl_resource *outputdevice, wl_resource *modeResource) void OutputConfigurationV2Interface::kde_output_configuration_v2_mode(Resource *resource, wl_resource *outputdevice, wl_resource *modeResource)
{ {
Q_UNUSED(resource) Q_UNUSED(resource)
OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice); OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice);
@ -74,7 +55,7 @@ void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_mode(Res
pendingChanges(output)->d->refreshRate = mode->refreshRate(); pendingChanges(output)->d->refreshRate = mode->refreshRate();
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_transform(Resource *resource, wl_resource *outputdevice, int32_t transform) void OutputConfigurationV2Interface::kde_output_configuration_v2_transform(Resource *resource, wl_resource *outputdevice, int32_t transform)
{ {
Q_UNUSED(resource) Q_UNUSED(resource)
auto toTransform = [transform]() { auto toTransform = [transform]() {
@ -103,7 +84,7 @@ void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_transfor
pendingChanges(output)->d->transform = _transform; pendingChanges(output)->d->transform = _transform;
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_position(Resource *resource, wl_resource *outputdevice, int32_t x, int32_t y) void OutputConfigurationV2Interface::kde_output_configuration_v2_position(Resource *resource, wl_resource *outputdevice, int32_t x, int32_t y)
{ {
Q_UNUSED(resource) Q_UNUSED(resource)
auto _pos = QPoint(x, y); auto _pos = QPoint(x, y);
@ -111,7 +92,7 @@ void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_position
pendingChanges(output)->d->position = _pos; pendingChanges(output)->d->position = _pos;
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_scale(Resource *resource, wl_resource *outputdevice, wl_fixed_t scale) void OutputConfigurationV2Interface::kde_output_configuration_v2_scale(Resource *resource, wl_resource *outputdevice, wl_fixed_t scale)
{ {
Q_UNUSED(resource) Q_UNUSED(resource)
const qreal doubleScale = wl_fixed_to_double(scale); const qreal doubleScale = wl_fixed_to_double(scale);
@ -125,13 +106,82 @@ void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_scale(Re
pendingChanges(output)->d->scale = doubleScale; pendingChanges(output)->d->scale = doubleScale;
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_apply(Resource *resource) void OutputConfigurationV2Interface::kde_output_configuration_v2_apply(Resource *resource)
{ {
Q_UNUSED(resource) if (applied) {
emitConfigurationChangeRequested(); wl_resource_post_error(resource->handle, 0, "an output configuration can be applied only once");
return;
}
applied = true;
OutputConfiguration cfg;
for (auto it = changes.constBegin(); it != changes.constEnd(); ++it) {
const KWaylandServer::OutputChangeSetV2 *changeset = it.value();
auto output = kwinApp()->platform()->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;
send_failed();
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 = kwinApp()->platform()->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";
send_failed();
return;
}
if (kwinApp()->platform()->applyOutputChanges(cfg)) {
if (primaryOutput.has_value() || !kwinApp()->platform()->primaryOutput()->isEnabled()) {
auto requestedPrimaryOutput = kwinApp()->platform()->findOutput((*primaryOutput)->uuid());
if (requestedPrimaryOutput && requestedPrimaryOutput->isEnabled()) {
kwinApp()->platform()->setPrimaryOutput(requestedPrimaryOutput);
} else {
Output *defaultPrimaryOutput = nullptr;
const auto candidates = kwinApp()->platform()->outputs();
for (Output *output : candidates) {
if (output->isEnabled()) {
defaultPrimaryOutput = output;
break;
}
}
qCWarning(KWIN_CORE) << "Requested invalid primary screen, using" << defaultPrimaryOutput;
kwinApp()->platform()->setPrimaryOutput(defaultPrimaryOutput);
}
}
Q_EMIT workspace()->screens()->changed();
send_applied();
} else {
qCDebug(KWIN_CORE) << "Applying config failed";
send_failed();
}
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_overscan(Resource *resource, wl_resource *outputdevice, uint32_t overscan) void OutputConfigurationV2Interface::kde_output_configuration_v2_overscan(Resource *resource, wl_resource *outputdevice, uint32_t overscan)
{ {
Q_UNUSED(resource) Q_UNUSED(resource)
if (overscan > 100) { if (overscan > 100) {
@ -142,7 +192,7 @@ void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_overscan
pendingChanges(output)->d->overscan = overscan; pendingChanges(output)->d->overscan = overscan;
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_set_vrr_policy(Resource *resource, wl_resource *outputdevice, uint32_t policy) void OutputConfigurationV2Interface::kde_output_configuration_v2_set_vrr_policy(Resource *resource, wl_resource *outputdevice, uint32_t policy)
{ {
Q_UNUSED(resource) Q_UNUSED(resource)
if (policy > static_cast<uint32_t>(OutputDeviceV2Interface::VrrPolicy::Automatic)) { if (policy > static_cast<uint32_t>(OutputDeviceV2Interface::VrrPolicy::Automatic)) {
@ -153,7 +203,7 @@ void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_set_vrr_
pendingChanges(output)->d->vrrPolicy = static_cast<OutputDeviceV2Interface::VrrPolicy>(policy); pendingChanges(output)->d->vrrPolicy = static_cast<OutputDeviceV2Interface::VrrPolicy>(policy);
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_set_rgb_range(Resource *resource, wl_resource *outputdevice, uint32_t rgbRange) void OutputConfigurationV2Interface::kde_output_configuration_v2_set_rgb_range(Resource *resource, wl_resource *outputdevice, uint32_t rgbRange)
{ {
Q_UNUSED(resource) Q_UNUSED(resource)
if (rgbRange > static_cast<uint32_t>(OutputDeviceV2Interface::RgbRange::Limited)) { if (rgbRange > static_cast<uint32_t>(OutputDeviceV2Interface::RgbRange::Limited)) {
@ -164,107 +214,30 @@ void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_set_rgb_
pendingChanges(output)->d->rgbRange = static_cast<OutputDeviceV2Interface::RgbRange>(rgbRange); pendingChanges(output)->d->rgbRange = static_cast<OutputDeviceV2Interface::RgbRange>(rgbRange);
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_set_primary_output(Resource *resource, struct ::wl_resource *output) void OutputConfigurationV2Interface::kde_output_configuration_v2_set_primary_output(Resource *resource, struct ::wl_resource *output)
{ {
Q_UNUSED(resource); Q_UNUSED(resource);
primaryOutput = OutputDeviceV2Interface::get(output); primaryOutput = OutputDeviceV2Interface::get(output);
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_destroy(Resource *resource) void OutputConfigurationV2Interface::kde_output_configuration_v2_destroy(Resource *resource)
{ {
wl_resource_destroy(resource->handle); wl_resource_destroy(resource->handle);
} }
void OutputConfigurationV2InterfacePrivate::kde_output_configuration_v2_destroy_resource(Resource *resource) void OutputConfigurationV2Interface::kde_output_configuration_v2_destroy_resource(Resource *resource)
{ {
Q_UNUSED(resource) Q_UNUSED(resource)
delete q; delete this;
} }
void OutputConfigurationV2InterfacePrivate::emitConfigurationChangeRequested() const OutputChangeSetV2 *OutputConfigurationV2Interface::pendingChanges(OutputDeviceV2Interface *outputdevice)
{
Q_EMIT outputManagement->configurationChangeRequested(q);
}
OutputConfigurationV2InterfacePrivate::OutputConfigurationV2InterfacePrivate(OutputConfigurationV2Interface *q, OutputManagementV2Interface *outputManagement, wl_resource *resource)
: QtWaylandServer::kde_output_configuration_v2(resource)
, outputManagement(outputManagement)
, q(q)
{
}
QHash<OutputDeviceV2Interface *, OutputChangeSetV2 *> OutputConfigurationV2Interface::changes() const
{
return d->changes;
}
bool OutputConfigurationV2Interface::primaryChanged() const
{
return d->primaryOutput.has_value();
}
OutputDeviceV2Interface *OutputConfigurationV2Interface::primary() const
{
Q_ASSERT(d->primaryOutput.has_value());
return *d->primaryOutput;
}
void OutputConfigurationV2Interface::setApplied()
{
d->clearPendingChanges();
d->sendApplied();
}
void OutputConfigurationV2InterfacePrivate::sendApplied()
{
send_applied();
}
void OutputConfigurationV2Interface::setFailed()
{
d->clearPendingChanges();
d->sendFailed();
}
void OutputConfigurationV2InterfacePrivate::sendFailed()
{
send_failed();
}
OutputChangeSetV2 *OutputConfigurationV2InterfacePrivate::pendingChanges(OutputDeviceV2Interface *outputdevice)
{ {
auto &change = changes[outputdevice]; auto &change = changes[outputdevice];
if (!change) { if (!change) {
change = new OutputChangeSetV2(outputdevice, q); change = new OutputChangeSetV2(outputdevice);
} }
return change; return change;
} }
bool OutputConfigurationV2InterfacePrivate::hasPendingChanges(OutputDeviceV2Interface *outputdevice) const
{
auto it = changes.constFind(outputdevice);
if (it == changes.constEnd()) {
return false;
}
auto c = *it;
return c->enabledChanged() || c->sizeChanged() || c->refreshRateChanged() || c->transformChanged() || c->positionChanged() || c->scaleChanged();
}
void OutputConfigurationV2InterfacePrivate::clearPendingChanges()
{
qDeleteAll(changes.begin(), changes.end());
changes.clear();
}
OutputConfigurationV2Interface::OutputConfigurationV2Interface(OutputManagementV2Interface *parent, wl_resource *resource)
: QObject()
, d(new OutputConfigurationV2InterfacePrivate(this, parent, resource))
{
}
OutputConfigurationV2Interface::~OutputConfigurationV2Interface()
{
d->clearPendingChanges();
}
} }

View file

@ -6,88 +6,44 @@
*/ */
#pragma once #pragma once
#include "kwin_export.h" #include "qwayland-server-kde-output-management-v2.h"
#include "outputchangeset_v2.h" #include "outputchangeset_v2.h"
#include "outputdevice_v2_interface.h" #include "outputdevice_v2_interface.h"
#include "outputmanagement_v2_interface.h" #include "outputmanagement_v2_interface.h"
#include <QHash>
#include <optional>
namespace KWaylandServer namespace KWaylandServer
{ {
class OutputConfigurationV2InterfacePrivate; class OutputConfigurationV2Interface : public QtWaylandServer::kde_output_configuration_v2
/** @class OutputConfigurationInterface
*
* Holds a new configuration for the outputs.
*
* The overall mechanism is to get a new OutputConfiguration from the OutputManagement global and
* apply changes through the OutputConfiguration::set* calls. When all changes are set, the client
* calls apply, which asks the server to look at the changes and apply them. The server will then
* signal back whether the changes have been applied successfully (@c setApplied()) or were rejected
* or failed to apply (@c setFailed()).
*
* Once the client has called applied, the OutputManagementInterface send the configuration object
* to the compositor through the OutputManagement::configurationChangeRequested(OutputConfiguration*)
* signal, the compositor can then decide what to do with the changes.
*
* These KWayland classes will not apply changes to the OutputDevices, this is the compositor's
* task. As such, the configuration set through this interface can be seen as a hint what the
* compositor should set up, but whether or not the compositor does it (based on hardware or
* rendering policies, for example), is up to the compositor. The mode setting is passed on to
* the DRM subsystem through the compositor. The compositor also saves this configuration and reads
* it on startup, this interface is not involved in that process.
*
* @see OutputManagementInterface
* @see OutputConfiguration
*/
class KWIN_EXPORT OutputConfigurationV2Interface : public QObject
{ {
Q_OBJECT
public: public:
explicit OutputConfigurationV2Interface(wl_resource *resource);
~OutputConfigurationV2Interface() override; ~OutputConfigurationV2Interface() override;
/** OutputChangeSetV2 *pendingChanges(OutputDeviceV2Interface *outputdevice);
* Accessor for the changes made to OutputDevices. The data returned from this call
* will be deleted by the OutputConfigurationInterface when
* OutputManagementInterface::setApplied() or OutputManagementInterface::setFailed()
* is called, and on destruction of the OutputConfigurationInterface, so make sure you
* do not keep these pointers around.
* @returns A QHash of ChangeSets per outputdevice.
* @see ChangeSet
* @see OutputDeviceInterface
* @see OutputManagement
*/
QHash<OutputDeviceV2Interface *, OutputChangeSetV2 *> changes() const;
bool primaryChanged() const; bool applied = false;
OutputDeviceV2Interface *primary() const; QHash<OutputDeviceV2Interface *, OutputChangeSetV2 *> changes;
std::optional<OutputDeviceV2Interface *> primaryOutput;
public Q_SLOTS: protected:
/** void kde_output_configuration_v2_enable(Resource *resource, wl_resource *outputdevice, int32_t enable) override;
* Called by the compositor once the changes have successfully been applied. void kde_output_configuration_v2_mode(Resource *resource, struct ::wl_resource *outputdevice, struct ::wl_resource *mode) override;
* The compositor is responsible for updating the OutputDevices. After having void kde_output_configuration_v2_transform(Resource *resource, wl_resource *outputdevice, int32_t transform) override;
* done so, calling this function sends applied() through the client. void kde_output_configuration_v2_position(Resource *resource, wl_resource *outputdevice, int32_t x, int32_t y) override;
* @see setFailed void kde_output_configuration_v2_scale(Resource *resource, wl_resource *outputdevice, wl_fixed_t scale) override;
* @see OutputConfiguration::applied void kde_output_configuration_v2_apply(Resource *resource) override;
*/ void kde_output_configuration_v2_destroy(Resource *resource) override;
void setApplied(); void kde_output_configuration_v2_destroy_resource(Resource *resource) override;
/** void kde_output_configuration_v2_overscan(Resource *resource, wl_resource *outputdevice, uint32_t overscan) override;
* Called by the compositor when the changes as a whole are rejected or void kde_output_configuration_v2_set_vrr_policy(Resource *resource, struct ::wl_resource *outputdevice, uint32_t policy) override;
* failed to apply. This function results in the client OutputConfiguration emitting void kde_output_configuration_v2_set_rgb_range(Resource *resource, wl_resource *outputdevice, uint32_t rgbRange) override;
* failed(). void kde_output_configuration_v2_set_primary_output(Resource *resource, struct ::wl_resource *output) override;
* @see setApplied
* @see OutputConfiguration::failed
*/
void setFailed();
private:
explicit OutputConfigurationV2Interface(OutputManagementV2Interface *parent, wl_resource *resource);
friend class OutputManagementV2InterfacePrivate;
std::unique_ptr<OutputConfigurationV2InterfacePrivate> d;
}; };
} }
Q_DECLARE_METATYPE(KWaylandServer::OutputConfigurationV2Interface *)

View file

@ -42,7 +42,7 @@ void OutputManagementV2InterfacePrivate::kde_output_management_v2_create_configu
wl_client_post_no_memory(resource->client()); wl_client_post_no_memory(resource->client());
return; return;
} }
new OutputConfigurationV2Interface(q, config_resource); new OutputConfigurationV2Interface(config_resource);
} }
OutputManagementV2Interface::OutputManagementV2Interface(Display *display, QObject *parent) OutputManagementV2Interface::OutputManagementV2Interface(Display *display, QObject *parent)

View file

@ -15,7 +15,6 @@ namespace KWaylandServer
class Display; class Display;
class OutputManagementV2InterfacePrivate; class OutputManagementV2InterfacePrivate;
class OutputConfigurationV2Interface;
/** /**
* @class OutputManagementInterface * @class OutputManagementInterface
@ -38,22 +37,6 @@ public:
explicit OutputManagementV2Interface(Display *display, QObject *parent = nullptr); explicit OutputManagementV2Interface(Display *display, QObject *parent = nullptr);
~OutputManagementV2Interface() override; ~OutputManagementV2Interface() override;
Q_SIGNALS:
/**
* Emitted after the client has requested an OutputConfiguration to be applied.
* through OutputConfiguration::apply. The compositor can use this object to get
* notified when the new configuration is set up, and it should be applied to the
* Wayland server's OutputInterfaces.
*
* @param config The OutputConfigurationInterface corresponding to the client that
* called apply().
* @see OutputConfiguration::apply
* @see OutputConfigurationInterface
* @see OutputDeviceInterface
* @see OutputInterface
*/
void configurationChangeRequested(KWaylandServer::OutputConfigurationV2Interface *configurationInterface);
private: private:
std::unique_ptr<OutputManagementV2InterfacePrivate> d; std::unique_ptr<OutputManagementV2InterfacePrivate> d;
}; };

View file

@ -465,9 +465,6 @@ bool WaylandServer::init(InitializationFlags flags)
}); });
m_outputManagement = new OutputManagementV2Interface(m_display, m_display); m_outputManagement = new OutputManagementV2Interface(m_display, m_display);
connect(m_outputManagement, &OutputManagementV2Interface::configurationChangeRequested, this, [](KWaylandServer::OutputConfigurationV2Interface *config) {
kwinApp()->platform()->requestOutputsChange(config);
});
m_primary = new PrimaryOutputV1Interface(m_display, m_display); m_primary = new PrimaryOutputV1Interface(m_display, m_display);
m_xdgOutputManagerV1 = new XdgOutputManagerV1Interface(m_display, m_display); m_xdgOutputManagerV1 = new XdgOutputManagerV1Interface(m_display, m_display);