diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -22,6 +22,7 @@ kscreen_add_test(testinprocess) kscreen_add_test(testbackendloader) kscreen_add_test(testlog) +kscreen_add_test(testmodelistchange) set(KSCREEN_WAYLAND_LIBS KF5::WaylandServer KF5::WaylandClient diff --git a/autotests/testmodelistchange.cpp b/autotests/testmodelistchange.cpp new file mode 100644 --- /dev/null +++ b/autotests/testmodelistchange.cpp @@ -0,0 +1,179 @@ +/************************************************************************************* + * Copyright 2016 by 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) any later version. * + * * + * 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, write to the Free Software * + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * + *************************************************************************************/ + +#include +#include + +#include "../src/config.h" +#include "../src/configmonitor.h" +#include "../src/output.h" +#include "../src/mode.h" +#include "../src/getconfigoperation.h" +#include "../src/setconfigoperation.h" +#include "../src/backendmanager_p.h" + +using namespace KScreen; + + +class TestModeListChange : public QObject +{ + Q_OBJECT + +private: + KScreen::ConfigPtr getConfig(); + KScreen::ModeList createModeList(); + bool compareModeList(KScreen::ModeList before, KScreen::ModeList &after); + + QSize s0 = QSize(1920, 1080); + QSize s1 = QSize(1600, 1200); + QSize s2 = QSize(1280, 1024); + QSize s3 = QSize(800, 600); + QSize snew = QSize(777, 888); + QString idnew = QStringLiteral("666"); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void modeListChange(); +}; + +ConfigPtr TestModeListChange::getConfig() +{ + qputenv("KSCREEN_BACKEND_INPROCESS", "1"); + auto *op = new GetConfigOperation(); + if (!op->exec()) { + qWarning("ConfigOperation error: %s", qPrintable(op->errorString())); + BackendManager::instance()->shutdownBackend(); + return ConfigPtr(); + } + + BackendManager::instance()->shutdownBackend(); + + return op->config(); +} + +KScreen::ModeList TestModeListChange::createModeList() +{ + + KScreen::ModeList newmodes; + { + QString _id = QString::number(11); + KScreen::ModePtr kscreenMode(new KScreen::Mode); + kscreenMode->setId(_id); + kscreenMode->setName(_id); + kscreenMode->setSize(s0); + kscreenMode->setRefreshRate(60); + newmodes.insert(_id, kscreenMode); + } + { + QString _id = QString::number(22); + KScreen::ModePtr kscreenMode(new KScreen::Mode); + kscreenMode->setId(_id); + kscreenMode->setName(_id); + kscreenMode->setSize(s1); + kscreenMode->setRefreshRate(60); + newmodes.insert(_id, kscreenMode); + } + { + QString _id = QString::number(33); + KScreen::ModePtr kscreenMode(new KScreen::Mode); + kscreenMode->setId(_id); + kscreenMode->setName(_id); + kscreenMode->setSize(s2); + kscreenMode->setRefreshRate(60); + newmodes.insert(_id, kscreenMode); + } + return newmodes; +} + + +void TestModeListChange::initTestCase() +{ + qputenv("KSCREEN_LOGGING", "false"); + qputenv("KSCREEN_BACKEND", "Fake"); +} + +void TestModeListChange::cleanupTestCase() +{ + BackendManager::instance()->shutdownBackend(); +} + +void TestModeListChange::modeListChange() +{ + //json file for the fake backend + qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleoutput.json"); + + const ConfigPtr config = getConfig(); + QVERIFY(!config.isNull()); + + auto output = config->outputs().first(); + QVERIFY(!output.isNull()); + auto modelist = output->modes(); + + auto mode = modelist.first(); + mode->setId(QStringLiteral("44")); + mode->setSize(QSize(880, 440)); + output->setModes(modelist); + + QCOMPARE(output->modes().first()->id(), QStringLiteral("44")); + QCOMPARE(output->modes().first()->size(), QSize(880, 440)); + QVERIFY(!modelist.isEmpty()); + + ConfigMonitor::instance()->addConfig(config); + QSignalSpy outputChangedSpy(output.data(), &Output::outputChanged); + QVERIFY(outputChangedSpy.isValid()); + QSignalSpy modesChangedSpy(output.data(), &Output::modesChanged); + QVERIFY(modesChangedSpy.isValid()); + + auto before = createModeList(); + output->setModes(before); + QCOMPARE(modesChangedSpy.count(), 1); + output->setModes(before); + QCOMPARE(modesChangedSpy.count(), 1); + output->setModes(before); + QCOMPARE(modesChangedSpy.count(), 1); + QCOMPARE(output->modes().first()->size(), s0); + QCOMPARE(output->modes().first()->id(), QStringLiteral("11")); + + auto after = createModeList(); + auto firstmode = after.first(); + QVERIFY(!firstmode.isNull()); + QCOMPARE(firstmode->size(), s0); + QCOMPARE(firstmode->id(), QStringLiteral("11")); + firstmode->setSize(snew); + firstmode->setId(idnew); + output->setModes(after); + QCOMPARE(modesChangedSpy.count(), 2); + + QString _id = QString::number(11); + KScreen::ModePtr kscreenMode(new KScreen::Mode); + kscreenMode->setId(_id); + kscreenMode->setName(_id); + kscreenMode->setSize(s0); + kscreenMode->setRefreshRate(60); + before.insert(_id, kscreenMode); + output->setModes(before); + QCOMPARE(modesChangedSpy.count(), 3); + QCOMPARE(outputChangedSpy.count(), modesChangedSpy.count()); +} + + +QTEST_MAIN(TestModeListChange) + +#include "testmodelistchange.moc" diff --git a/backends/xrandr/xrandroutput.cpp b/backends/xrandr/xrandroutput.cpp --- a/backends/xrandr/xrandroutput.cpp +++ b/backends/xrandr/xrandroutput.cpp @@ -141,7 +141,7 @@ if (isConnected() != (conn == XCB_RANDR_CONNECTION_CONNECTED)) { if (conn == XCB_RANDR_CONNECTION_CONNECTED) { // New monitor has been connected, refresh everything - init(); + init(); } else { // Disconnected m_connected = conn; @@ -153,6 +153,14 @@ m_modes.clear(); m_preferredModes.clear(); } + } else if (conn == XCB_RANDR_CONNECTION_CONNECTED) { + // the output changed in some way, let's update the internal + // list of modes, as it may have changed + XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME); + if (outputInfo) { + updateModes(outputInfo); + } + } // A monitor has been enabled or disabled diff --git a/src/output.h b/src/output.h --- a/src/output.h +++ b/src/output.h @@ -46,7 +46,7 @@ Q_PROPERTY(QString name READ name WRITE setName NOTIFY outputChanged) Q_PROPERTY(Type type READ type WRITE setType NOTIFY outputChanged) Q_PROPERTY(QString icon READ icon WRITE setIcon NOTIFY outputChanged) - Q_PROPERTY(ModeList modes READ modes CONSTANT) + Q_PROPERTY(ModeList modes READ modes NOTIFY modesChanged) Q_PROPERTY(QPoint pos READ pos WRITE setPos NOTIFY posChanged) Q_PROPERTY(QSize size READ size WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(Rotation rotation READ rotation WRITE setRotation NOTIFY rotationChanged) @@ -204,6 +204,14 @@ void isPrimaryChanged(); void clonesChanged(); + /** The mode list changed. + * + * This may happen when a mode is added or changed. + * + * @since 5.8.3 + */ + void modesChanged(); + private: Q_DISABLE_COPY(Output) diff --git a/src/output.cpp b/src/output.cpp --- a/src/output.cpp +++ b/src/output.cpp @@ -69,6 +69,7 @@ } QString biggestMode(const ModeList& modes) const; + bool compareModeList(const ModeList& before, const ModeList& after); int id; QString name; @@ -90,6 +91,32 @@ mutable QPointer edid; }; +bool Output::Private::compareModeList(const ModeList& before, const ModeList &after) +{ + if (before.keys() != after.keys()) { + return false; + } + for (const QString &key : before.keys()) { + const auto mb = before.value(key); + const auto ma = after.value(key); + if (mb->id() != ma->id()) { + return false; + } + if (mb->size() != ma->size()) { + return false; + } + if (mb->refreshRate() != ma->refreshRate()) { + return false; + } + if (mb->name() != ma->name()) { + return false; + } + } + // They're the same + return true; +} + + QString Output::Private::biggestMode(const ModeList& modes) const { int area, total = 0; @@ -221,7 +248,12 @@ void Output::setModes(const ModeList &modes) { + bool changed = !d->compareModeList(d->modeList, modes); d->modeList = modes; + if (changed) { + emit modesChanged(); + emit outputChanged(); + } } QString Output::currentModeId() const @@ -494,15 +526,18 @@ changes << &Output::clonesChanged; setClones(other->d->clones);; } + if (!d->compareModeList(d->modeList, other->d->modeList)) { + changes << &Output::outputChanged; + } - // Non-notifyable changes setPreferredModes(other->d->preferredModes); ModeList modes; Q_FOREACH (const ModePtr &otherMode, other->modes()) { modes.insert(otherMode->id(), otherMode->clone()); } setModes(modes); + // Non-notifyable changes if (other->d->edid) { delete d->edid; d->edid = other->d->edid->clone();