diff --git a/autotests/configs/multipleoutput.json b/autotests/configs/multipleoutput.json index d168f27..6df3529 100644 --- a/autotests/configs/multipleoutput.json +++ b/autotests/configs/multipleoutput.json @@ -1,131 +1,132 @@ { "screen" : { "id" : 1, "maxSize" : { "width" : 8192, "height" : 8192 }, "minSize" : { "width" : 320, "height" : 200 }, "currentSize" : { "width" : 3200, "height" : 1880 }, "maxActiveOutputsCount": 2 }, "outputs" : [ { "id" : 1, "name" : "LVDS1", "type" : "LVDS", "modes" : [ { "id" : 3, "name" : "1280x800", "refreshRate" : 59.9, "size" : { "width" : 1280, "height" : 800 } }, { "id" : 2, "name" : "1024x768", "refreshRate" : 59.9, "size" : { "width" : 1024, "height" : 768 } }, { "id" : 1, "name" : "800x600", "refreshRate" : 60, "size" : { "width" : 800, "height" : 600 } } ], "pos" : { "x" : 0, "y" : 0 }, "size" : { "width" : 1280, "height" : 800 }, "currentModeId" : 3, "preferredModes" : [2], "rotation" : 1, "connected" : true, "enabled" : true, "primary" : true, "edid" : "AP///////wBMLcMFMzJGRQkUAQMOMx14Ku6Ro1RMmSYPUFQjCACBAIFAgYCVAKlAswABAQEBAjqAGHE4LUBYLEUA/h8RAAAeAAAA/QA4PB5REQAKICAgICAgAAAA/ABTeW5jTWFzdGVyCiAgAAAA/wBIOU1aMzAyMTk2CiAgAC4=" }, { "id" : 2, "name" : "HDMI1", "type" : "HDMI", "modes" : [ { "id" : 4, "name" : "1920x1080", "refreshRate" : 60, "size" : { "width" : 1920, "height" : 1080 } }, { "id" : 3, "name" : "1600x1200", "refreshRate" : 60, "size" : { "width" : 1600, "height" : 1200 } }, { "id" : 2, "name" : "1024x768", "refreshRate" : 59.9, "size" : { "width" : 1024, "height" : 768 } }, { "id" : 1, "name" : "800x600", "refreshRate" : 59.9, "size" : { "width" : 800, "height" : 600 } } ], "pos" : { "x" : 1280, "y" : 0 }, "size" : { "width" : 1920, "height" : 1080 }, + "scale" : 1.4, "currentModeId" : 4, "preferredModes" : [4], "rotation" : 1, "connected" : true, "enabled" : true, "primary" : false, "edid" : "AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHowK0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg==" } ] } diff --git a/autotests/testscreenconfig.cpp b/autotests/testscreenconfig.cpp index ef91dc2..968cf30 100644 --- a/autotests/testscreenconfig.cpp +++ b/autotests/testscreenconfig.cpp @@ -1,324 +1,326 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * * * 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/screen.h" #include "../src/config.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 testScreenConfig : public QObject { Q_OBJECT private: KScreen::ConfigPtr getConfig(); private Q_SLOTS: void initTestCase(); void singleOutput(); void singleOutputWithoutPreferred(); void multiOutput(); void clonesOutput(); void configCanBeApplied(); void supportedFeatures(); void testInvalidMode(); void cleanupTestCase(); void testOutputPositionNormalization(); }; ConfigPtr testScreenConfig::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(); } void testScreenConfig::initTestCase() { qputenv("KSCREEN_LOGGING", "false"); qputenv("KSCREEN_BACKEND", "Fake"); } void testScreenConfig::cleanupTestCase() { BackendManager::instance()->shutdownBackend(); } void testScreenConfig::singleOutput() { //json file for the fake backend qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleoutput.json"); // QVERIFY2(kscreen, KScreen::errorString().toLatin1()); // QVERIFY2(!kscreen->backend().isEmpty(), "No backend loaded"); const ConfigPtr config = getConfig(); QVERIFY(!config.isNull()); const ScreenPtr screen = config->screen(); QVERIFY(!screen.isNull()); QCOMPARE(screen->minSize(), QSize(320, 200)); QCOMPARE(screen->maxSize(), QSize(8192, 8192)); QCOMPARE(screen->currentSize(), QSize(1280, 800)); QCOMPARE(config->outputs().count(), 1); const OutputPtr output = config->outputs().take(1); QVERIFY(!output.isNull()); QCOMPARE(output->name(), QString("LVDS1")); QCOMPARE(output->type(), Output::Panel); QCOMPARE(output->modes().count(), 3); QCOMPARE(output->pos(), QPoint(0, 0)); QCOMPARE(output->geometry(), QRect(0,0, 1280, 800)); QCOMPARE(output->currentModeId(), QLatin1String("3")); QCOMPARE(output->preferredModeId(), QLatin1String("3")); QCOMPARE(output->rotation(), Output::None); + QCOMPARE(output->scale(), 1.0); QCOMPARE(output->isConnected(), true); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->isPrimary(), true); //QCOMPARE(output->isEmbedded(), true); QVERIFY2(output->clones().isEmpty(), "In singleOutput is impossible to have clones"); const ModePtr mode = output->currentMode(); QCOMPARE(mode->size(), QSize(1280, 800)); QCOMPARE(mode->refreshRate(), (float)59.9); } void testScreenConfig::singleOutputWithoutPreferred() { qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleOutputWithoutPreferred.json"); const ConfigPtr config = getConfig(); QVERIFY(!config.isNull()); const OutputPtr output = config->outputs().take(1); QVERIFY(!output.isNull()); QVERIFY(output->preferredModes().isEmpty()); QCOMPARE(output->preferredModeId(), QLatin1String("3")); } void testScreenConfig::multiOutput() { qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "multipleoutput.json"); const ConfigPtr config = getConfig(); QVERIFY(!config.isNull()); const ScreenPtr screen = config->screen(); QVERIFY(!screen.isNull()); QCOMPARE(screen->minSize(), QSize(320, 200)); QCOMPARE(screen->maxSize(), QSize(8192, 8192)); QCOMPARE(screen->currentSize(), QSize(3200, 1880)); QCOMPARE(config->outputs().count(), 2); const OutputPtr output = config->outputs().take(2); QVERIFY(!output.isNull()); QCOMPARE(output->name(), QString("HDMI1")); QCOMPARE(output->type(), Output::HDMI); QCOMPARE(output->modes().count(), 4); QCOMPARE(output->pos(), QPoint(1280, 0)); QCOMPARE(output->geometry(), QRect(1280, 0, 1920, 1080)); QCOMPARE(output->currentModeId(), QLatin1String("4")); QCOMPARE(output->preferredModeId(), QLatin1String("4")); QCOMPARE(output->rotation(), Output::None); + QCOMPARE(output->scale(), 1.4); QCOMPARE(output->isConnected(), true); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->isPrimary(), false); QVERIFY2(output->clones().isEmpty(), "This simulates extended output, no clones"); const ModePtr mode = output->currentMode(); QVERIFY(!mode.isNull()); QCOMPARE(mode->size(), QSize(1920, 1080)); QCOMPARE(mode->refreshRate(), (float)60.0); } void testScreenConfig::clonesOutput() { qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "multipleclone.json"); const ConfigPtr config = getConfig(); QVERIFY(!config.isNull()); const ScreenPtr screen = config->screen(); QVERIFY(!screen.isNull()); QCOMPARE(screen->minSize(), QSize(320, 200)); QCOMPARE(screen->maxSize(), QSize(8192, 8192)); QCOMPARE(screen->currentSize(), QSize(1024, 768)); const OutputPtr one = config->outputs()[1]; const OutputPtr two = config->outputs()[2]; QCOMPARE(one->currentMode()->size(), two->currentMode()->size()); QCOMPARE(one->clones().count(), 1); QCOMPARE(one->clones().first(), two->id()); QVERIFY2(two->clones().isEmpty(), "Output two should have no clones"); } void testScreenConfig::configCanBeApplied() { qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleoutputBroken.json"); const ConfigPtr brokenConfig = getConfig(); qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleoutput.json"); const ConfigPtr currentConfig = getConfig(); QVERIFY(!currentConfig.isNull()); const OutputPtr primaryBroken = brokenConfig->outputs()[2]; QVERIFY(!primaryBroken.isNull()); const OutputPtr currentPrimary = currentConfig->outputs()[1]; QVERIFY(!currentPrimary.isNull()); QVERIFY(!Config::canBeApplied(brokenConfig)); primaryBroken->setId(currentPrimary->id()); QVERIFY(!Config::canBeApplied(brokenConfig)); primaryBroken->setConnected(currentPrimary->isConnected()); QVERIFY(!Config::canBeApplied(brokenConfig)); primaryBroken->setCurrentModeId(QLatin1String("42")); QVERIFY(!Config::canBeApplied(brokenConfig)); primaryBroken->setCurrentModeId(currentPrimary->currentModeId()); QVERIFY(!Config::canBeApplied(brokenConfig)); qDebug() << "brokenConfig.modes" << primaryBroken->mode("3"); primaryBroken->mode(QLatin1String("3"))->setSize(QSize(1280, 800)); qDebug() << "brokenConfig.modes" << primaryBroken->mode("3"); QVERIFY(Config::canBeApplied(brokenConfig)); qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "tooManyOutputs.json"); const ConfigPtr brokenConfig2 = getConfig(); QVERIFY(!brokenConfig2.isNull()); int enabledOutputsCount = 0; Q_FOREACH (const OutputPtr &output, brokenConfig2->outputs()) { if (output->isEnabled()) { ++enabledOutputsCount; } } QVERIFY(brokenConfig2->screen()->maxActiveOutputsCount() < enabledOutputsCount); QVERIFY(!Config::canBeApplied(brokenConfig2)); const ConfigPtr nulllConfig; QVERIFY(!Config::canBeApplied(nulllConfig)); } void testScreenConfig::supportedFeatures() { ConfigPtr config = getConfig(); QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::None)); QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::Writable)); QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::PrimaryDisplay)); QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)); config->setSupportedFeatures(KScreen::Config::Feature::Writable | KScreen::Config::Feature::PrimaryDisplay); QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::Writable)); QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::PrimaryDisplay)); config->setSupportedFeatures(KScreen::Config::Feature::None); QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::None)); config->setSupportedFeatures(KScreen::Config::Feature::PerOutputScaling | KScreen::Config::Feature::Writable); QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::None)); QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::Writable)); QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)); config->setSupportedFeatures(KScreen::Config::Feature::PerOutputScaling | KScreen::Config::Feature::Writable | KScreen::Config::Feature::PrimaryDisplay); QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::None)); QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::Writable)); QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::PrimaryDisplay)); QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)); } void testScreenConfig::testInvalidMode() { ModeList modes; ModePtr invalidMode = modes.value("99"); QVERIFY(invalidMode.isNull()); auto output = new KScreen::Output(); auto currentMode = output->currentMode(); QVERIFY(currentMode.isNull()); QVERIFY(!currentMode); delete output; } void testScreenConfig::testOutputPositionNormalization() { qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "multipleoutput.json"); const ConfigPtr config = getConfig(); QVERIFY(!config.isNull()); auto left = config->outputs().first(); auto right = config->outputs().last(); QVERIFY(!left.isNull()); QVERIFY(!right.isNull()); left->setPos(QPoint(-5000, 700)); right->setPos(QPoint(-3720, 666)); QCOMPARE(left->pos(), QPoint(-5000, 700)); QCOMPARE(right->pos(), QPoint(-3720, 666)); // start a set operation to fix up the positions { auto setop = new SetConfigOperation(config); setop->exec(); } QCOMPARE(left->pos(), QPoint(0, 34)); QCOMPARE(right->pos(), QPoint(1280, 0)); // make sure it doesn't touch a valid config { auto setop = new SetConfigOperation(config); setop->exec(); } QCOMPARE(left->pos(), QPoint(0, 34)); QCOMPARE(right->pos(), QPoint(1280, 0)); // positions of single outputs should be at 0, 0 left->setEnabled(false); { auto setop = new SetConfigOperation(config); setop->exec(); } QCOMPARE(right->pos(), QPoint()); } QTEST_MAIN(testScreenConfig) #include "testscreenconfig.moc" diff --git a/backends/fake/parser.cpp b/backends/fake/parser.cpp index 48f2303..ccf00ad 100644 --- a/backends/fake/parser.cpp +++ b/backends/fake/parser.cpp @@ -1,251 +1,257 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * * * 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 "parser.h" #include "fake.h" #include "config.h" #include "output.h" #include #include #include #include #include #include #include using namespace KScreen; ConfigPtr Parser::fromJson(const QByteArray& data) { ConfigPtr config(new Config); const QJsonObject json = QJsonDocument::fromJson(data).object(); ScreenPtr screen = Parser::screenFromJson(json["screen"].toObject().toVariantMap()); config->setScreen(screen); const QVariantList outputs = json["outputs"].toArray().toVariantList(); if (outputs.isEmpty()) { return config; } OutputList outputList; Q_FOREACH(const QVariant &value, outputs) { const OutputPtr output = Parser::outputFromJson(value.toMap()); outputList.insert(output->id(), output); } config->setOutputs(outputList); return config; } ConfigPtr Parser::fromJson(const QString& path) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) { qWarning() << file.errorString(); qWarning() << "File: " << path; return ConfigPtr(); } return Parser::fromJson(file.readAll()); } ScreenPtr Parser::screenFromJson(const QVariantMap &data) { ScreenPtr screen(new Screen); screen->setId(data["id"].toInt()); screen->setMinSize(Parser::sizeFromJson(data["minSize"].toMap())); screen->setMaxSize(Parser::sizeFromJson(data["maxSize"].toMap())); screen->setCurrentSize(Parser::sizeFromJson(data["currentSize"].toMap())); screen->setMaxActiveOutputsCount(data["maxActiveOutputsCount"].toInt()); return screen; } void Parser::qvariant2qobject(const QVariantMap &variant, QObject *object) { const QMetaObject *metaObject = object->metaObject(); for (QVariantMap::const_iterator iter = variant.begin(); iter != variant.end(); ++iter) { const int propertyIndex = metaObject->indexOfProperty(qPrintable(iter.key())); if (propertyIndex == -1) { //qWarning() << "Skipping non-existent property" << iter.key(); continue; } const QMetaProperty metaProperty = metaObject->property(propertyIndex); if (!metaProperty.isWritable()) { //qWarning() << "Skipping read-only property" << iter.key(); continue; } const QVariant property = object->property(iter.key().toLatin1()); Q_ASSERT(property.isValid()); if (property.isValid()) { QVariant value = iter.value(); if (value.canConvert(property.type())) { value.convert(property.type()); object->setProperty(iter.key().toLatin1(), value); } else if (QString(QLatin1String("QVariant")).compare(QLatin1String(property.typeName())) == 0) { object->setProperty(iter.key().toLatin1(), value); } } } } OutputPtr Parser::outputFromJson(QMap< QString, QVariant > map) { OutputPtr output(new Output); output->setId(map["id"].toInt()); QStringList preferredModes; const QVariantList prefModes = map["preferredModes"].toList(); Q_FOREACH(const QVariant &mode, prefModes) { preferredModes.append(mode.toString()); } output->setPreferredModes(preferredModes); map.remove(QLatin1Literal("preferredModes")); ModeList modelist; const QVariantList modes = map["modes"].toList(); Q_FOREACH(const QVariant &modeValue, modes) { const ModePtr mode = Parser::modeFromJson(modeValue); modelist.insert(mode->id(), mode); } output->setModes(modelist); map.remove(QLatin1Literal("modes")); if(map.contains("clones")) { QList clones; Q_FOREACH(const QVariant &id, map["clones"].toList()) { clones.append(id.toInt()); } output->setClones(clones); map.remove(QLatin1Literal("clones")); } const QString type = map["type"].toByteArray().toUpper(); if (type.contains("LVDS") || type.contains("EDP") || type.contains("IDP") || type.contains("7")) { output->setType(Output::Panel); } else if (type.contains("VGA")) { output->setType(Output::VGA); } else if (type.contains("DVI")) { output->setType(Output::DVI); } else if (type.contains("DVI-I")) { output->setType(Output::DVII); } else if (type.contains("DVI-A")) { output->setType(Output::DVIA); } else if (type.contains("DVI-D")) { output->setType(Output::DVID); } else if (type.contains("HDMI") || type.contains("6")) { output->setType(Output::HDMI); } else if (type.contains("Panel")) { output->setType(Output::Panel); } else if (type.contains("TV")) { output->setType(Output::TV); } else if (type.contains("TV-Composite")) { output->setType(Output::TVComposite); } else if (type.contains("TV-SVideo")) { output->setType(Output::TVSVideo); } else if (type.contains("TV-Component")) { output->setType(Output::TVComponent); } else if (type.contains("TV-SCART")) { output->setType(Output::TVSCART); } else if (type.contains("TV-C4")) { output->setType(Output::TVC4); } else if (type.contains("DisplayPort") || type.contains("14")) { output->setType(Output::DisplayPort); } else if (type.contains("Unknown")) { output->setType(Output::Unknown); } else { qCWarning(KSCREEN_FAKE) << "Output Type not translated:" << type; } map.remove(QLatin1Literal("type")); if (map.contains("pos")) { output->setPos(Parser::pointFromJson(map["pos"].toMap())); map.remove(QLatin1Literal("pos")); } if (map.contains("size")) { output->setSize(Parser::sizeFromJson(map["size"].toMap())); map.remove(QLatin1Literal("size")); } + if (map.contains("scale")) { + qDebug() << "Scale found:" << map["scale"].toReal(); + output->setScale(map["scale"].toReal()); + map.remove(QLatin1Literal("size")); + } + //Remove some extra properties that we do not want or need special treatment map.remove(QLatin1Literal("edid")); Parser::qvariant2qobject(map, output.data()); return output; } ModePtr Parser::modeFromJson(const QVariant& data) { const QVariantMap map = data.toMap(); ModePtr mode(new Mode); Parser::qvariant2qobject(map, mode.data()); mode->setSize(Parser::sizeFromJson(map["size"].toMap())); return mode; } QSize Parser::sizeFromJson(const QVariant& data) { const QVariantMap map = data.toMap(); QSize size; size.setWidth(map["width"].toInt()); size.setHeight(map["height"].toInt()); return size; } QPoint Parser::pointFromJson(const QVariant& data) { const QVariantMap map = data.toMap(); QPoint point; point.setX(map["x"].toInt()); point.setY(map["y"].toInt()); return point; } QRect Parser::rectFromJson(const QVariant& data) { QRect rect; rect.setSize(Parser::sizeFromJson(data)); rect.setBottomLeft(Parser::pointFromJson(data)); return rect; } bool Parser::validate(const QByteArray& data) { Q_UNUSED(data); return true; } bool Parser::validate(const QString& data) { Q_UNUSED(data); return true; } diff --git a/src/output.cpp b/src/output.cpp index 1fcf30b..8f35605 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1,569 +1,586 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2014 by Daniel Vrátil * * * * 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 "output.h" #include "mode.h" #include "edid.h" #include "abstractbackend.h" #include "backendmanager_p.h" #include "debug_p.h" #include #include #include using namespace KScreen; class Output::Private { public: Private(): id(0), type(Unknown), rotation(None), + scale(1.0), connected(false), enabled(false), primary(false), edid(0) {} Private(const Private &other): id(other.id), name(other.name), type(other.type), icon(other.icon), clones(other.clones), currentMode(other.currentMode), preferredMode(other.preferredMode), preferredModes(other.preferredModes), sizeMm(other.sizeMm), pos(other.pos), size(other.size), rotation(other.rotation), + scale(other.scale), connected(other.connected), enabled(other.enabled), primary(other.primary) { Q_FOREACH (const ModePtr &otherMode, other.modeList) { modeList.insert(otherMode->id(), otherMode->clone()); } if (other.edid) { edid = other.edid->clone(); } } QString biggestMode(const ModeList& modes) const; bool compareModeList(const ModeList& before, const ModeList& after); int id; QString name; Type type; QString icon; ModeList modeList; QList clones; QString currentMode; QString preferredMode; QStringList preferredModes; QSize sizeMm; QPoint pos; QSize size; Rotation rotation; + qreal scale; bool connected; bool enabled; bool primary; 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; KScreen::ModePtr biggest; Q_FOREACH(const KScreen::ModePtr &mode, modes) { area = mode->size().width() * mode->size().height(); if (area < total) { continue; } if (area == total && mode->refreshRate() < biggest->refreshRate()) { continue; } if (area == total && mode->refreshRate() > biggest->refreshRate()) { biggest = mode; continue; } total = area; biggest = mode; } if (!biggest) { return 0; } return biggest->id(); } Output::Output() : QObject(0) , d(new Private()) { } Output::Output(Output::Private *dd) : QObject() , d(dd) { } Output::~Output() { delete d; } OutputPtr Output::clone() const { return OutputPtr(new Output(new Private(*d))); } int Output::id() const { return d->id; } void Output::setId(int id) { if (d->id == id) { return; } d->id = id; Q_EMIT outputChanged(); } QString Output::name() const { return d->name; } void Output::setName(const QString& name) { if (d->name == name) { return; } d->name = name; Q_EMIT outputChanged(); } Output::Type Output::type() const { return d->type; } void Output::setType(Type type) { if (d->type == type) { return; } d->type = type; Q_EMIT outputChanged(); } QString Output::icon() const { return d->icon; } void Output::setIcon(const QString& icon) { if (d->icon == icon) { return; } d->icon = icon; Q_EMIT outputChanged(); } ModePtr Output::mode(const QString& id) const { if (!d->modeList.contains(id)) { return ModePtr(); } return d->modeList[id]; } ModeList Output::modes() const { return d->modeList; } 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 { return d->currentMode; } void Output::setCurrentModeId(const QString& mode) { if (d->currentMode == mode) { return; } d->currentMode = mode; Q_EMIT currentModeIdChanged(); } ModePtr Output::currentMode() const { return d->modeList.value(d->currentMode); } void Output::setPreferredModes(const QStringList &modes) { d->preferredMode = QString(); d->preferredModes = modes; } QStringList Output::preferredModes() const { return d->preferredModes; } QString Output::preferredModeId() const { if (!d->preferredMode.isEmpty()) { return d->preferredMode; } if (d->preferredModes.isEmpty()) { return d->biggestMode(modes()); } int area, total = 0; KScreen::ModePtr biggest; KScreen::ModePtr candidateMode; Q_FOREACH(const QString &modeId, d->preferredModes) { candidateMode = mode(modeId); area = candidateMode->size().width() * candidateMode->size().height(); if (area < total) { continue; } if (area == total && biggest && candidateMode->refreshRate() < biggest->refreshRate()) { continue; } if (area == total && biggest && candidateMode->refreshRate() > biggest->refreshRate()) { biggest = candidateMode; continue; } total = area; biggest = candidateMode; } Q_ASSERT_X(biggest, "preferredModeId", "biggest mode must exist"); d->preferredMode = biggest->id(); return d->preferredMode; } ModePtr Output::preferredMode() const { return d->modeList.value(preferredModeId()); } QPoint Output::pos() const { return d->pos; } void Output::setPos(const QPoint& pos) { if (d->pos == pos) { return; } d->pos = pos; Q_EMIT posChanged(); } QSize Output::size() const { return d->size; } void Output::setSize(const QSize& size) { if (d->size == size) { return; } d->size = size; Q_EMIT sizeChanged(); } Output::Rotation Output::rotation() const { return d->rotation; } void Output::setRotation(Output::Rotation rotation) { if (d->rotation == rotation) { return; } d->rotation = rotation; Q_EMIT rotationChanged(); } +qreal Output::scale() const +{ + return d->scale; +} + +void Output::setScale(qreal factor) +{ + if (d->scale == factor) { + return; + } + d->scale = factor; + emit scaleChanged(); +} + bool Output::isConnected() const { return d->connected; } void Output::setConnected(bool connected) { if (d->connected == connected) { return; } d->connected = connected; Q_EMIT isConnectedChanged(); } bool Output::isEnabled() const { return d->enabled; } void Output::setEnabled(bool enabled) { if (d->enabled == enabled) { return; } d->enabled = enabled; Q_EMIT isEnabledChanged(); } bool Output::isPrimary() const { return d->primary; } void Output::setPrimary(bool primary) { if (d->primary == primary) { return; } d->primary = primary; Q_EMIT isPrimaryChanged(); } QList Output::clones() const { return d->clones; } void Output::setClones(QList outputlist) { if (d->clones == outputlist) { return; } d->clones = outputlist; Q_EMIT clonesChanged(); } void Output::setEdid(const QByteArray& rawData) { Q_ASSERT(d->edid == 0); d->edid = new Edid(rawData); } Edid *Output::edid() const { return d->edid; } QSize Output::sizeMm() const { return d->sizeMm; } void Output::setSizeMm(const QSize &size) { d->sizeMm = size; } QRect Output::geometry() const { if (!currentMode()) { return QRect(); } // We can't use QRect(d->pos, d->size), because d->size does not reflect the // actual rotation() set by caller, it's only updated when we get update from // KScreen, but not when user changes mode or rotation manually return isHorizontal() ? QRect(d->pos, currentMode()->size()) : QRect(d->pos, currentMode()->size().transposed()); } void Output::apply(const OutputPtr& other) { typedef void (KScreen::Output::*ChangeSignal)(); QList changes; // We block all signals, and emit them only after we have set up everything // This is necessary in order to prevent clients from accessing inconsistent // outputs from intermediate change signals const bool keepBlocked = signalsBlocked(); blockSignals(true); if (d->name != other->d->name) { changes << &Output::outputChanged; setName(other->d->name); } if (d->type != other->d->type) { changes << &Output::outputChanged; setType(other->d->type); } if (d->icon != other->d->icon) { changes << &Output::outputChanged; setIcon(other->d->icon); } if (d->pos != other->d->pos) { changes << &Output::posChanged; setPos(other->pos()); } if (d->rotation != other->d->rotation) { changes << &Output::rotationChanged; setRotation(other->d->rotation); } if (d->currentMode != other->d->currentMode) { changes << &Output::currentModeIdChanged; setCurrentModeId(other->d->currentMode); } if (d->connected != other->d->connected) { changes << &Output::isConnectedChanged; setConnected(other->d->connected); } if (d->enabled != other->d->enabled) { changes << &Output::isEnabledChanged; setEnabled(other->d->enabled); } if (d->primary != other->d->primary) { changes << &Output::isPrimaryChanged; setPrimary(other->d->primary); } if (d->clones != other->d->clones) { changes << &Output::clonesChanged; setClones(other->d->clones);; } if (!d->compareModeList(d->modeList, other->d->modeList)) { changes << &Output::outputChanged; } 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(); } blockSignals(keepBlocked); while (!changes.isEmpty()) { const ChangeSignal &sig = changes.first(); Q_EMIT (this->*sig)(); changes.removeAll(sig); } } QDebug operator<<(QDebug dbg, const KScreen::OutputPtr &output) { if(output) { dbg << "KScreen::Output(" << output->id() << " " << output->name() << (output->isConnected() ? "connected" : "disconnected") << (output->isEnabled() ? "enabled" : "disabled") << output->pos() << output->size() << output->currentModeId() << ")"; } else { dbg << "KScreen::Output(NULL)"; } return dbg; } diff --git a/src/output.h b/src/output.h index 13b4a72..f126dc2 100644 --- a/src/output.h +++ b/src/output.h @@ -1,232 +1,252 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2014 by Daniel Vrátil * * * * 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 * *************************************************************************************/ #ifndef OUTPUT_CONFIG_H #define OUTPUT_CONFIG_H #include "mode.h" #include "types.h" #include "kscreen_export.h" #include #include #include #include #include #include namespace KScreen { class Edid; class KSCREEN_EXPORT Output : public QObject { Q_OBJECT public: Q_ENUMS(Rotation) Q_ENUMS(Type) Q_PROPERTY(int id READ id CONSTANT) 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 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) Q_PROPERTY(QString currentModeId READ currentModeId WRITE setCurrentModeId NOTIFY currentModeIdChanged) Q_PROPERTY(QString preferredModeId READ preferredModeId CONSTANT) Q_PROPERTY(bool connected READ isConnected WRITE setConnected NOTIFY isConnectedChanged) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY isEnabledChanged) Q_PROPERTY(bool primary READ isPrimary WRITE setPrimary NOTIFY isPrimaryChanged) Q_PROPERTY(QList clones READ clones WRITE setClones NOTIFY clonesChanged) Q_PROPERTY(KScreen::Edid* edid READ edid CONSTANT) Q_PROPERTY(QSize sizeMm READ sizeMm CONSTANT) + Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged) + enum Type { Unknown, VGA, DVI, DVII, DVIA, DVID, HDMI, Panel, TV, TVComposite, TVSVideo, TVComponent, TVSCART, TVC4, DisplayPort }; enum Rotation { None = 1, Left = 2, Inverted = 4, Right = 8 }; explicit Output(); virtual ~Output(); OutputPtr clone() const; int id() const; void setId(int id); QString name() const; void setName(const QString& name); Type type() const; void setType(Type type); QString icon() const; void setIcon(const QString& icon); Q_INVOKABLE ModePtr mode(const QString &id) const; ModeList modes() const; void setModes(const ModeList &modes); QString currentModeId() const; void setCurrentModeId(const QString& mode); Q_INVOKABLE ModePtr currentMode() const; void setPreferredModes(const QStringList &modes); QStringList preferredModes() const; /** * Returns the preferred mode with higer resolution and refresh */ Q_INVOKABLE QString preferredModeId() const; /** * Returns KScreen::Mode associated with preferredModeId() */ Q_INVOKABLE ModePtr preferredMode() const; QPoint pos() const; void setPos(const QPoint& pos); /*** * Returns actual size being rendered in the output * * The returned valued is after transformations have been applied to * the resolution of the current mode. * * For example if currentMode is 1280x800 but it is a vertical screen * the returned size will be 800x1280. * * If that same resolution (1280x800) is transformed and scale x2, the * value returned will be 2560x1600. * * This property reflects the currently active output configuration and * is not affected by current mode or orientation change made by user * until the config is applied. * * @since 5.4 */ QSize size() const; void setSize(const QSize& size); Rotation rotation() const; void setRotation(Rotation rotation); /** * A comfortable function that returns true when output is not rotated * or is rotated upside down. */ Q_INVOKABLE inline bool isHorizontal() const { return ((rotation() == Output::None) || (rotation() == Output::Inverted)); } bool isConnected() const; void setConnected(bool connected); bool isEnabled() const; void setEnabled(bool enabled); bool isPrimary() const; void setPrimary(bool primary); QList clones() const; void setClones(QList outputlist); void setEdid(const QByteArray &rawData); Edid* edid() const; /** * Returns the physical size of the screen in milimeters. * * @note Some broken GPUs or monitors return the size in centimeters instead * of millimeters. KScreen at the moment is not sanitizing the values. */ QSize sizeMm() const; void setSizeMm(const QSize &size); /** * Returns a rectangle containing the currently set output position and * size. * * The geometry also reflects current orientation (i.e. if current mode * is 1920x1080 and orientation is @p KScreen::Output::Left, then the * size of the returned rectangle will be 1080x1920. * * This property contains the current settings stored in the particular * Output object, so it is updated even when user changes current mode * or orientation without applying the whole config/ */ QRect geometry() const; + /** + * returns the scaling factor to use for this output + * + * @since 5.9 + */ + qreal scale() const; + + /** + * Set the scaling factor for this output. + * + * @arg factor Scale factor to use for this output, the backend may or may not + * be able to deal with non-integer values, in that case, the factor gets rounded. + * + * @since 5.9 + */ + void setScale(qreal factor); + void apply(const OutputPtr &other); Q_SIGNALS: void outputChanged(); void posChanged(); void sizeChanged(); void currentModeIdChanged(); void rotationChanged(); void isConnectedChanged(); void isEnabledChanged(); void isPrimaryChanged(); void clonesChanged(); + void scaleChanged(); /** 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) class Private; Private * const d; Output(Private *dd); }; } //KScreen namespace KSCREEN_EXPORT QDebug operator<<(QDebug dbg, const KScreen::OutputPtr &output); Q_DECLARE_METATYPE(KScreen::OutputList) Q_DECLARE_METATYPE(KScreen::Output::Rotation) Q_DECLARE_METATYPE(KScreen::Output::Type) #endif //OUTPUT_H