diff --git a/autotests/client/test_wayland_outputdevice.cpp b/autotests/client/test_wayland_outputdevice.cpp index 5bab042..4807713 100644 --- a/autotests/client/test_wayland_outputdevice.cpp +++ b/autotests/client/test_wayland_outputdevice.cpp @@ -1,613 +1,683 @@ /******************************************************************** Copyright 2014 Martin Gräßlin Copyright 2015 Sebastian Kügler 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/outputdevice.h" #include "../../src/client/registry.h" #include "../../src/server/display.h" #include "../../src/server/outputdevice_interface.h" // Wayland #include using namespace KWayland::Client; using namespace KWayland::Server; class TestWaylandOutputDevice : public QObject { Q_OBJECT public: explicit TestWaylandOutputDevice(QObject *parent = nullptr); private Q_SLOTS: void init(); void cleanup(); void testRegistry(); void testModeChanges(); void testScaleChange_legacy(); void testScaleChange(); + void testColorCurvesChange(); void testSubPixel_data(); void testSubPixel(); void testTransform_data(); void testTransform(); void testEnabled(); void testEdid(); void testId(); void testDone(); private: KWayland::Server::Display *m_display; KWayland::Server::OutputDeviceInterface *m_serverOutputDevice; QByteArray m_edid; + KWayland::Server::OutputDeviceInterface::ColorCurves m_initColorCurves; KWayland::Client::ConnectionThread *m_connection; KWayland::Client::EventQueue *m_queue; QThread *m_thread; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-output-0"); TestWaylandOutputDevice::TestWaylandOutputDevice(QObject *parent) : QObject(parent) , m_display(nullptr) , m_serverOutputDevice(nullptr) , m_connection(nullptr) , m_queue(nullptr) , m_thread(nullptr) { } void TestWaylandOutputDevice::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_serverOutputDevice = m_display->createOutputDevice(this); m_serverOutputDevice->setUuid("1337"); OutputDeviceInterface::Mode m0; m0.id = 0; m0.size = QSize(800, 600); m0.flags = OutputDeviceInterface::ModeFlags(OutputDeviceInterface::ModeFlag::Preferred); m_serverOutputDevice->addMode(m0); OutputDeviceInterface::Mode m1; m1.id = 1; m1.size = QSize(1024, 768); m_serverOutputDevice->addMode(m1); OutputDeviceInterface::Mode m2; m2.id = 2; m2.size = QSize(1280, 1024); m2.refreshRate = 90000; m_serverOutputDevice->addMode(m2); m_serverOutputDevice->setCurrentMode(1); m_edid = QByteArray::fromBase64("AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHowK0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg=="); m_serverOutputDevice->setEdid(m_edid); + m_initColorCurves.red.clear(); + m_initColorCurves.green.clear(); + m_initColorCurves.blue.clear(); + // 8 bit color ramps + for (int i = 0; i < 256; i++) { + quint16 val = (double)i / 255 * UINT16_MAX; + m_initColorCurves.red << val ; + m_initColorCurves.green << val ; + } + // 10 bit color ramp + for (int i = 0; i < 320; i++) { + m_initColorCurves.blue << (double)i / 319 * UINT16_MAX; + } + m_serverOutputDevice->setColorCurves(m_initColorCurves); + m_serverOutputDevice->create(); // 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()); } void TestWaylandOutputDevice::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_serverOutputDevice; m_serverOutputDevice = nullptr; delete m_display; m_display = nullptr; } void TestWaylandOutputDevice::testRegistry() { m_serverOutputDevice->setGlobalPosition(QPoint(100, 50)); m_serverOutputDevice->setPhysicalSize(QSize(200, 100)); KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice output; QVERIFY(!output.isValid()); QCOMPARE(output.uuid(), QByteArray()); 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.scale(), 1); + QCOMPARE(output.colorCurves().red, QVector()); + QCOMPARE(output.colorCurves().green, QVector()); + QCOMPARE(output.colorCurves().blue, QVector()); 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()); QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::done); QVERIFY(outputChanged.isValid()); output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); wl_display_flush(m_connection->display()); QVERIFY(outputChanged.wait()); QCOMPARE(output.geometry(), QRect(100, 50, 1024, 768)); QCOMPARE(output.globalPosition(), QPoint(100, 50)); QCOMPARE(output.manufacturer(), QStringLiteral("org.kde.kwin")); QCOMPARE(output.model(), QStringLiteral("none")); QCOMPARE(output.physicalSize(), QSize(200, 100)); QCOMPARE(output.pixelSize(), QSize(1024, 768)); QCOMPARE(output.refreshRate(), 60000); QCOMPARE(output.scale(), 1); + QCOMPARE(output.colorCurves().red, m_initColorCurves.red); + QCOMPARE(output.colorCurves().green, m_initColorCurves.green); + QCOMPARE(output.colorCurves().blue, m_initColorCurves.blue); // for xwayland output it's unknown QCOMPARE(output.subPixel(), KWayland::Client::OutputDevice::SubPixel::Unknown); // for xwayland transform is normal QCOMPARE(output.transform(), KWayland::Client::OutputDevice::Transform::Normal); QCOMPARE(output.edid(), m_edid); QCOMPARE(output.enabled(), OutputDevice::Enablement::Enabled); QCOMPARE(output.uuid(), QByteArray("1337")); } void TestWaylandOutputDevice::testModeChanges() { using namespace KWayland::Client; KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice output; QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::changed); QVERIFY(outputChanged.isValid()); QSignalSpy modeAddedSpy(&output, &KWayland::Client::OutputDevice::modeAdded); QVERIFY(modeAddedSpy.isValid()); QSignalSpy doneSpy(&output, &KWayland::Client::OutputDevice::done); QVERIFY(doneSpy.isValid()); output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); wl_display_flush(m_connection->display()); QVERIFY(doneSpy.wait()); QCOMPARE(modeAddedSpy.count(), 3); QCOMPARE(modeAddedSpy.at(0).first().value().size, QSize(800, 600)); QCOMPARE(modeAddedSpy.at(0).first().value().refreshRate, 60000); QCOMPARE(modeAddedSpy.at(0).first().value().flags, OutputDevice::Mode::Flags(OutputDevice::Mode::Flag::Preferred)); QCOMPARE(modeAddedSpy.at(0).first().value().output, QPointer(&output)); QVERIFY(modeAddedSpy.at(0).first().value().id > -1); QCOMPARE(modeAddedSpy.at(1).first().value().size, QSize(1280, 1024)); QCOMPARE(modeAddedSpy.at(1).first().value().refreshRate, 90000); QCOMPARE(modeAddedSpy.at(1).first().value().flags, OutputDevice::Mode::Flags(OutputDevice::Mode::Flag::None)); QCOMPARE(modeAddedSpy.at(1).first().value().output, QPointer(&output)); QVERIFY(modeAddedSpy.at(1).first().value().id > -1); QCOMPARE(modeAddedSpy.at(2).first().value().size, QSize(1024, 768)); QCOMPARE(modeAddedSpy.at(2).first().value().refreshRate, 60000); QCOMPARE(modeAddedSpy.at(2).first().value().flags, OutputDevice::Mode::Flags(OutputDevice::Mode::Flag::Current)); QCOMPARE(modeAddedSpy.at(2).first().value().output, QPointer(&output)); const QList &modes = output.modes(); QVERIFY(modeAddedSpy.at(2).first().value().id > -1); QCOMPARE(modes.size(), 3); QCOMPARE(modes.at(0), modeAddedSpy.at(0).first().value()); QCOMPARE(modes.at(1), modeAddedSpy.at(1).first().value()); QCOMPARE(modes.at(2), modeAddedSpy.at(2).first().value()); QCOMPARE(output.pixelSize(), QSize(1024, 768)); // change the current mode outputChanged.clear(); QSignalSpy modeChangedSpy(&output, &KWayland::Client::OutputDevice::modeChanged); QVERIFY(modeChangedSpy.isValid()); m_serverOutputDevice->setCurrentMode(0); QVERIFY(doneSpy.wait()); QCOMPARE(modeChangedSpy.size(), 2); // the one which lost the current flag QCOMPARE(modeChangedSpy.first().first().value().size, QSize(1024, 768)); QCOMPARE(modeChangedSpy.first().first().value().refreshRate, 60000); QCOMPARE(modeChangedSpy.first().first().value().flags, OutputDevice::Mode::Flags()); // the one which got the current flag QCOMPARE(modeChangedSpy.last().first().value().size, QSize(800, 600)); QCOMPARE(modeChangedSpy.last().first().value().refreshRate, 60000); QCOMPARE(modeChangedSpy.last().first().value().flags, OutputDevice::Mode::Flags(OutputDevice::Mode::Flag::Current | OutputDevice::Mode::Flag::Preferred)); QVERIFY(!outputChanged.isEmpty()); QCOMPARE(output.pixelSize(), QSize(800, 600)); const QList &modes2 = output.modes(); QCOMPARE(modes2.at(0).size, QSize(1280, 1024)); QCOMPARE(modes2.at(0).refreshRate, 90000); QCOMPARE(modes2.at(0).flags, OutputDevice::Mode::Flag::None); QCOMPARE(modes2.at(1).size, QSize(1024, 768)); QCOMPARE(modes2.at(1).refreshRate, 60000); QCOMPARE(modes2.at(1).flags, OutputDevice::Mode::Flag::None); QCOMPARE(modes2.at(2).size, QSize(800, 600)); QCOMPARE(modes2.at(2).refreshRate, 60000); QCOMPARE(modes2.at(2).flags, OutputDevice::Mode::Flag::Current | OutputDevice::Mode::Flag::Preferred); // change once more outputChanged.clear(); modeChangedSpy.clear(); m_serverOutputDevice->setCurrentMode(2); QVERIFY(doneSpy.wait()); QCOMPARE(modeChangedSpy.size(), 2); // the one which lost the current flag QCOMPARE(modeChangedSpy.first().first().value().size, QSize(800, 600)); QCOMPARE(modeChangedSpy.first().first().value().refreshRate, 60000); QCOMPARE(modeChangedSpy.first().first().value().flags, OutputDevice::Mode::Flags(OutputDevice::Mode::Flag::Preferred)); // the one which got the current flag QCOMPARE(modeChangedSpy.last().first().value().size, QSize(1280, 1024)); QCOMPARE(modeChangedSpy.last().first().value().refreshRate, 90000); QCOMPARE(modeChangedSpy.last().first().value().flags, OutputDevice::Mode::Flags(OutputDevice::Mode::Flag::Current)); QVERIFY(!outputChanged.isEmpty()); QCOMPARE(output.pixelSize(), QSize(1280, 1024)); } void TestWaylandOutputDevice::testScaleChange_legacy() { KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice output; QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::done); QVERIFY(outputChanged.isValid()); output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); wl_display_flush(m_connection->display()); QVERIFY(outputChanged.wait()); QCOMPARE(output.scale(), 1); // change the scale outputChanged.clear(); m_serverOutputDevice->setScale(2); QVERIFY(outputChanged.wait()); QCOMPARE(output.scale(), 2); QCOMPARE(output.scaleF(), 2.0); //check we're forward compatiable // change once more outputChanged.clear(); m_serverOutputDevice->setScale(4); QVERIFY(outputChanged.wait()); QCOMPARE(output.scale(), 4); QCOMPARE(output.scaleF(), 4.0); } void TestWaylandOutputDevice::testScaleChange() { KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice output; QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::done); QVERIFY(outputChanged.isValid()); output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); wl_display_flush(m_connection->display()); QVERIFY(outputChanged.wait()); QCOMPARE(output.scaleF(), 1.0); // change the scale outputChanged.clear(); m_serverOutputDevice->setScaleF(2.2); QVERIFY(outputChanged.wait()); QCOMPARE(output.scale(), 2); //check backwards compatibility works QCOMPARE(wl_fixed_from_double(output.scaleF()), wl_fixed_from_double(2.2)); // change once more outputChanged.clear(); m_serverOutputDevice->setScaleF(4.9); QVERIFY(outputChanged.wait()); QCOMPARE(output.scale(), 5); QCOMPARE(wl_fixed_from_double(output.scaleF()), wl_fixed_from_double(4.9)); } +void TestWaylandOutputDevice::testColorCurvesChange() +{ + KWayland::Client::Registry registry; + QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); + QVERIFY(interfacesAnnouncedSpy.isValid()); + QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); + registry.setEventQueue(m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + wl_display_flush(m_connection->display()); + QVERIFY(interfacesAnnouncedSpy.wait()); + + KWayland::Client::OutputDevice output; + QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::done); + QVERIFY(outputChanged.isValid()); + output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); + wl_display_flush(m_connection->display()); + QVERIFY(outputChanged.wait()); + QCOMPARE(output.colorCurves().red, m_initColorCurves.red); + QCOMPARE(output.colorCurves().green, m_initColorCurves.green); + QCOMPARE(output.colorCurves().blue, m_initColorCurves.blue); + + // change the color curves + outputChanged.clear(); + KWayland::Server::OutputDeviceInterface::ColorCurves cc; + cc.red = QVector(256, 0); + cc.green = QVector(256, UINT16_MAX); + cc.blue = QVector(320, 1); + m_serverOutputDevice->setColorCurves(cc); + QVERIFY(outputChanged.wait()); + QCOMPARE(output.colorCurves().red, cc.red); + QCOMPARE(output.colorCurves().green, cc.green); + QCOMPARE(output.colorCurves().blue, cc.blue); + + // change once more + outputChanged.clear(); + cc.red = QVector(256, 0); + cc.green = QVector(256, UINT16_MAX); + cc.blue = QVector(320, UINT16_MAX); + m_serverOutputDevice->setColorCurves(cc); + QVERIFY(outputChanged.wait()); + QCOMPARE(output.colorCurves().red, cc.red); + QCOMPARE(output.colorCurves().green, cc.green); + QCOMPARE(output.colorCurves().blue, cc.blue); +} + void TestWaylandOutputDevice::testSubPixel_data() { using namespace KWayland::Client; using namespace KWayland::Server; QTest::addColumn("expected"); QTest::addColumn("actual"); QTest::newRow("none") << OutputDevice::SubPixel::None << OutputDeviceInterface::SubPixel::None; QTest::newRow("horizontal/rgb") << OutputDevice::SubPixel::HorizontalRGB << OutputDeviceInterface::SubPixel::HorizontalRGB; QTest::newRow("horizontal/bgr") << OutputDevice::SubPixel::HorizontalBGR << OutputDeviceInterface::SubPixel::HorizontalBGR; QTest::newRow("vertical/rgb") << OutputDevice::SubPixel::VerticalRGB << OutputDeviceInterface::SubPixel::VerticalRGB; QTest::newRow("vertical/bgr") << OutputDevice::SubPixel::VerticalBGR << OutputDeviceInterface::SubPixel::VerticalBGR; } void TestWaylandOutputDevice::testSubPixel() { using namespace KWayland::Client; using namespace KWayland::Server; QFETCH(OutputDeviceInterface::SubPixel, actual); m_serverOutputDevice->setSubPixel(actual); KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice output; QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::done); QVERIFY(outputChanged.isValid()); output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); wl_display_flush(m_connection->display()); QVERIFY(outputChanged.wait()); QTEST(output.subPixel(), "expected"); // change back to unknown outputChanged.clear(); m_serverOutputDevice->setSubPixel(OutputDeviceInterface::SubPixel::Unknown); QVERIFY(outputChanged.wait()); QCOMPARE(output.subPixel(), OutputDevice::SubPixel::Unknown); } void TestWaylandOutputDevice::testTransform_data() { using namespace KWayland::Client; using namespace KWayland::Server; QTest::addColumn("expected"); QTest::addColumn("actual"); QTest::newRow("90") << OutputDevice::Transform::Rotated90 << OutputDeviceInterface::Transform::Rotated90; QTest::newRow("180") << OutputDevice::Transform::Rotated180 << OutputDeviceInterface::Transform::Rotated180; QTest::newRow("270") << OutputDevice::Transform::Rotated270 << OutputDeviceInterface::Transform::Rotated270; QTest::newRow("Flipped") << OutputDevice::Transform::Flipped << OutputDeviceInterface::Transform::Flipped; QTest::newRow("Flipped 90") << OutputDevice::Transform::Flipped90 << OutputDeviceInterface::Transform::Flipped90; QTest::newRow("Flipped 180") << OutputDevice::Transform::Flipped180 << OutputDeviceInterface::Transform::Flipped180; QTest::newRow("Flipped 280") << OutputDevice::Transform::Flipped270 << OutputDeviceInterface::Transform::Flipped270; } void TestWaylandOutputDevice::testTransform() { using namespace KWayland::Client; using namespace KWayland::Server; QFETCH(OutputDeviceInterface::Transform, actual); m_serverOutputDevice->setTransform(actual); KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice *output = registry.createOutputDevice(announced.first().first().value(), announced.first().last().value(), ®istry); QSignalSpy outputChanged(output, &KWayland::Client::OutputDevice::done); QVERIFY(outputChanged.isValid()); wl_display_flush(m_connection->display()); QVERIFY(outputChanged.wait()); QTEST(output->transform(), "expected"); // change back to normal outputChanged.clear(); m_serverOutputDevice->setTransform(OutputDeviceInterface::Transform::Normal); QVERIFY(outputChanged.wait()); QCOMPARE(output->transform(), OutputDevice::Transform::Normal); } void TestWaylandOutputDevice::testEnabled() { KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice output; QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::done); QVERIFY(outputChanged.isValid()); output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); wl_display_flush(m_connection->display()); QVERIFY(outputChanged.wait()); QCOMPARE(output.enabled(), OutputDevice::Enablement::Enabled); QSignalSpy changed(&output, &KWayland::Client::OutputDevice::changed); QSignalSpy enabledChanged(&output, &KWayland::Client::OutputDevice::enabledChanged); QVERIFY(enabledChanged.isValid()); m_serverOutputDevice->setEnabled(OutputDeviceInterface::Enablement::Disabled); QVERIFY(enabledChanged.wait()); QCOMPARE(output.enabled(), OutputDevice::Enablement::Disabled); if (changed.count() != enabledChanged.count()) { QVERIFY(changed.wait()); } QCOMPARE(changed.count(), enabledChanged.count()); m_serverOutputDevice->setEnabled(OutputDeviceInterface::Enablement::Enabled); QVERIFY(enabledChanged.wait()); QCOMPARE(output.enabled(), OutputDevice::Enablement::Enabled); if (changed.count() != enabledChanged.count()) { QVERIFY(changed.wait()); } QCOMPARE(changed.count(), enabledChanged.count()); } void TestWaylandOutputDevice::testEdid() { KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice output; QCOMPARE(output.edid(), QByteArray()); QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::done); QVERIFY(outputChanged.isValid()); output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); wl_display_flush(m_connection->display()); QVERIFY(outputChanged.wait()); QCOMPARE(output.edid(), m_edid); } void TestWaylandOutputDevice::testId() { KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice output; QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::done); QVERIFY(outputChanged.isValid()); output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); wl_display_flush(m_connection->display()); QVERIFY(outputChanged.wait()); QCOMPARE(output.uuid(), QByteArray("1337")); QSignalSpy idChanged(&output, &KWayland::Client::OutputDevice::uuidChanged); QVERIFY(idChanged.isValid()); m_serverOutputDevice->setUuid("42"); QVERIFY(idChanged.wait()); QCOMPARE(idChanged.first().first().toByteArray(), QByteArray("42")); idChanged.clear(); QCOMPARE(output.uuid(), QByteArray("42")); m_serverOutputDevice->setUuid("4711"); QVERIFY(idChanged.wait()); QCOMPARE(idChanged.first().first().toByteArray(), QByteArray("4711")); idChanged.clear(); QCOMPARE(output.uuid(), QByteArray("4711")); } void TestWaylandOutputDevice::testDone() { KWayland::Client::Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy announced(®istry, &KWayland::Client::Registry::outputDeviceAnnounced); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_display_flush(m_connection->display()); QVERIFY(interfacesAnnouncedSpy.wait()); KWayland::Client::OutputDevice output; QSignalSpy outputDone(&output, &KWayland::Client::OutputDevice::done); QVERIFY(outputDone.isValid()); output.setup(registry.bindOutputDevice(announced.first().first().value(), announced.first().last().value())); wl_display_flush(m_connection->display()); QVERIFY(outputDone.wait()); } QTEST_GUILESS_MAIN(TestWaylandOutputDevice) #include "test_wayland_outputdevice.moc" diff --git a/autotests/client/test_wayland_outputmanagement.cpp b/autotests/client/test_wayland_outputmanagement.cpp index 935da29..bbb719c 100644 --- a/autotests/client/test_wayland_outputmanagement.cpp +++ b/autotests/client/test_wayland_outputmanagement.cpp @@ -1,535 +1,545 @@ /******************************************************************** Copyright 2014 Martin Gräßlin Copyright 2015 Sebastian Kügler 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/outputdevice.h" #include "../../src/client/outputconfiguration.h" #include "../../src/client/outputmanagement.h" #include "../../src/client/output.h" #include "../../src/client/registry.h" #include "../../src/server/display.h" #include "../../src/server/shell_interface.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/outputconfiguration_interface.h" #include "../../src/server/outputdevice_interface.h" #include "../../src/server/outputmanagement_interface.h" // Wayland #include using namespace KWayland::Client; using namespace KWayland::Server; 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(KWayland::Server::OutputConfigurationInterface *configurationInterface); KWayland::Server::Display *m_display; KWayland::Server::OutputManagementInterface *m_outputManagementInterface; QList 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 m_clientOutputs; QList m_modes; KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread; QSignalSpy *m_announcedSpy; QSignalSpy *m_omSpy; QSignalSpy *m_configSpy; }; 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(); } void TestWaylandOutputManagement::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()); auto shell = m_display->createShell(this); shell->create(); auto comp = m_display->createCompositor(this); comp->create(); auto outputDeviceInterface = m_display->createOutputDevice(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)); outputDeviceInterface->create(); m_serverOutputs << outputDeviceInterface; m_outputManagementInterface = m_display->createOutputManagement(this); m_outputManagementInterface->create(); QVERIFY(m_outputManagementInterface->isValid()); // 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(), m_announcedSpy->first().last().value()); 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; } if (m_outputManagementInterface) { delete m_outputManagementInterface; m_outputManagementInterface = nullptr; } delete m_display; m_display = nullptr; m_serverOutputs.clear(); m_clientOutputs.clear(); } void TestWaylandOutputManagement::applyPendingChanges(KWayland::Server::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->scale(), 1); + QCOMPARE(output->colorCurves().red, QVector()); + QCOMPARE(output->colorCurves().green, QVector()); + QCOMPARE(output->colorCurves().blue, QVector()); 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(), QByteArray()); QSignalSpy outputChanged(output, &KWayland::Client::OutputDevice::changed); QVERIFY(outputChanged.isValid()); output->setup(m_registry->bindOutputDevice(m_omSpy->first().first().value(), m_omSpy->first().last().value())); 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); KWayland::Server::OutputConfigurationInterface *configurationInterface = nullptr; connect(m_outputManagementInterface, &OutputManagementInterface::configurationChangeRequested, [=, &configurationInterface](KWayland::Server::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, [=](KWayland::Server::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, [=](KWayland::Server::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); KWayland::Server::OutputConfigurationInterface *configurationInterface; connect(m_outputManagementInterface, &OutputManagementInterface::configurationChangeRequested, [=, &configurationInterface](KWayland::Server::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->setScale(output, 2); + const auto zeroVector = QVector(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->setScale(output, 1); + const auto oneVector = QVector(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; auto s_o = m_serverOutputs.first(); KWayland::Client::OutputDevice *output = m_clientOutputs.first(); QVERIFY(config->isValid()); QVERIFY(s_o->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, [=](KWayland::Server::OutputConfigurationInterface *c) { c->setFailed(); }); QVERIFY(serverApplySpy.wait(200)); // Artificialy 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, [=](KWayland::Server::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, [=](KWayland::Server::OutputConfigurationInterface *c) { applyPendingChanges(c); c->setApplied(); }); QVERIFY(configAppliedSpy.isValid()); QVERIFY(configAppliedSpy.wait(200)); QCOMPARE(output->scale(), 2); //test backwards compatibility QCOMPARE(wl_fixed_from_double(output->scaleF()), wl_fixed_from_double(2.3)); config->setScale(output, 3); config->apply(); QVERIFY(configAppliedSpy.isValid()); QVERIFY(configAppliedSpy.wait(200)); //will be setApplied using the connect above QCOMPARE(output->scale(), 3); QCOMPARE(output->scaleF(), 3.0); //test fowards compatibility } QTEST_GUILESS_MAIN(TestWaylandOutputManagement) #include "test_wayland_outputmanagement.moc" diff --git a/src/client/outputconfiguration.cpp b/src/client/outputconfiguration.cpp index 90016bf..e4f3ee1 100644 --- a/src/client/outputconfiguration.cpp +++ b/src/client/outputconfiguration.cpp @@ -1,208 +1,232 @@ /**************************************************************************** * Copyright 2015 Sebastian Kügler * * 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 "outputconfiguration.h" #include "outputdevice.h" #include "outputmanagement.h" #include "event_queue.h" #include "wayland_pointer_p.h" #include "wayland-output-management-client-protocol.h" #include "wayland-org_kde_kwin_outputdevice-client-protocol.h" namespace KWayland { namespace Client { class Q_DECL_HIDDEN OutputConfiguration::Private { public: Private() = default; void setup(org_kde_kwin_outputconfiguration *outputconfiguration); WaylandPointer outputconfiguration; static struct org_kde_kwin_outputconfiguration_listener s_outputconfigurationListener; EventQueue *queue = nullptr; OutputConfiguration *q; private: static void appliedCallback(void *data, org_kde_kwin_outputconfiguration *config); static void failedCallback(void *data, org_kde_kwin_outputconfiguration *config); }; OutputConfiguration::OutputConfiguration(QObject *parent) : QObject(parent) , d(new Private) { d->q = this; } OutputConfiguration::~OutputConfiguration() { release(); } void OutputConfiguration::setup(org_kde_kwin_outputconfiguration *outputconfiguration) { Q_ASSERT(outputconfiguration); Q_ASSERT(!d->outputconfiguration); d->outputconfiguration.setup(outputconfiguration); d->setup(outputconfiguration); } void OutputConfiguration::Private::setup(org_kde_kwin_outputconfiguration* outputconfiguration) { org_kde_kwin_outputconfiguration_add_listener(outputconfiguration, &s_outputconfigurationListener, this); } void OutputConfiguration::release() { d->outputconfiguration.release(); } void OutputConfiguration::destroy() { d->outputconfiguration.destroy(); } void OutputConfiguration::setEventQueue(EventQueue *queue) { d->queue = queue; } EventQueue *OutputConfiguration::eventQueue() { return d->queue; } OutputConfiguration::operator org_kde_kwin_outputconfiguration*() { return d->outputconfiguration; } OutputConfiguration::operator org_kde_kwin_outputconfiguration*() const { return d->outputconfiguration; } bool OutputConfiguration::isValid() const { return d->outputconfiguration.isValid(); } // Requests void OutputConfiguration::setEnabled(OutputDevice *outputdevice, OutputDevice::Enablement enable) { qint32 _enable = ORG_KDE_KWIN_OUTPUTDEVICE_ENABLEMENT_DISABLED; if (enable == OutputDevice::Enablement::Enabled) { _enable = ORG_KDE_KWIN_OUTPUTDEVICE_ENABLEMENT_ENABLED; } org_kde_kwin_outputdevice *od = outputdevice->output(); org_kde_kwin_outputconfiguration_enable(d->outputconfiguration, od, _enable); } void OutputConfiguration::setMode(OutputDevice* outputdevice, const int modeId) { org_kde_kwin_outputdevice *od = outputdevice->output(); org_kde_kwin_outputconfiguration_mode(d->outputconfiguration, od, modeId); } void OutputConfiguration::setTransform(OutputDevice *outputdevice, KWayland::Client::OutputDevice::Transform transform) { auto toTransform = [transform]() { switch (transform) { using KWayland::Client::OutputDevice; case KWayland::Client::OutputDevice::Transform::Normal: return WL_OUTPUT_TRANSFORM_NORMAL; case KWayland::Client::OutputDevice::Transform::Rotated90: return WL_OUTPUT_TRANSFORM_90; case KWayland::Client::OutputDevice::Transform::Rotated180: return WL_OUTPUT_TRANSFORM_180; case KWayland::Client::OutputDevice::Transform::Rotated270: return WL_OUTPUT_TRANSFORM_270; case KWayland::Client::OutputDevice::Transform::Flipped: return WL_OUTPUT_TRANSFORM_FLIPPED; case KWayland::Client::OutputDevice::Transform::Flipped90: return WL_OUTPUT_TRANSFORM_FLIPPED_90; case KWayland::Client::OutputDevice::Transform::Flipped180: return WL_OUTPUT_TRANSFORM_FLIPPED_180; case KWayland::Client::OutputDevice::Transform::Flipped270: return WL_OUTPUT_TRANSFORM_FLIPPED_270; } abort(); }; org_kde_kwin_outputdevice *od = outputdevice->output(); org_kde_kwin_outputconfiguration_transform(d->outputconfiguration, od, toTransform()); } void OutputConfiguration::setPosition(OutputDevice *outputdevice, const QPoint &pos) { org_kde_kwin_outputdevice *od = outputdevice->output(); org_kde_kwin_outputconfiguration_position(d->outputconfiguration, od, pos.x(), pos.y()); } void OutputConfiguration::setScale(OutputDevice *outputdevice, qint32 scale) { setScaleF(outputdevice, scale); } void OutputConfiguration::setScaleF(OutputDevice *outputdevice, qreal scale) { org_kde_kwin_outputdevice *od = outputdevice->output(); if (wl_proxy_get_version(d->outputconfiguration) < ORG_KDE_KWIN_OUTPUTCONFIGURATION_SCALEF_SINCE_VERSION) { org_kde_kwin_outputconfiguration_scale(d->outputconfiguration, od, qRound(scale)); } else { org_kde_kwin_outputconfiguration_scalef(d->outputconfiguration, od, wl_fixed_from_double(scale)); } } +void OutputConfiguration::setColorCurves(OutputDevice *outputdevice, + QVector red, QVector green, QVector blue) +{ + org_kde_kwin_outputdevice *od = outputdevice->output(); + + wl_array wlRed, wlGreen, wlBlue; + + auto fillArray = [](QVector &origin, wl_array *dest) { + wl_array_init(dest); + const size_t memLength = sizeof(uint16_t) * origin.size(); + void *s = wl_array_add(dest, memLength); + memcpy(s, origin.data(), memLength); + }; + fillArray(red, &wlRed); + fillArray(green, &wlGreen); + fillArray(blue, &wlBlue); + + org_kde_kwin_outputconfiguration_colorcurves(d->outputconfiguration, od, &wlRed, &wlGreen, &wlBlue); + + wl_array_release(&wlRed); + wl_array_release(&wlGreen); + wl_array_release(&wlBlue); +} + void OutputConfiguration::apply() { org_kde_kwin_outputconfiguration_apply(d->outputconfiguration); } // Callbacks org_kde_kwin_outputconfiguration_listener OutputConfiguration::Private::s_outputconfigurationListener = { appliedCallback, failedCallback }; void OutputConfiguration::Private::appliedCallback(void* data, org_kde_kwin_outputconfiguration* config) { Q_UNUSED(config); auto o = reinterpret_cast(data); emit o->q->applied(); } void OutputConfiguration::Private::failedCallback(void* data, org_kde_kwin_outputconfiguration* config) { Q_UNUSED(config); auto o = reinterpret_cast(data); emit o->q->failed(); } } } diff --git a/src/client/outputconfiguration.h b/src/client/outputconfiguration.h index 0102a6e..242108f 100644 --- a/src/client/outputconfiguration.h +++ b/src/client/outputconfiguration.h @@ -1,257 +1,273 @@ /**************************************************************************** * Copyright 2015 Sebastian Kügler * * 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_CLIENT_OUTPUTCONFIGURATION_H #define KWAYLAND_CLIENT_OUTPUTCONFIGURATION_H #include #include +#include #include "outputdevice.h" #include struct org_kde_kwin_outputmanagement; struct org_kde_kwin_outputconfiguration; namespace KWayland { namespace Client { class EventQueue; /** @class OutputConfiguration * * OutputConfiguration provides access to changing OutputDevices. The interface is async * and atomic. An OutputConfiguration is created through OutputManagement::createConfiguration(). * * 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 applied()) or were rejected * or failed to apply (@c failed()). * * The current settings for outputdevices can be gotten from @c Registry::outputDevices(), these * are used in the set* calls to identify the output the setting applies to. * * 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. * * @c apply() should only be called after changes to all output devices have been made, not after * each change. This allows to test the new configuration as a whole, and is a lot faster since * hardware changes can be tested in their new combination, they done in parallel.and rolled back * as a whole. * * \verbatim // We're just picking the first of our outputdevices KWayland::Client::OutputDevice *output = m_clientOutputs.first(); // Create a new configuration object auto config = m_outputManagement.createConfiguration(); // handle applied and failed signals connect(config, &OutputConfiguration::applied, []() { qDebug() << "Configuration applied!"; }); connect(config, &OutputConfiguration::failed, []() { qDebug() << "Configuration failed!"; }); // Change settings config->setMode(output, m_clientOutputs.first()->modes().last().id); config->setTransform(output, OutputDevice::Transform::Normal); config->setPosition(output, QPoint(0, 1920)); config->setScale(output, 2); // Now ask the compositor to apply the changes config->apply(); // You may wait for the applied() or failed() signal here \endverbatim * @see OutputDevice * @see OutputManagement * @see OutputManagement::createConfiguration() * @since 5.5 */ class KWAYLANDCLIENT_EXPORT OutputConfiguration : public QObject { Q_OBJECT public: virtual ~OutputConfiguration(); /** * Setup this OutputConfiguration to manage the @p outputconfiguration. * When using OutputManagement::createOutputConfiguration there is no need to call this * method. * @param outputconfiguration the outputconfiguration object to set up. **/ void setup(org_kde_kwin_outputconfiguration *outputconfiguration); /** * @returns @c true if managing a org_kde_kwin_outputconfiguration. **/ bool isValid() const; /** * Releases the org_kde_kwin_outputconfiguration interface. * After the interface has been released the OutputConfiguration instance is no * longer valid and can be setup with another org_kde_kwin_outputconfiguration interface. **/ void release(); /** * Destroys the data held by this OutputConfiguration. * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid any more, it's not * possible to call release any more as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or setup to a new org_kde_kwin_outputconfiguration interface * once there is a new connection available. * * This method is automatically invoked when the Registry which created this * OutputConfiguration gets destroyed. * * * @see release **/ void destroy(); /** * Sets the @p queue to use for creating a OutputConfiguration. **/ void setEventQueue(EventQueue *queue); /** * @returns The event queue to use for creating a OutputConfiguration **/ EventQueue *eventQueue(); /** * Enable or disable an output. Enabled means it's used by the * compositor for rendering, Disabled means, that no wl_output * is connected to this, and the device is sitting there unused * by this compositor. * The changes done in this call will be recorded in the * OutputDevice and only applied after apply() has been called. * * @param outputdevice the OutputDevice this change applies to. * @param enable new Enablement state of this output device. */ void setEnabled(OutputDevice *outputdevice, OutputDevice::Enablement enable); /** * Set the mode of this output, identified by its mode id. * The changes done in this call will be recorded in the * OutputDevice and only applied after apply() has been called. * * @param outputdevice the OutputDevice this change applies to. * @param modeId the id of the mode. */ void setMode(OutputDevice *outputdevice, const int modeId); /** * Set transformation for this output, for example rotated or flipped. * The changes done in this call will be recorded in the * OutputDevice and only applied after apply() has been called. * * @param outputdevice the OutputDevice this change applies to. * @param scale the scaling factor for this output device. */ void setTransform(OutputDevice *outputdevice, KWayland::Client::OutputDevice::Transform transform); /** * Position this output in the global space, relative to other outputs. * QPoint(0, 0) for top-left. The position is the top-left corner of this output. * There may not be gaps between outputs, they have to be positioned adjacend to * each other. * The changes done in this call will be recorded in the * OutputDevice and only applied after apply() has been called. * * @param outputdevice the OutputDevice this change applies to. * @param pos the OutputDevice global position relative to other outputs, * */ void setPosition(OutputDevice *outputdevice, const QPoint &pos); /** * Scale rendering of this output. * The changes done in this call will be recorded in the * OutputDevice and only applied after apply() has been called. * @deprecated see setScaleF(qreal) * * @param scale the scaling factor for this output device. * @param outputdevice the OutputDevice this change applies to. */ void setScale(OutputDevice *outputdevice, qint32 scale); /** * Scale rendering of this output. * The changes done in this call will be recorded in the * OutputDevice and only applied after apply() has been called. * * @param scale the scaling factor for this output device. * @param outputdevice the OutputDevice this change applies to. * @since 5.50 */ void setScaleF(OutputDevice *outputdevice, qreal scale); + /* Set color curves for this output. The respective color curve vector + * lengths must equal the current ones in the OutputDevice. The codomain + * of the curves is always the full uint16 value range, such that any vector + * is accepted as long as it has the right size. + * The changes done in this call will be recorded in the + * OutputDevice and only applied after apply() has been called. + * + * @param red color curve of red channel. + * @param green color curve of green channel. + * @param blue color curve of blue channel. + * @param outputdevice the OutputDevice this change applies to. + * @since 5.50 + */ + void setColorCurves(OutputDevice *outputdevice, QVector red, QVector green, QVector blue); + /** * Ask the compositor to apply the changes. * This results in the compositor looking at all outputdevices and if they have * pending changes from the set* calls, these changes will be tested with the * hardware and applied if possible. The compositor will react to these changes * with the applied() or failed() signals. Note that mode setting may take a * while, so the interval between calling apply() and receiving the applied() * signal may be considerable, depending on the hardware. * * @see applied() * @see failed() */ void apply(); operator org_kde_kwin_outputconfiguration*(); operator org_kde_kwin_outputconfiguration*() const; Q_SIGNALS: /** * The server has applied all settings successfully. Pending changes in the * OutputDevices have been cleared, changed signals from the OutputDevice have * been emitted. */ void applied(); /** * The server has failed to apply the settings or rejected them. Pending changes * in the * OutputDevices have been cleared. No changes have been applied to the * OutputDevices. */ void failed(); private: friend class OutputManagement; explicit OutputConfiguration(QObject *parent = nullptr); class Private; QScopedPointer d; }; } } Q_DECLARE_METATYPE(KWayland::Client::OutputConfiguration*) #endif diff --git a/src/client/outputdevice.cpp b/src/client/outputdevice.cpp index 35e8351..f9a7aee 100644 --- a/src/client/outputdevice.cpp +++ b/src/client/outputdevice.cpp @@ -1,464 +1,512 @@ /******************************************************************** Copyright 2013 Martin Gräßlin +Copyright 2018 Roman Gilg 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 "logging_p.h" #include "outputdevice.h" #include "wayland_pointer_p.h" // Qt #include #include #include #include // wayland #include "wayland-org_kde_kwin_outputdevice-client-protocol.h" #include namespace KWayland { namespace Client { typedef QList Modes; class Q_DECL_HIDDEN OutputDevice::Private { public: Private(OutputDevice *q); void setup(org_kde_kwin_outputdevice *o); WaylandPointer output; EventQueue *queue = nullptr; QSize physicalSize; QPoint globalPosition; QString manufacturer; QString model; qreal scale = 1.0; SubPixel subPixel = SubPixel::Unknown; Transform transform = Transform::Normal; Modes modes; Modes::iterator currentMode = modes.end(); QByteArray edid; OutputDevice::Enablement enabled = OutputDevice::Enablement::Enabled; QByteArray uuid; + + ColorCurves colorCurves; + bool done = false; private: static void geometryCallback(void *data, org_kde_kwin_outputdevice *output, int32_t x, int32_t y, int32_t physicalWidth, int32_t physicalHeight, int32_t subPixel, const char *make, const char *model, int32_t transform); static void modeCallback(void *data, org_kde_kwin_outputdevice *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh, int32_t mode_id); static void doneCallback(void *data, org_kde_kwin_outputdevice *output); static void scaleCallback(void *data, org_kde_kwin_outputdevice *output, int32_t scale); static void scaleFCallback(void *data, org_kde_kwin_outputdevice *output, wl_fixed_t scale); static void edidCallback(void *data, org_kde_kwin_outputdevice *output, const char *raw); static void enabledCallback(void *data, org_kde_kwin_outputdevice *output, int32_t enabled); static void uuidCallback(void *data, org_kde_kwin_outputdevice *output, const char *uuid); + static void colorcurvesCallback(void *data, org_kde_kwin_outputdevice *output, + wl_array *red, wl_array *green, wl_array *blue); + void setPhysicalSize(const QSize &size); void setGlobalPosition(const QPoint &pos); void setManufacturer(const QString &manufacturer); void setModel(const QString &model); void setScale(qreal scale); void setSubPixel(SubPixel subPixel); void setTransform(Transform transform); void addMode(uint32_t flags, int32_t width, int32_t height, int32_t refresh, int32_t mode_id); OutputDevice *q; static struct org_kde_kwin_outputdevice_listener s_outputListener; }; OutputDevice::Private::Private(OutputDevice *q) : q(q) { } void OutputDevice::Private::setup(org_kde_kwin_outputdevice *o) { Q_ASSERT(o); Q_ASSERT(!output); output.setup(o); org_kde_kwin_outputdevice_add_listener(output, &s_outputListener, this); } bool OutputDevice::Mode::operator==(const OutputDevice::Mode &m) const { return size == m.size && refreshRate == m.refreshRate && flags == m.flags && output == m.output; } +bool OutputDevice::ColorCurves::operator==(const OutputDevice::ColorCurves &cc) const +{ + return red == cc.red && green == cc.green && blue == cc.blue; +} +bool OutputDevice::ColorCurves::operator!=(const ColorCurves &cc) const { + return !operator==(cc); +} + OutputDevice::OutputDevice(QObject *parent) : QObject(parent) , d(new Private(this)) { } OutputDevice::~OutputDevice() { d->output.release(); } org_kde_kwin_outputdevice_listener OutputDevice::Private::s_outputListener = { geometryCallback, modeCallback, doneCallback, scaleCallback, edidCallback, enabledCallback, uuidCallback, - scaleFCallback + scaleFCallback, + colorcurvesCallback }; void OutputDevice::Private::geometryCallback(void *data, org_kde_kwin_outputdevice *output, int32_t x, int32_t y, int32_t physicalWidth, int32_t physicalHeight, int32_t subPixel, const char *make, const char *model, int32_t transform) { Q_UNUSED(transform) auto o = reinterpret_cast(data); Q_ASSERT(o->output == output); o->setGlobalPosition(QPoint(x, y)); o->setManufacturer(make); o->setModel(model); o->setPhysicalSize(QSize(physicalWidth, physicalHeight)); auto toSubPixel = [subPixel]() { switch (subPixel) { case WL_OUTPUT_SUBPIXEL_NONE: return SubPixel::None; case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: return SubPixel::HorizontalRGB; case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: return SubPixel::HorizontalBGR; case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: return SubPixel::VerticalRGB; case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: return SubPixel::VerticalBGR; case WL_OUTPUT_SUBPIXEL_UNKNOWN: default: return SubPixel::Unknown; } }; o->setSubPixel(toSubPixel()); auto toTransform = [transform]() { switch (transform) { case WL_OUTPUT_TRANSFORM_90: return Transform::Rotated90; case WL_OUTPUT_TRANSFORM_180: return Transform::Rotated180; case WL_OUTPUT_TRANSFORM_270: return Transform::Rotated270; case WL_OUTPUT_TRANSFORM_FLIPPED: return Transform::Flipped; case WL_OUTPUT_TRANSFORM_FLIPPED_90: return Transform::Flipped90; case WL_OUTPUT_TRANSFORM_FLIPPED_180: return Transform::Flipped180; case WL_OUTPUT_TRANSFORM_FLIPPED_270: return Transform::Flipped270; case WL_OUTPUT_TRANSFORM_NORMAL: default: return Transform::Normal; } }; o->setTransform(toTransform()); } void OutputDevice::Private::modeCallback(void *data, org_kde_kwin_outputdevice *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh, int32_t mode_id) { auto o = reinterpret_cast(data); Q_ASSERT(o->output == output); o->addMode(flags, width, height, refresh, mode_id); } void OutputDevice::Private::addMode(uint32_t flags, int32_t width, int32_t height, int32_t refresh, int32_t mode_id) { Mode mode; mode.output = QPointer(q); mode.refreshRate = refresh; mode.size = QSize(width, height); mode.id = mode_id; if (flags & WL_OUTPUT_MODE_CURRENT) { mode.flags |= Mode::Flag::Current; } if (flags & WL_OUTPUT_MODE_PREFERRED) { mode.flags |= Mode::Flag::Preferred; } auto currentIt = modes.insert(modes.end(), mode); bool existing = false; if (flags & WL_OUTPUT_MODE_CURRENT) { auto it = modes.begin(); while (it != currentIt) { auto &m = (*it); if (m.flags.testFlag(Mode::Flag::Current)) { m.flags &= ~Mode::Flags(Mode::Flag::Current); emit q->modeChanged(m); } if (m.refreshRate == mode.refreshRate && m.size == mode.size) { it = modes.erase(it); existing = true; } else { it++; } } currentMode = currentIt; } if (existing) { emit q->modeChanged(mode); } else { emit q->modeAdded(mode); } } KWayland::Client::OutputDevice::Mode OutputDevice::currentMode() const { for (const auto &m: modes()) { if (m.flags.testFlag(KWayland::Client::OutputDevice::Mode::Flag::Current)) { return m; } } qCWarning(KWAYLAND_CLIENT) << "current mode not found"; return Mode(); } void OutputDevice::Private::scaleCallback(void *data, org_kde_kwin_outputdevice *output, int32_t scale) { auto o = reinterpret_cast(data); Q_ASSERT(o->output == output); o->setScale(scale); } void OutputDevice::Private::scaleFCallback(void *data, org_kde_kwin_outputdevice *output, wl_fixed_t scale_fixed) { auto o = reinterpret_cast(data); Q_ASSERT(o->output == output); o->setScale(wl_fixed_to_double(scale_fixed)); } void OutputDevice::Private::doneCallback(void *data, org_kde_kwin_outputdevice *output) { auto o = reinterpret_cast(data); Q_ASSERT(o->output == output); o->done = true; emit o->q->changed(); emit o->q->done(); } void OutputDevice::Private::edidCallback(void* data, org_kde_kwin_outputdevice* output, const char* raw) { Q_UNUSED(output); auto o = reinterpret_cast(data); o->edid = QByteArray::fromBase64(raw); } void OutputDevice::Private::enabledCallback(void* data, org_kde_kwin_outputdevice* output, int32_t enabled) { Q_UNUSED(output); auto o = reinterpret_cast(data); OutputDevice::Enablement _enabled = OutputDevice::Enablement::Disabled; if (enabled == ORG_KDE_KWIN_OUTPUTDEVICE_ENABLEMENT_ENABLED) { _enabled = OutputDevice::Enablement::Enabled; } if (o->enabled != _enabled) { o->enabled = _enabled; emit o->q->enabledChanged(o->enabled); if (o->done) { emit o->q->changed(); } } } void OutputDevice::Private::uuidCallback(void* data, org_kde_kwin_outputdevice* output, const char *uuid) { Q_UNUSED(output); auto o = reinterpret_cast(data); if (o->uuid != uuid) { o->uuid = uuid; emit o->q->uuidChanged(o->uuid); if (o->done) { emit o->q->changed(); } } } +void OutputDevice::Private::colorcurvesCallback(void *data, org_kde_kwin_outputdevice *output, + wl_array *red, + wl_array *green, + wl_array *blue) +{ + Q_UNUSED(output); + auto o = reinterpret_cast(data); + + auto cc = ColorCurves(); + + auto setCurve = [](const wl_array *curve, QVector *destination) { + destination->resize(curve->size / sizeof(uint16_t)); + memcpy(destination->data(), curve->data, curve->size); + }; + setCurve(red, &cc.red); + setCurve(green, &cc.green); + setCurve(blue, &cc.blue); + + if (o->colorCurves != cc) { + o->colorCurves = cc; + emit o->q->colorCurvesChanged(); + if (o->done) { + emit o->q->changed(); + } + } +} + void OutputDevice::setup(org_kde_kwin_outputdevice *output) { d->setup(output); } EventQueue *OutputDevice::eventQueue() const { return d->queue; } void OutputDevice::setEventQueue(EventQueue *queue) { d->queue = queue; } void OutputDevice::Private::setGlobalPosition(const QPoint &pos) { globalPosition = pos; } void OutputDevice::Private::setManufacturer(const QString &m) { manufacturer = m; } void OutputDevice::Private::setModel(const QString &m) { model = m; } void OutputDevice::Private::setPhysicalSize(const QSize &size) { physicalSize = size; } void OutputDevice::Private::setScale(qreal s) { scale = s; } QRect OutputDevice::geometry() const { if (d->currentMode == d->modes.end()) { return QRect(); } return QRect(d->globalPosition, pixelSize()); } void OutputDevice::Private::setSubPixel(OutputDevice::SubPixel s) { subPixel = s; } void OutputDevice::Private::setTransform(OutputDevice::Transform t) { transform = t; } QPoint OutputDevice::globalPosition() const { return d->globalPosition; } QString OutputDevice::manufacturer() const { return d->manufacturer; } QString OutputDevice::model() const { return d->model; } org_kde_kwin_outputdevice *OutputDevice::output() { return d->output; } QSize OutputDevice::physicalSize() const { return d->physicalSize; } QSize OutputDevice::pixelSize() const { if (d->currentMode == d->modes.end()) { return QSize(); } return (*d->currentMode).size; } int OutputDevice::refreshRate() const { if (d->currentMode == d->modes.end()) { return 0; } return (*d->currentMode).refreshRate; } int OutputDevice::scale() const { return qRound(d->scale); } qreal OutputDevice::scaleF() const { return d->scale; } bool OutputDevice::isValid() const { return d->output.isValid(); } OutputDevice::SubPixel OutputDevice::subPixel() const { return d->subPixel; } OutputDevice::Transform OutputDevice::transform() const { return d->transform; } QList< OutputDevice::Mode > OutputDevice::modes() const { return d->modes; } OutputDevice::operator org_kde_kwin_outputdevice*() { return d->output; } OutputDevice::operator org_kde_kwin_outputdevice*() const { return d->output; } QByteArray OutputDevice::edid() const { return d->edid; } OutputDevice::Enablement OutputDevice::enabled() const { return d->enabled; } QByteArray OutputDevice::uuid() const { return d->uuid; } +OutputDevice::ColorCurves OutputDevice::colorCurves() const +{ + return d->colorCurves; +} + void OutputDevice::destroy() { d->output.destroy(); } } } diff --git a/src/client/outputdevice.h b/src/client/outputdevice.h index 74a0628..6589ff8 100644 --- a/src/client/outputdevice.h +++ b/src/client/outputdevice.h @@ -1,308 +1,328 @@ /******************************************************************** Copyright 2013 Martin Gräßlin +Copyright 2018 Roman Gilg 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 WAYLAND_OUTPUTDEVICE_H #define WAYLAND_OUTPUTDEVICE_H #include #include #include +#include #include struct org_kde_kwin_outputdevice; class QPoint; class QRect; namespace KWayland { namespace Client { class EventQueue; /** * @short Wrapper for the org_kde_kwin_outputdevice interface. * * This class provides a convenient wrapper for the org_kde_kwin_outputdevice interface. * Its main purpose is to hold the information about one OutputDevice. * * To use this class one needs to interact with the Registry. There are two * possible ways to create an OutputDevice interface: * @code * OutputDevice *c = registry->createOutputDevice(name, version); * @endcode * * This creates the OutputDevice and sets it up directly. As an alternative this * can also be done in a more low level way: * @code * OutputDevice *c = new OutputDevice; * c->setup(registry->bindOutputDevice(name, version)); * @endcode * * The OutputDevice can be used as a drop-in replacement for any org_kde_kwin_outputdevice * pointer as it provides matching cast operators. * * Please note that all properties of OutputDevice are not valid until the * changed signal has been emitted. The wayland server is pushing the * information in an async way to the OutputDevice instance. By emitting changed * the OutputDevice indicates that all relevant information is available. * * @see Registry * @since 5.5 **/ class KWAYLANDCLIENT_EXPORT OutputDevice : public QObject { Q_OBJECT public: enum class SubPixel { Unknown, None, HorizontalRGB, HorizontalBGR, VerticalRGB, VerticalBGR }; enum class Transform { Normal, Rotated90, Rotated180, Rotated270, Flipped, Flipped90, Flipped180, Flipped270 }; enum class Enablement { Disabled = 0, Enabled = 1 }; struct Mode { enum class Flag { None = 0, Current = 1 << 0, Preferred = 1 << 1 }; Q_DECLARE_FLAGS(Flags, Flag) /** * The size of this Mode in pixel space. **/ QSize size; /** * The refresh rate in mHz of this Mode. **/ int refreshRate = 0; /** * The flags of this Mode, that is whether it's the * Current and/or Preferred Mode of the OutputDevice. **/ Flags flags = Flag::None; /** * The OutputDevice to which this Mode belongs. **/ QPointer output; /** * The id of this mode, unique per OutputDevice. This id can be used to call * OutputConfiguration->setMode(); * @see OutputConfiguration::setMode **/ int id; bool operator==(const Mode &m) const; }; + struct ColorCurves { + QVector red, green, blue; + + bool operator==(const ColorCurves &cc) const; + bool operator!=(const ColorCurves &cc) const; + }; explicit OutputDevice(QObject *parent = nullptr); virtual ~OutputDevice(); /** * Setup this Compositor to manage the @p output. * When using Registry::createOutputDevice there is no need to call this * method. **/ void setup(org_kde_kwin_outputdevice *output); /** * @returns @c true if managing a org_kde_kwin_outputdevice. **/ bool isValid() const; operator org_kde_kwin_outputdevice*(); operator org_kde_kwin_outputdevice*() const; org_kde_kwin_outputdevice *output(); /** * Size in millimeters. **/ QSize physicalSize() const; /** * Position within the global compositor space. **/ QPoint globalPosition() const; /** * Textual description of the manufacturer. **/ QString manufacturer() const; /** * Textual description of the model. **/ QString model() const; /** * Size in the current mode. **/ QSize pixelSize() const; /** * The geometry of this OutputDevice in pixels. * Convenient for QRect(globalPosition(), pixelSize()). * @see globalPosition * @see pixelSize **/ QRect geometry() const; /** * Refresh rate in mHz of the current mode. **/ int refreshRate() const; /** * Scaling factor of this output. * * A scale larger than 1 means that the compositor will automatically scale surface buffers * by this amount when rendering. This is used for very high resolution displays where * applications rendering at the native resolution would be too small to be legible. * @deprecated see scaleF **/ int scale() const; /** * Scaling factor of this output. * * A scale larger than 1 means that the compositor will automatically scale surface buffers * by this amount when rendering. This is used for very high resolution displays where * applications rendering at the native resolution would be too small to be legible. * @since 5.50 **/ qreal scaleF() const; /** * Subpixel orientation of this OutputDevice. **/ SubPixel subPixel() const; /** * Transform that maps framebuffer to OutputDevice. * * The purpose is mainly to allow clients render accordingly and tell the compositor, * so that for fullscreen surfaces, the compositor will still be able to scan out * directly from client surfaces. **/ Transform transform() const; + /** + * Color curves. + * @since 5.50 + **/ + ColorCurves colorCurves() const; /** * @returns The Modes of this OutputDevice. **/ QList modes() const; KWayland::Client::OutputDevice::Mode currentMode() const; /** * Sets the @p queue to use for bound proxies. **/ void setEventQueue(EventQueue *queue); /** * @returns The event queue to use for bound proxies. **/ EventQueue *eventQueue() const; /** * @returns The EDID information for this output. **/ QByteArray edid() const; /** * @returns Whether this output is enabled or not. **/ OutputDevice::Enablement enabled() const; /** * @returns A unique identifier for this outputdevice, determined by the server. **/ QByteArray uuid() const; /** * Destroys the data hold by this OutputDevice. * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid any more, it's not * possible to call release any more as that calls into the Wayland * connection and the call would fail. * * This method is automatically invoked when the Registry which created this * Output gets destroyed. **/ void destroy(); Q_SIGNALS: /** * Emitted when the output is fully initialized. **/ void done(); /** * Emitted whenever at least one of the data changed. **/ void changed(); /** * Emitted whenever the enabled property changes. **/ void enabledChanged(OutputDevice::Enablement enabled); /** * Emitted whenever the id property changes. **/ void uuidChanged(const QByteArray &uuid); /** * Emitted whenever a new Mode is added. * This normally only happens during the initial promoting of modes. * Afterwards only modeChanged should be emitted. * @param mode The newly added Mode. * @see modeChanged **/ void modeAdded(const KWayland::Client::OutputDevice::Mode &mode); /** * Emitted whenever a Mode changes. * This normally means that the @c Mode::Flag::Current is added or removed. * @param mode The changed Mode **/ void modeChanged(const KWayland::Client::OutputDevice::Mode &mode); + /** + * Emitted whenever the color curves changed. + * + * @since 5.TODO + **/ + void colorCurvesChanged(); /** * The corresponding global for this interface on the Registry got removed. * * This signal gets only emitted if the OutputDevice got created by * Registry::createOutputDevice * * @since 5.5 **/ void removed(); private: class Private; QScopedPointer d; }; } } Q_DECLARE_METATYPE(KWayland::Client::OutputDevice::SubPixel) Q_DECLARE_METATYPE(KWayland::Client::OutputDevice::Transform) Q_DECLARE_METATYPE(KWayland::Client::OutputDevice::Enablement) Q_DECLARE_METATYPE(KWayland::Client::OutputDevice::Mode) +Q_DECLARE_METATYPE(KWayland::Client::OutputDevice::ColorCurves) Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Client::OutputDevice::Mode::Flags) #endif diff --git a/src/client/protocols/output-management.xml b/src/client/protocols/output-management.xml index 4bb3879..3b3bd70 100644 --- a/src/client/protocols/output-management.xml +++ b/src/client/protocols/output-management.xml @@ -1,181 +1,195 @@ Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]> This interface enables clients to set properties of output devices for screen configuration purposes via the server. To this end output devices are referenced by global org_kde_kwin_outputdevice objects. outputmanagement (wl_global) -------------------------- request: * create_configuration -> outputconfiguration (wl_resource) outputconfiguration (wl_resource) -------------------------- requests: * enable(outputdevice, bool) * mode(outputdevice, mode_id) * transformation(outputdevice, flag) * position(outputdevice, x, y) * apply events: * applied * failed The server registers one outputmanagement object as a global object. In order to configure outputs a client requests create_configuration, which provides a resource referencing an outputconfiguration for one-time configuration. That way the server knows which requests belong together and can group them by that. On the outputconfiguration object the client calls for each output whether the output should be enabled, which mode should be set (by referencing the mode from the list of announced modes) and the output's global position. Once all outputs are configured that way, the client calls apply. At that point and not earlier the server should try to apply the configuration. If this succeeds the server emits the applied signal, otherwise the failed signal, such that the configuring client is noticed about the success of its configuration request. Through this design the interface enables atomic output configuration changes if internally supported by the server. Request an outputconfiguration object through which the client can configure output devices. outputconfiguration is a client-specific resource that can be used to ask the server to apply changes to available output devices. The client receives a list of output devices from the registry. When it wants to apply new settings, it creates a configuration object from the outputmanagement global, writes changes through this object's enable, scale, transform and mode calls. It then asks the server to apply these settings in an atomic fashion, for example through Linux' DRM interface. The server signals back whether the new settings have applied successfully or failed to apply. outputdevice objects are updated after the changes have been applied to the hardware and before the server side sends the applied event. Mark the output as enabled or disabled. Sets the mode for a given output by its mode size (width and height) and refresh rate. Sets the transformation for a given output. Sets the position for this output device. (x,y) describe the top-left corner of the output in global space, whereby the origin (0,0) of the global space has to be aligned with the top-left corner of the most left and in case this does not define a single one the top output. There may be no gaps or overlaps between outputs, i.e. the outputs are stacked horizontally, vertically, or both on each other. Sets the scaling factor for this output device. Asks the server to apply property changes requested through this outputconfiguration object to all outputs on the server side. Sent after the server has successfully applied the changes. . Sent if the server rejects the changes or failed to apply them. Sets the scaling factor for this output device. Sending both scale and scalef is undefined. + + + Set color curves of output devices through RGB color ramps. Allows color + correction of output device from user space. + + These are the raw values. A compositor might opt to adjust these values + internally, for example to shift color temperature at night. + + + + + + + diff --git a/src/client/protocols/outputdevice.xml b/src/client/protocols/outputdevice.xml index 910bcce..48cc6bd 100644 --- a/src/client/protocols/outputdevice.xml +++ b/src/client/protocols/outputdevice.xml @@ -1,264 +1,280 @@ Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]> An outputdevice describes a display device available to the compositor. outputdevice is similar to wl_output, but focuses on output configuration management. A client can query all global outputdevice objects to enlist all available display devices, even those that may currently not be represented by the compositor as a wl_output. The client sends configuration changes to the server through the outputconfiguration interface, and the server applies the configuration changes to the hardware and signals changes to the outputdevices accordingly. This object is published as global during start up for every available display devcies, or when one later becomes available, for example by being hotplugged via a physical connector. This enumeration describes how the physical pixels on an output are laid out. This describes the transform, that a compositor will apply to a surface to compensate for the rotation or mirroring of an output device. The flipped values correspond to an initial flip around a vertical axis followed by rotation. The purpose is mainly to allow clients to render accordingly and tell the compositor, so that for fullscreen surfaces, the compositor is still able to scan out directly client surfaces. The geometry event describes geometric properties of the output. The event is sent when binding to the output object and whenever any of the properties change. These flags describe properties of an output mode. They are used in the flags bitfield of the mode event. The mode event describes an available mode for the output. When the client binds to the outputdevice object, the server sends this event once for every available mode the outputdevice can be operated by. There will always be at least one event sent out on initial binding, which represents the current mode. Later on if an output changes its mode the event is sent again, whereby this event represents the mode that has now become current. In other words, the current mode is always represented by the latest event sent with the current flag set. The size of a mode is given in physical hardware units of the output device. This is not necessarily the same as the output size in the global compositor space. For instance, the output may be scaled, as described in org_kde_kwin_outputdevice.scale, or transformed, as described in org_kde_kwin_outputdevice.transform. The id can be used to refer to a mode when calling set_mode on an org_kde_kwin_outputconfiguration object. This event is sent after all other properties have been sent on binding to the output object as well as after any other output property change have been applied later on. This allows to see changes to the output properties as atomic, even if multiple events successively announce them. This event contains scaling geometry information that is not in the geometry event. It may be sent after binding the output object or if the output scale changes later. If it is not sent, the client should assume a scale of 1. A scale larger than 1 means that the compositor will automatically scale surface buffers by this amount when rendering. This is used for high resolution displays where applications rendering at the native resolution would be too small to be legible. It is intended that scaling aware clients track the current output of a surface, and if it is on a scaled output it should use wl_surface.set_buffer_scale with the scale of the output. That way the compositor can avoid scaling the surface, and the client can supply a higher detail image. The edid event encapsulates the EDID data for the outputdevice. The event is sent when binding to the output object. The EDID data may be empty, in which case this event is sent anyway. If the EDID information is empty, you can fall back to the name et al. properties of the outputdevice. Describes whether a device is enabled, i.e. device is used to display content by the compositor. This wraps a boolean around an int to avoid a boolean trap. The enabled event notifies whether this output is currently enabled and used for displaying content by the server. The event is sent when binding to the output object and whenever later on an output changes its state by becoming enabled or disabled. The uuid can be used to identify the output. It's controlled by the server entirely. The server should make sure the uuid is persistent across restarts. An empty uuid is considered invalid. This event contains scaling geometry information that is not in the geometry event. It may be sent after binding the output object or if the output scale changes later. If it is not sent, the client should assume a scale of 1. A scale larger than 1 means that the compositor will automatically scale surface buffers by this amount when rendering. This is used for high resolution displays where applications rendering at the native resolution would be too small to be legible. It is intended that scaling aware clients track the current output of a surface, and if it is on a scaled output it should use wl_surface.set_buffer_scale with the scale of the output. That way the compositor can avoid scaling the surface, and the client can supply a higher detail image. wl_output will keep the output scale as an integer. In every situation except configuring the window manager you want to use that. + + + Decribes the color intensity profile of the output. + Commonly used for gamma/color correction. + The array contains all color ramp values of the output. + For example on 8bit screens there are 256 of them. + + The array elements are unsigned 16bit integers. + + + + + diff --git a/src/server/outputchangeset.cpp b/src/server/outputchangeset.cpp index 2393dbb..1cdba96 100644 --- a/src/server/outputchangeset.cpp +++ b/src/server/outputchangeset.cpp @@ -1,121 +1,134 @@ /**************************************************************************** * Copyright 2015 Sebastian Kügler * * 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 "outputchangeset.h" #include "outputchangeset_p.h" namespace KWayland { namespace Server { OutputChangeSet::Private::Private(OutputDeviceInterface *outputdevice, OutputChangeSet *parent) : q(parent) , o(outputdevice) , enabled(o->enabled()) , modeId(o->currentModeId()) , transform(o->transform()) , position(o->globalPosition()) , scale(o->scale()) + , colorCurves(o->colorCurves()) { } OutputChangeSet::Private::~Private() = default; OutputChangeSet::OutputChangeSet(OutputDeviceInterface *outputdevice, QObject *parent) : QObject(parent) , d(new Private(outputdevice, this)) { } OutputChangeSet::~OutputChangeSet() = default; OutputChangeSet::Private *OutputChangeSet::d_func() const { return reinterpret_cast(d.data()); } bool OutputChangeSet::enabledChanged() const { Q_D(); return d->enabled != d->o->enabled(); } OutputDeviceInterface::Enablement OutputChangeSet::enabled() const { Q_D(); return d->enabled; } bool OutputChangeSet::modeChanged() const { Q_D(); return d->modeId != d->o->currentModeId(); } int OutputChangeSet::mode() const { Q_D(); return d->modeId; } bool OutputChangeSet::transformChanged() const { Q_D(); return d->transform != d->o->transform(); } OutputDeviceInterface::Transform OutputChangeSet::transform() const { Q_D(); return d->transform; } bool OutputChangeSet::positionChanged() const { Q_D(); return d->position != d->o->globalPosition(); } QPoint OutputChangeSet::position() const { Q_D(); return d->position; } bool OutputChangeSet::scaleChanged() const { Q_D(); return !qFuzzyCompare(d->scale, d->o->scaleF()); } int OutputChangeSet::scale() const { Q_D(); return qRound(d->scale); } qreal OutputChangeSet::scaleF() const { Q_D(); return d->scale; } +bool OutputChangeSet::colorCurvesChanged() const +{ + Q_D(); + return d->colorCurves != d->o->colorCurves(); +} + +OutputDeviceInterface::ColorCurves OutputChangeSet::colorCurves() const +{ + Q_D(); + return d->colorCurves; +} + } } diff --git a/src/server/outputchangeset.h b/src/server/outputchangeset.h index 62385b2..0555f55 100644 --- a/src/server/outputchangeset.h +++ b/src/server/outputchangeset.h @@ -1,103 +1,111 @@ /**************************************************************************** * Copyright 2015 Sebastian Kügler * * 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 WAYLAND_SERVER_CHANGESET_H #define WAYLAND_SERVER_CHANGESET_H #include #include "outputdevice_interface.h" #include namespace KWayland { namespace Server { /** * @brief Holds a set of changes to an OutputInterface or OutputDeviceInterface. * * This class implements a set of changes that the compositor can apply to an * OutputInterface after OutputConfiguration::apply has been called on the client * side. The changes are per-configuration. * * @see OutputConfiguration * @since 5.5 **/ class KWAYLANDSERVER_EXPORT OutputChangeSet : public QObject { Q_OBJECT public: virtual ~OutputChangeSet(); /** Whether the enabled() property of the outputdevice changed. * @returns @c true if the enabled property of the outputdevice has changed. */ bool enabledChanged() const; /** Whether the currentModeId() property of the outputdevice changed. * @returns @c true if the enabled property of the outputdevice has changed. * bool modeChanged() const; */ /** Whether the transform() property of the outputdevice changed. */ bool transformChanged() const; /** Whether the currentModeId() property of the outputdevice changed. * @returns @c true if the currentModeId() property of the outputdevice has changed. */ bool modeChanged() const; /** Whether the globalPosition() property of the outputdevice changed. * @returns @c true if the globalPosition() property of the outputdevice has changed. */ bool positionChanged() const; /** Whether the scale() property of the outputdevice changed. * @returns @c true if the scale() property of the outputdevice has changed. */ bool scaleChanged() const; + /** Whether the colorCurves() property of the outputdevice changed. + * @returns @c true if the colorCurves() property of the outputdevice has changed. + */ + bool colorCurvesChanged() const; /** The new value for enabled. */ OutputDeviceInterface::Enablement enabled() const; /** The new mode id.*/ int mode() const; /** The new value for transform. */ OutputDeviceInterface::Transform transform() const; /** The new value for globalPosition. */ QPoint position() const; /** The new value for scale. @deprecated see scaleF */ int scale() const; /** The new value for scale. * @since 5.XX */ qreal scaleF() const; + /** The new value for colorCurves. + * @since 5.XX + */ + OutputDeviceInterface::ColorCurves colorCurves() const; private: friend class OutputConfigurationInterface; explicit OutputChangeSet(OutputDeviceInterface *outputdevice, QObject *parent = nullptr); class Private; QScopedPointer d; Private *d_func() const; }; } } #endif diff --git a/src/server/outputchangeset_p.h b/src/server/outputchangeset_p.h index 4b555c8..c2bdf8c 100644 --- a/src/server/outputchangeset_p.h +++ b/src/server/outputchangeset_p.h @@ -1,48 +1,49 @@ /**************************************************************************** * Copyright 2015 Sebastian Kügler * * 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_OUTPUTCHANGESET_P_H #define KWAYLAND_SERVER_OUTPUTCHANGESET_P_H #include "outputchangeset.h" namespace KWayland { namespace Server { class OutputChangeSet::Private { public: Private(OutputDeviceInterface *outputdevice, OutputChangeSet *parent); ~Private(); OutputChangeSet *q; OutputDeviceInterface *o; OutputDeviceInterface::Enablement enabled; int modeId; OutputDeviceInterface::Transform transform; QPoint position; qreal scale; + OutputDeviceInterface::ColorCurves colorCurves; }; } } #endif diff --git a/src/server/outputconfiguration_interface.cpp b/src/server/outputconfiguration_interface.cpp index d79cd64..7d62c7f 100644 --- a/src/server/outputconfiguration_interface.cpp +++ b/src/server/outputconfiguration_interface.cpp @@ -1,294 +1,334 @@ /******************************************************************** Copyright 2014 Martin Gräßlin Copyright 2015 Sebastian Kügler 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 "outputconfiguration_interface.h" #include "outputdevice_interface.h" #include "logging_p.h" #include "resource_p.h" #include "display.h" #include "outputchangeset_p.h" #include #include "wayland-output-management-server-protocol.h" #include "wayland-org_kde_kwin_outputdevice-server-protocol.h" #include #include namespace KWayland { namespace Server { class OutputConfigurationInterface::Private : public Resource::Private { public: Private(OutputConfigurationInterface *q, OutputManagementInterface *c, wl_resource *parentResource); ~Private(); void sendApplied(); void sendFailed(); void emitConfigurationChangeRequested() const; void clearPendingChanges(); bool hasPendingChanges(OutputDeviceInterface *outputdevice) const; OutputChangeSet* pendingChanges(OutputDeviceInterface *outputdevice); OutputManagementInterface *outputManagement; QHash changes; static const quint32 s_version = 2; private: static void enableCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t enable); static void modeCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t mode_id); static void transformCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t transform); static void positionCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t x, int32_t y); static void scaleCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t scale); static void applyCallback(wl_client *client, wl_resource *resource); static void scaleFCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, wl_fixed_t scale); + static void colorcurvesCallback(wl_client *client, wl_resource *resource, + wl_resource * outputdevice, + wl_array *red, wl_array *green, wl_array *blue); OutputConfigurationInterface *q_func() { return reinterpret_cast(q); } static const struct org_kde_kwin_outputconfiguration_interface s_interface; }; const struct org_kde_kwin_outputconfiguration_interface OutputConfigurationInterface::Private::s_interface = { enableCallback, modeCallback, transformCallback, positionCallback, scaleCallback, applyCallback, scaleFCallback, + colorcurvesCallback, resourceDestroyedCallback }; OutputConfigurationInterface::OutputConfigurationInterface(OutputManagementInterface* parent, wl_resource* parentResource): Resource(new Private(this, parent, parentResource)) { Q_D(); d->outputManagement = parent; } OutputConfigurationInterface::~OutputConfigurationInterface() { Q_D(); d->clearPendingChanges(); } void OutputConfigurationInterface::Private::enableCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t enable) { Q_UNUSED(client); auto s = cast(resource); Q_ASSERT(s); auto _enable = (enable == ORG_KDE_KWIN_OUTPUTDEVICE_ENABLEMENT_ENABLED) ? OutputDeviceInterface::Enablement::Enabled : OutputDeviceInterface::Enablement::Disabled; OutputDeviceInterface *o = OutputDeviceInterface::get(outputdevice); s->pendingChanges(o)->d_func()->enabled = _enable; } void OutputConfigurationInterface::Private::modeCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t mode_id) { Q_UNUSED(client); OutputDeviceInterface *o = OutputDeviceInterface::get(outputdevice); bool modeValid = false; for (const auto &m: o->modes()) { if (m.id == mode_id) { modeValid = true; break; } } if (!modeValid) { qCWarning(KWAYLAND_SERVER) << "Set invalid mode id:" << mode_id; return; } auto s = cast(resource); Q_ASSERT(s); s->pendingChanges(o)->d_func()->modeId = mode_id; } void OutputConfigurationInterface::Private::transformCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t transform) { Q_UNUSED(client); auto toTransform = [transform]() { switch (transform) { case WL_OUTPUT_TRANSFORM_90: return OutputDeviceInterface::Transform::Rotated90; case WL_OUTPUT_TRANSFORM_180: return OutputDeviceInterface::Transform::Rotated180; case WL_OUTPUT_TRANSFORM_270: return OutputDeviceInterface::Transform::Rotated270; case WL_OUTPUT_TRANSFORM_FLIPPED: return OutputDeviceInterface::Transform::Flipped; case WL_OUTPUT_TRANSFORM_FLIPPED_90: return OutputDeviceInterface::Transform::Flipped90; case WL_OUTPUT_TRANSFORM_FLIPPED_180: return OutputDeviceInterface::Transform::Flipped180; case WL_OUTPUT_TRANSFORM_FLIPPED_270: return OutputDeviceInterface::Transform::Flipped270; case WL_OUTPUT_TRANSFORM_NORMAL: default: return OutputDeviceInterface::Transform::Normal; } }; auto _transform = toTransform(); OutputDeviceInterface *o = OutputDeviceInterface::get(outputdevice); auto s = cast(resource); Q_ASSERT(s); s->pendingChanges(o)->d_func()->transform = _transform; } void OutputConfigurationInterface::Private::positionCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t x, int32_t y) { Q_UNUSED(client); auto _pos = QPoint(x, y); OutputDeviceInterface *o = OutputDeviceInterface::get(outputdevice); auto s = cast(resource); Q_ASSERT(s); s->pendingChanges(o)->d_func()->position = _pos; } void OutputConfigurationInterface::Private::scaleCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, int32_t scale) { Q_UNUSED(client); if (scale <= 0) { qCWarning(KWAYLAND_SERVER) << "Requested to scale output device to" << scale << ", but I can't do that."; return; } OutputDeviceInterface *o = OutputDeviceInterface::get(outputdevice); auto s = cast(resource); Q_ASSERT(s); s->pendingChanges(o)->d_func()->scale = scale; } void OutputConfigurationInterface::Private::scaleFCallback(wl_client *client, wl_resource *resource, wl_resource * outputdevice, wl_fixed_t scale_fixed) { Q_UNUSED(client); const qreal scale = wl_fixed_to_double(scale_fixed); if (scale <= 0) { qCWarning(KWAYLAND_SERVER) << "Requested to scale output device to" << scale << ", but I can't do that."; return; } OutputDeviceInterface *o = OutputDeviceInterface::get(outputdevice); auto s = cast(resource); Q_ASSERT(s); s->pendingChanges(o)->d_func()->scale = scale; } void OutputConfigurationInterface::Private::applyCallback(wl_client *client, wl_resource *resource) { Q_UNUSED(client); auto s = cast(resource); Q_ASSERT(s); s->emitConfigurationChangeRequested(); } +void OutputConfigurationInterface::Private::colorcurvesCallback(wl_client *client, wl_resource *resource, + wl_resource * outputdevice, + wl_array *red, wl_array *green, wl_array *blue) +{ + Q_UNUSED(client); + OutputDeviceInterface *o = OutputDeviceInterface::get(outputdevice); + OutputDeviceInterface::ColorCurves oldCc = o->colorCurves(); + + auto checkArg = [](const wl_array *newColor, const QVector &oldColor) { + return (newColor->size % sizeof(uint16_t) == 0) && + (newColor->size / sizeof(uint16_t) == static_cast(oldColor.size())); + }; + if (!checkArg(red, oldCc.red) || !checkArg(green, oldCc.green) || !checkArg(blue, oldCc.blue)) { + qCWarning(KWAYLAND_SERVER) << "Requested to change color curves, but have wrong size."; + return; + } + + auto s = cast(resource); + Q_ASSERT(s); + OutputDeviceInterface::ColorCurves cc; + + auto fillVector = [](const wl_array *array, QVector *v) { + const uint16_t *pos = (uint16_t*)array->data; + + while((char*)pos < (char*)array->data + array->size) { + v->append(*pos); + pos++; + } + }; + fillVector(red, &cc.red); + fillVector(green, &cc.green); + fillVector(blue, &cc.blue); + + s->pendingChanges(o)->d_func()->colorCurves = cc; +} + void OutputConfigurationInterface::Private::emitConfigurationChangeRequested() const { auto configinterface = reinterpret_cast(q); emit outputManagement->configurationChangeRequested(configinterface); } OutputConfigurationInterface::Private::Private(OutputConfigurationInterface *q, OutputManagementInterface *c, wl_resource *parentResource) : Resource::Private(q, c, parentResource, &org_kde_kwin_outputconfiguration_interface, &s_interface) { } OutputConfigurationInterface::Private::~Private() = default; OutputConfigurationInterface::Private *OutputConfigurationInterface::d_func() const { return reinterpret_cast(d.data()); } QHash OutputConfigurationInterface::changes() const { Q_D(); return d->changes; } void OutputConfigurationInterface::setApplied() { Q_D(); d->clearPendingChanges(); d->sendApplied(); } void OutputConfigurationInterface::Private::sendApplied() { org_kde_kwin_outputconfiguration_send_applied(resource); } void OutputConfigurationInterface::setFailed() { Q_D(); d->clearPendingChanges(); d->sendFailed(); } void OutputConfigurationInterface::Private::sendFailed() { org_kde_kwin_outputconfiguration_send_failed(resource); } OutputChangeSet* OutputConfigurationInterface::Private::pendingChanges(OutputDeviceInterface *outputdevice) { if (!changes.keys().contains(outputdevice)) { changes[outputdevice] = new OutputChangeSet(outputdevice, q); } return changes[outputdevice]; } bool OutputConfigurationInterface::Private::hasPendingChanges(OutputDeviceInterface *outputdevice) const { if (!changes.keys().contains(outputdevice)) { return false; } auto c = changes[outputdevice]; return c->enabledChanged() || c->modeChanged() || c->transformChanged() || c->positionChanged() || c->scaleChanged(); } void OutputConfigurationInterface::Private::clearPendingChanges() { qDeleteAll(changes.begin(), changes.end()); changes.clear(); } } } diff --git a/src/server/outputdevice_interface.cpp b/src/server/outputdevice_interface.cpp index c34da1c..c272498 100644 --- a/src/server/outputdevice_interface.cpp +++ b/src/server/outputdevice_interface.cpp @@ -1,620 +1,684 @@ /******************************************************************** Copyright 2014 Martin Gräßlin 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 "outputdevice_interface.h" #include "global_p.h" #include "display.h" #include "logging_p.h" #include #include "wayland-org_kde_kwin_outputdevice-server-protocol.h" #include namespace KWayland { namespace Server { class OutputDeviceInterface::Private : public Global::Private { public: struct ResourceData { wl_resource *resource; uint32_t version; }; Private(OutputDeviceInterface *q, Display *d); ~Private(); void sendMode(wl_resource *resource, const Mode &mode); void sendDone(const ResourceData &data); void updateGeometry(); void updateScale(); + void updateColorCurves(); void sendUuid(); void sendEdid(); void sendEnabled(); QSize physicalSize; QPoint globalPosition; QString manufacturer = QStringLiteral("org.kde.kwin"); QString model = QStringLiteral("none"); qreal scale = 1.0; SubPixel subPixel = SubPixel::Unknown; Transform transform = Transform::Normal; + ColorCurves colorCurves; QList modes; QList resources; QByteArray edid; Enablement enabled = Enablement::Enabled; QByteArray uuid; static OutputDeviceInterface *get(wl_resource *native); private: static Private *cast(wl_resource *native); static void unbind(wl_resource *resource); void bind(wl_client *client, uint32_t version, uint32_t id) override; int32_t toTransform() const; int32_t toSubPixel() const; void sendGeometry(wl_resource *resource); void sendScale(const ResourceData &data); + void sendColorCurves(const ResourceData &data); static const quint32 s_version; OutputDeviceInterface *q; static QVector s_privates; }; const quint32 OutputDeviceInterface::Private::s_version = 2; QVector OutputDeviceInterface::Private::s_privates; OutputDeviceInterface::Private::Private(OutputDeviceInterface *q, Display *d) : Global::Private(d, &org_kde_kwin_outputdevice_interface, s_version) , q(q) { s_privates << this; } OutputDeviceInterface::Private::~Private() { s_privates.removeAll(this); } OutputDeviceInterface *OutputDeviceInterface::Private::get(wl_resource *native) { if (Private *p = cast(native)) { return p->q; } return nullptr; } OutputDeviceInterface::Private *OutputDeviceInterface::Private::cast(wl_resource *native) { for (auto it = s_privates.constBegin(); it != s_privates.constEnd(); ++it) { const auto &resources = (*it)->resources; auto rit = std::find_if(resources.begin(), resources.end(), [native] (const ResourceData &data) { return data.resource == native; }); if (rit != resources.end()) { return (*it); } } return nullptr; } OutputDeviceInterface::OutputDeviceInterface(Display *display, QObject *parent) : Global(new Private(this, display), parent) { Q_D(); connect(this, &OutputDeviceInterface::currentModeChanged, this, [this, d] { auto currentModeIt = std::find_if(d->modes.constBegin(), d->modes.constEnd(), [](const Mode &mode) { return mode.flags.testFlag(ModeFlag::Current); }); if (currentModeIt == d->modes.constEnd()) { return; } for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { d->sendMode((*it).resource, *currentModeIt); d->sendDone(*it); } wl_display_flush_clients(*(d->display)); } ); connect(this, &OutputDeviceInterface::subPixelChanged, this, [this, d] { d->updateGeometry(); }); connect(this, &OutputDeviceInterface::transformChanged, this, [this, d] { d->updateGeometry(); }); connect(this, &OutputDeviceInterface::globalPositionChanged, this, [this, d] { d->updateGeometry(); }); connect(this, &OutputDeviceInterface::modelChanged, this, [this, d] { d->updateGeometry(); }); connect(this, &OutputDeviceInterface::manufacturerChanged, this, [this, d] { d->updateGeometry(); }); connect(this, &OutputDeviceInterface::scaleFChanged, this, [this, d] { d->updateScale(); }); + connect(this, &OutputDeviceInterface::scaleChanged, this, [this, d] { d->updateScale(); }); + connect(this, &OutputDeviceInterface::colorCurvesChanged, this, [this, d] { d->updateColorCurves(); }); } OutputDeviceInterface::~OutputDeviceInterface() = default; QSize OutputDeviceInterface::pixelSize() const { Q_D(); auto it = std::find_if(d->modes.begin(), d->modes.end(), [](const Mode &mode) { return mode.flags.testFlag(ModeFlag::Current); } ); if (it == d->modes.end()) { return QSize(); } return (*it).size; } OutputDeviceInterface *OutputDeviceInterface::get(wl_resource* native) { return Private::get(native); } int OutputDeviceInterface::refreshRate() const { Q_D(); auto it = std::find_if(d->modes.begin(), d->modes.end(), [](const Mode &mode) { return mode.flags.testFlag(ModeFlag::Current); } ); if (it == d->modes.end()) { return 60000; } return (*it).refreshRate; } void OutputDeviceInterface::addMode(Mode &mode) { Q_ASSERT(!isValid()); Q_D(); auto currentModeIt = std::find_if(d->modes.begin(), d->modes.end(), [](const Mode &mode) { return mode.flags.testFlag(ModeFlag::Current); } ); if (currentModeIt == d->modes.end() && !mode.flags.testFlag(ModeFlag::Current)) { // no mode with current flag - enforce mode.flags |= ModeFlag::Current; } if (currentModeIt != d->modes.end() && mode.flags.testFlag(ModeFlag::Current)) { // another mode has the current flag - remove (*currentModeIt).flags &= ~uint(ModeFlag::Current); } if (mode.flags.testFlag(ModeFlag::Preferred)) { // remove from existing Preferred mode auto preferredIt = std::find_if(d->modes.begin(), d->modes.end(), [](const Mode &mode) { return mode.flags.testFlag(ModeFlag::Preferred); } ); if (preferredIt != d->modes.end()) { (*preferredIt).flags &= ~uint(ModeFlag::Preferred); } } auto existingModeIt = std::find_if(d->modes.begin(), d->modes.end(), [mode](const Mode &mode_it) { return mode.size == mode_it.size && mode.refreshRate == mode_it.refreshRate && mode.id == mode_it.id; } ); auto emitChanges = [this,mode] { emit modesChanged(); if (mode.flags.testFlag(ModeFlag::Current)) { emit refreshRateChanged(mode.refreshRate); emit pixelSizeChanged(mode.size); emit currentModeChanged(); } }; if (existingModeIt != d->modes.end()) { if ((*existingModeIt).flags == mode.flags) { // nothing to do return; } (*existingModeIt).flags = mode.flags; emitChanges(); return; } else { auto idIt = std::find_if(d->modes.begin(), d->modes.end(), [mode](const Mode &mode_it) { return mode.id == mode_it.id; } ); if (idIt != d->modes.end()) { qCWarning(KWAYLAND_SERVER) << "Duplicate Mode id" << mode.id << ": not adding mode" << mode.size << mode.refreshRate; return; } } d->modes << mode; emitChanges(); } void OutputDeviceInterface::setCurrentMode(const int modeId) { Q_D(); auto currentModeIt = std::find_if(d->modes.begin(), d->modes.end(), [](const Mode &mode) { return mode.flags.testFlag(ModeFlag::Current); } ); if (currentModeIt != d->modes.end()) { // another mode has the current flag - remove (*currentModeIt).flags &= ~uint(ModeFlag::Current); } auto existingModeIt = std::find_if(d->modes.begin(), d->modes.end(), [modeId](const Mode &mode) { return mode.id == modeId; } ); Q_ASSERT(existingModeIt != d->modes.end()); (*existingModeIt).flags |= ModeFlag::Current; emit modesChanged(); emit refreshRateChanged((*existingModeIt).refreshRate); emit pixelSizeChanged((*existingModeIt).size); emit currentModeChanged(); } int32_t OutputDeviceInterface::Private::toTransform() const { switch (transform) { case Transform::Normal: return WL_OUTPUT_TRANSFORM_NORMAL; case Transform::Rotated90: return WL_OUTPUT_TRANSFORM_90; case Transform::Rotated180: return WL_OUTPUT_TRANSFORM_180; case Transform::Rotated270: return WL_OUTPUT_TRANSFORM_270; case Transform::Flipped: return WL_OUTPUT_TRANSFORM_FLIPPED; case Transform::Flipped90: return WL_OUTPUT_TRANSFORM_FLIPPED_90; case Transform::Flipped180: return WL_OUTPUT_TRANSFORM_FLIPPED_180; case Transform::Flipped270: return WL_OUTPUT_TRANSFORM_FLIPPED_270; } abort(); } int32_t OutputDeviceInterface::Private::toSubPixel() const { switch (subPixel) { case SubPixel::Unknown: return WL_OUTPUT_SUBPIXEL_UNKNOWN; case SubPixel::None: return WL_OUTPUT_SUBPIXEL_NONE; case SubPixel::HorizontalRGB: return WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB; case SubPixel::HorizontalBGR: return WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR; case SubPixel::VerticalRGB: return WL_OUTPUT_SUBPIXEL_VERTICAL_RGB; case SubPixel::VerticalBGR: return WL_OUTPUT_SUBPIXEL_VERTICAL_BGR; } abort(); } void OutputDeviceInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) { auto c = display->getConnection(client); wl_resource *resource = c->createResource(&org_kde_kwin_outputdevice_interface, qMin(version, s_version), id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_user_data(resource, this); wl_resource_set_destructor(resource, unbind); ResourceData r; r.resource = resource; r.version = version; resources << r; sendGeometry(resource); sendScale(r); + sendColorCurves(r); auto currentModeIt = modes.constEnd(); for (auto it = modes.constBegin(); it != modes.constEnd(); ++it) { const Mode &mode = *it; if (mode.flags.testFlag(ModeFlag::Current)) { // needs to be sent as last mode currentModeIt = it; continue; } sendMode(resource, mode); } if (currentModeIt != modes.constEnd()) { sendMode(resource, *currentModeIt); } sendUuid(); sendEdid(); sendEnabled(); sendDone(r); c->flush(); } void OutputDeviceInterface::Private::unbind(wl_resource *resource) { Private *o = cast(resource); if (!o) { return; } auto it = std::find_if(o->resources.begin(), o->resources.end(), [resource](const ResourceData &r) { return r.resource == resource; }); if (it != o->resources.end()) { o->resources.erase(it); } } void OutputDeviceInterface::Private::sendMode(wl_resource *resource, const Mode &mode) { int32_t flags = 0; if (mode.flags.testFlag(ModeFlag::Current)) { flags |= WL_OUTPUT_MODE_CURRENT; } if (mode.flags.testFlag(ModeFlag::Preferred)) { flags |= WL_OUTPUT_MODE_PREFERRED; } org_kde_kwin_outputdevice_send_mode(resource, flags, mode.size.width(), mode.size.height(), mode.refreshRate, mode.id); } void OutputDeviceInterface::Private::sendGeometry(wl_resource *resource) { org_kde_kwin_outputdevice_send_geometry(resource, globalPosition.x(), globalPosition.y(), physicalSize.width(), physicalSize.height(), toSubPixel(), qPrintable(manufacturer), qPrintable(model), toTransform()); } void OutputDeviceInterface::Private::sendScale(const ResourceData &data) { if (wl_resource_get_version(data.resource) < ORG_KDE_KWIN_OUTPUTDEVICE_SCALEF_SINCE_VERSION) { org_kde_kwin_outputdevice_send_scale(data.resource, qRound(scale)); } else { org_kde_kwin_outputdevice_send_scalef(data.resource, wl_fixed_from_double(scale)); } } +void OutputDeviceInterface::Private::sendColorCurves(const ResourceData &data) +{ + if (data.version < ORG_KDE_KWIN_OUTPUTDEVICE_COLORCURVES_SINCE_VERSION) { + return; + } + + wl_array wlRed, wlGreen, wlBlue; + + auto fillArray = [](const QVector &origin, wl_array *dest) { + wl_array_init(dest); + const size_t memLength = sizeof(uint16_t) * origin.size(); + void *s = wl_array_add(dest, memLength); + memcpy(s, origin.data(), memLength); + }; + fillArray(colorCurves.red, &wlRed); + fillArray(colorCurves.green, &wlGreen); + fillArray(colorCurves.blue, &wlBlue); + + org_kde_kwin_outputdevice_send_colorcurves(data.resource, &wlRed, &wlGreen, &wlBlue); + + wl_array_release(&wlRed); + wl_array_release(&wlGreen); + wl_array_release(&wlBlue); +} + void OutputDeviceInterface::Private::sendDone(const ResourceData &data) { org_kde_kwin_outputdevice_send_done(data.resource); } void OutputDeviceInterface::Private::updateGeometry() { for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { sendGeometry((*it).resource); sendDone(*it); } } void OutputDeviceInterface::Private::updateScale() { for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { sendScale(*it); sendDone(*it); } } +void OutputDeviceInterface::Private::updateColorCurves() +{ + for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { + sendColorCurves(*it); + sendDone(*it); + } +} + +bool OutputDeviceInterface::ColorCurves::operator==(const ColorCurves &cc) const +{ + return red == cc.red && green == cc.green && blue == cc.blue; +} +bool OutputDeviceInterface::ColorCurves::operator!=(const ColorCurves &cc) const { + return !operator==(cc); +} + #define SETTER(setterName, type, argumentName) \ void OutputDeviceInterface::setterName(type arg) \ { \ Q_D(); \ if (d->argumentName == arg) { \ return; \ } \ d->argumentName = arg; \ emit argumentName##Changed(d->argumentName); \ } SETTER(setPhysicalSize, const QSize&, physicalSize) SETTER(setGlobalPosition, const QPoint&, globalPosition) SETTER(setManufacturer, const QString&, manufacturer) SETTER(setModel, const QString&, model) SETTER(setSubPixel, SubPixel, subPixel) SETTER(setTransform, Transform, transform) #undef SETTER void OutputDeviceInterface::setScale(int scale) { Q_D(); if (d->scale == scale) { return; } d->scale = scale; emit scaleChanged(d->scale); emit scaleFChanged(d->scale); } void OutputDeviceInterface::setScaleF(qreal scale) { Q_D(); if (qFuzzyCompare(d->scale, scale)) { return; } d->scale = scale; emit scaleChanged(qRound(d->scale)); emit scaleFChanged(d->scale); } QSize OutputDeviceInterface::physicalSize() const { Q_D(); return d->physicalSize; } QPoint OutputDeviceInterface::globalPosition() const { Q_D(); return d->globalPosition; } QString OutputDeviceInterface::manufacturer() const { Q_D(); return d->manufacturer; } QString OutputDeviceInterface::model() const { Q_D(); return d->model; } int OutputDeviceInterface::scale() const { Q_D(); return qRound(d->scale); } qreal OutputDeviceInterface::scaleF() const { Q_D(); return d->scale; } OutputDeviceInterface::SubPixel OutputDeviceInterface::subPixel() const { Q_D(); return d->subPixel; } OutputDeviceInterface::Transform OutputDeviceInterface::transform() const { Q_D(); return d->transform; } +OutputDeviceInterface::ColorCurves OutputDeviceInterface::colorCurves() const +{ + Q_D(); + return d->colorCurves; +} + QList< OutputDeviceInterface::Mode > OutputDeviceInterface::modes() const { Q_D(); return d->modes; } int OutputDeviceInterface::currentModeId() const { Q_D(); for (const Mode &m: d->modes) { if (m.flags.testFlag(OutputDeviceInterface::ModeFlag::Current)) { return m.id; } } return -1; } OutputDeviceInterface::Private *OutputDeviceInterface::d_func() const { return reinterpret_cast(d.data()); } +void OutputDeviceInterface::setColorCurves(const ColorCurves &colorCurves) +{ + Q_D(); + + if (d->colorCurves == colorCurves) { + return; + } + d->colorCurves = colorCurves; + emit colorCurvesChanged(d->colorCurves); +} + void OutputDeviceInterface::setEdid(const QByteArray &edid) { Q_D(); d->edid = edid; d->sendEdid(); emit edidChanged(); } QByteArray OutputDeviceInterface::edid() const { Q_D(); return d->edid; } void OutputDeviceInterface::setEnabled(OutputDeviceInterface::Enablement enabled) { Q_D(); if (d->enabled != enabled) { d->enabled = enabled; d->sendEnabled(); emit enabledChanged(); } } OutputDeviceInterface::Enablement OutputDeviceInterface::enabled() const { Q_D(); return d->enabled; } void OutputDeviceInterface::setUuid(const QByteArray &uuid) { Q_D(); if (d->uuid != uuid) { d->uuid = uuid; d->sendUuid(); emit uuidChanged(); } } QByteArray OutputDeviceInterface::uuid() const { Q_D(); return d->uuid; } void KWayland::Server::OutputDeviceInterface::Private::sendEdid() { for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { org_kde_kwin_outputdevice_send_edid((*it).resource, edid.toBase64().constData()); } } void KWayland::Server::OutputDeviceInterface::Private::sendEnabled() { int _enabled = 0; if (enabled == OutputDeviceInterface::Enablement::Enabled) { _enabled = 1; } for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { org_kde_kwin_outputdevice_send_enabled((*it).resource, _enabled); } } void OutputDeviceInterface::Private::sendUuid() { for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { org_kde_kwin_outputdevice_send_uuid((*it).resource, uuid.constData()); } } } } diff --git a/src/server/outputdevice_interface.h b/src/server/outputdevice_interface.h index 1b7b25d..1fe35a4 100644 --- a/src/server/outputdevice_interface.h +++ b/src/server/outputdevice_interface.h @@ -1,165 +1,175 @@ /******************************************************************** Copyright 2014 Martin Gräßlin 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 WAYLAND_SERVER_OUTPUTDEVICE_INTERFACE_H #define WAYLAND_SERVER_OUTPUTDEVICE_INTERFACE_H #include #include #include +#include #include #include "global.h" struct wl_resource; namespace KWayland { namespace Server { class Display; /** @class OutputDeviceInterface * * Represents an output device, the difference to Output is that this output can be disabled, * so not currently used to display content. * * @see OutputManagementInterface * @since 5.5 */ class KWAYLANDSERVER_EXPORT OutputDeviceInterface : public Global { Q_OBJECT Q_PROPERTY(QSize physicalSize READ physicalSize WRITE setPhysicalSize NOTIFY physicalSizeChanged) Q_PROPERTY(QPoint globalPosition READ globalPosition WRITE setGlobalPosition NOTIFY globalPositionChanged) Q_PROPERTY(QString manufacturer READ manufacturer WRITE setManufacturer NOTIFY manufacturerChanged) Q_PROPERTY(QString model READ model WRITE setModel NOTIFY modelChanged) Q_PROPERTY(QSize pixelSize READ pixelSize NOTIFY pixelSizeChanged) Q_PROPERTY(int refreshRate READ refreshRate NOTIFY refreshRateChanged) Q_PROPERTY(qreal scale READ scaleF WRITE setScaleF NOTIFY scaleFChanged) Q_PROPERTY(QByteArray edid READ edid WRITE setEdid NOTIFY edidChanged) Q_PROPERTY(OutputDeviceInterface::Enablement enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(QByteArray uuid READ uuid WRITE setUuid NOTIFY uuidChanged) public: enum class SubPixel { Unknown, None, HorizontalRGB, HorizontalBGR, VerticalRGB, VerticalBGR }; enum class Transform { Normal, Rotated90, Rotated180, Rotated270, Flipped, Flipped90, Flipped180, Flipped270 }; enum class Enablement { Disabled = 0, Enabled = 1 }; enum class ModeFlag { Current = 1, Preferred = 2 }; Q_DECLARE_FLAGS(ModeFlags, ModeFlag) struct Mode { QSize size = QSize(); int refreshRate = 60000; ModeFlags flags; int id = -1; }; + struct ColorCurves { + QVector red, green, blue; + bool operator==(const ColorCurves &cc) const; + bool operator!=(const ColorCurves &cc) const; + }; virtual ~OutputDeviceInterface(); QSize physicalSize() const; QPoint globalPosition() const; QString manufacturer() const; QString model() const; QSize pixelSize() const; int refreshRate() const; int scale() const; qreal scaleF() const; SubPixel subPixel() const; Transform transform() const; + ColorCurves colorCurves() const; QList modes() const; int currentModeId() const; QByteArray edid() const; OutputDeviceInterface::Enablement enabled() const; QByteArray uuid() const; void setPhysicalSize(const QSize &size); void setGlobalPosition(const QPoint &pos); void setManufacturer(const QString &manufacturer); void setModel(const QString &model); void setScale(int scale); void setScaleF(qreal scale); void setSubPixel(SubPixel subPixel); void setTransform(Transform transform); + void setColorCurves(const ColorCurves &colorCurves); void addMode(Mode &mode); void setCurrentMode(const int modeId); void setEdid(const QByteArray &edid); void setEnabled(OutputDeviceInterface::Enablement enabled); void setUuid(const QByteArray &uuid); static OutputDeviceInterface *get(wl_resource *native); static QListlist(); Q_SIGNALS: void physicalSizeChanged(const QSize&); void globalPositionChanged(const QPoint&); void manufacturerChanged(const QString&); void modelChanged(const QString&); void pixelSizeChanged(const QSize&); void refreshRateChanged(int); //@deprecated see scaleChanged(real) void scaleChanged(int); void scaleFChanged(qreal); void subPixelChanged(SubPixel); void transformChanged(Transform); + void colorCurvesChanged(ColorCurves); void modesChanged(); void currentModeChanged(); void edidChanged(); void enabledChanged(); void uuidChanged(); private: friend class Display; explicit OutputDeviceInterface(Display *display, QObject *parent = nullptr); class Private; Private *d_func() const; }; } } Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Server::OutputDeviceInterface::ModeFlags) Q_DECLARE_METATYPE(KWayland::Server::OutputDeviceInterface::Enablement) Q_DECLARE_METATYPE(KWayland::Server::OutputDeviceInterface::SubPixel) Q_DECLARE_METATYPE(KWayland::Server::OutputDeviceInterface::Transform) +Q_DECLARE_METATYPE(KWayland::Server::OutputDeviceInterface::ColorCurves) #endif