diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 9f8cea7..774685c 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,47 +1,48 @@ add_definitions(-DTEST_DATA="${CMAKE_CURRENT_SOURCE_DIR}/configs/") include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/tests/kwayland/) macro(KSCREEN_ADD_TEST) foreach(_testname ${ARGN}) set(test_SRCS ${_testname}.cpp ${KSCREEN_WAYLAND_SRCS}) qt5_add_dbus_interface(test_SRCS ${CMAKE_SOURCE_DIR}/interfaces/org.kde.KScreen.FakeBackend.xml fakebackendinterface) add_executable(${_testname} ${test_SRCS}) target_link_libraries(${_testname} Qt5::Core Qt5::Gui Qt5::Test Qt5::DBus KF5::Screen ${KSCREEN_WAYLAND_LIBS}) add_test(NAME kscreen-${_testname} COMMAND dbus-launch $ ) ecm_mark_as_test(${_testname}) endforeach(_testname) endmacro(KSCREEN_ADD_TEST) kscreen_add_test(testscreenconfig) kscreen_add_test(testqscreenbackend) kscreen_add_test(testconfigserializer) kscreen_add_test(testconfigmonitor) kscreen_add_test(testinprocess) kscreen_add_test(testbackendloader) kscreen_add_test(testlog) +kscreen_add_test(testmodelistchange) set(KSCREEN_WAYLAND_LIBS KF5::WaylandServer KF5::WaylandClient ) # For WaylandConfigReader and TestServer set(KSCREEN_WAYLAND_SRCS ${CMAKE_SOURCE_DIR}/tests/kwayland/waylandconfigreader.cpp ${CMAKE_SOURCE_DIR}/tests/kwayland/waylandtestserver.cpp ) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../backends/kwayland) kscreen_add_test(testkwaylandbackend) kscreen_add_test(testkwaylandconfig) kscreen_add_test(testkwaylanddpms) set(KSCREEN_WAYLAND_LIBS "") set(KSCREEN_WAYLAND_SRCS "") if (ENABLE_XRANDR_TESTS) kscreen_add_test(textxrandr) endif() diff --git a/autotests/testmodelistchange.cpp b/autotests/testmodelistchange.cpp new file mode 100644 index 0000000..58080f8 --- /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 index 9a5cabc..94a0b65 100644 --- a/backends/xrandr/xrandroutput.cpp +++ b/backends/xrandr/xrandroutput.cpp @@ -1,381 +1,389 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2012, 2013 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 "xrandroutput.h" #include "xrandrmode.h" #include "xrandrconfig.h" #include "xrandr.h" #include "output.h" #include "config.h" #include Q_DECLARE_METATYPE(QList) XRandROutput::XRandROutput(xcb_randr_output_t id, XRandRConfig *config) : QObject(config) , m_config(config) , m_id(id) , m_type(KScreen::Output::Unknown) , m_primary(0) , m_crtc(0) { init(); } XRandROutput::~XRandROutput() { } xcb_randr_output_t XRandROutput::id() const { return m_id; } bool XRandROutput::isConnected() const { return m_connected == XCB_RANDR_CONNECTION_CONNECTED; } bool XRandROutput::isEnabled() const { return m_crtc != Q_NULLPTR && m_crtc->mode() != XCB_NONE; } bool XRandROutput::isPrimary() const { return m_primary; } QPoint XRandROutput::position() const { return m_crtc ? m_crtc->geometry().topLeft() : QPoint(); } QSize XRandROutput::size() const { return m_crtc ? m_crtc->geometry().size() : QSize(); } XRandRMode::Map XRandROutput::modes() const { return m_modes; } QString XRandROutput::currentModeId() const { return m_crtc ? QString::number(m_crtc->mode()) : QString(); } XRandRMode* XRandROutput::currentMode() const { if (!m_crtc) { return Q_NULLPTR; } int modeId = m_crtc->mode(); if (!m_modes.contains(modeId)) { return 0; } return m_modes[modeId]; } KScreen::Output::Rotation XRandROutput::rotation() const { return static_cast(m_crtc ? m_crtc->rotation() : XCB_RANDR_ROTATION_ROTATE_0); } QByteArray XRandROutput::edid() const { if (m_edid.isNull()) { size_t len; quint8 *data = XRandR::outputEdid(m_id, len); if (data) { m_edid = QByteArray((char *) data, len); delete[] data; } else { m_edid = QByteArray(); } } return m_edid; } XRandRCrtc* XRandROutput::crtc() const { return m_crtc; } void XRandROutput::update() { init(); } void XRandROutput::update(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t conn, bool primary) { qCDebug(KSCREEN_XRANDR) << "XRandROutput" << m_id << "update"; qCDebug(KSCREEN_XRANDR) << "\tm_connected:" << m_connected; qCDebug(KSCREEN_XRANDR) << "\tm_crtc" << m_crtc; qCDebug(KSCREEN_XRANDR) << "\tCRTC:" << crtc; qCDebug(KSCREEN_XRANDR) << "\tMODE:" << mode; qCDebug(KSCREEN_XRANDR) << "\tConnection:" << conn; qCDebug(KSCREEN_XRANDR) << "\tPrimary:" << primary; // Connected or disconnected 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; m_clones.clear(); m_heightMm = 0; m_widthMm = 0; m_type = KScreen::Output::Unknown; qDeleteAll(m_modes); 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 // We don't use isEnabled(), because it checks for crtc && crtc->mode(), however // crtc->mode may already be unset due to xcb_randr_crtc_tChangeNotify coming before // xcb_randr_output_tChangeNotify and reseting the CRTC mode if ((m_crtc == Q_NULLPTR) != (crtc == XCB_NONE)) { if (crtc == XCB_NONE && mode == XCB_NONE) { // Monitor has been disabled m_crtc->disconectOutput(m_id); m_crtc = 0; } else { m_crtc = m_config->crtc(crtc); m_crtc->connectOutput(m_id); } } // Primary has changed m_primary = primary; } void XRandROutput::setIsPrimary(bool primary) { m_primary = primary; } void XRandROutput::init() { XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME); Q_ASSERT(outputInfo); if (!outputInfo) { return; } XCB::PrimaryOutput primary(XRandR::rootWindow()); m_name = QString::fromUtf8((const char *) xcb_randr_get_output_info_name(outputInfo.data()), outputInfo->name_len); m_type = fetchOutputType(m_id, m_name); m_icon = QString(); m_connected = (xcb_randr_connection_t) outputInfo->connection; m_primary = (primary->output == m_id); xcb_randr_output_t *clones = xcb_randr_get_output_info_clones(outputInfo.data()); for (int i = 0; i < outputInfo->num_clones; ++i) { m_clones.append(clones[i]); } m_widthMm = outputInfo->mm_width; m_heightMm = outputInfo->mm_height; m_crtc = m_config->crtc(outputInfo->crtc); if (m_crtc) { m_crtc->connectOutput(m_id); } updateModes(outputInfo); } void XRandROutput::updateModes(const XCB::OutputInfo &outputInfo) { /* Init modes */ XCB::ScopedPointer screenResources(XRandR::screenResources()); Q_ASSERT(screenResources); if (!screenResources) { return; } xcb_randr_mode_info_t *modes = xcb_randr_get_screen_resources_modes(screenResources.data()); xcb_randr_mode_t *outputModes = xcb_randr_get_output_info_modes(outputInfo.data()); m_preferredModes.clear(); qDeleteAll(m_modes); m_modes.clear(); for (int i = 0; i < outputInfo->num_modes; ++i) { /* Resources->modes contains all possible modes, we are only interested * in those listed in outputInfo->modes. */ for (int j = 0; j < screenResources->num_modes; ++j) { if (modes[j].id != outputModes[i]) { continue; } XRandRMode *mode = new XRandRMode(modes[j], this); m_modes.insert(mode->id(), mode); if (i < outputInfo->num_preferred) { m_preferredModes.append(QString::number(mode->id())); } break; } } } KScreen::Output::Type XRandROutput::fetchOutputType(xcb_randr_output_t outputId, const QString &name) { QByteArray type = typeFromProperty(outputId); if (type.isEmpty()) { type = name.toLocal8Bit(); } static const QVector embedded{QLatin1String("LVDS"), QLatin1String("IDP"), QLatin1String("EDP"), QLatin1String("LCD")}; for (const QLatin1String &pre : embedded) { if (name.toUpper().startsWith(pre)) { return KScreen::Output::Panel; } } if (type.contains("VGA")) { return KScreen::Output::VGA; } else if (type.contains("DVI")) { return KScreen::Output::DVI; } else if (type.contains("DVI-I")) { return KScreen::Output::DVII; } else if (type.contains("DVI-A")) { return KScreen::Output::DVIA; } else if (type.contains("DVI-D")) { return KScreen::Output::DVID; } else if (type.contains("HDMI")) { return KScreen::Output::HDMI; } else if (type.contains("Panel")) { return KScreen::Output::Panel; } else if (type.contains("TV-Composite")) { return KScreen::Output::TVComposite; } else if (type.contains("TV-SVideo")) { return KScreen::Output::TVSVideo; } else if (type.contains("TV-Component")) { return KScreen::Output::TVComponent; } else if (type.contains("TV-SCART")) { return KScreen::Output::TVSCART; } else if (type.contains("TV-C4")) { return KScreen::Output::TVC4; } else if (type.contains("TV")) { return KScreen::Output::TV; } else if (type.contains("DisplayPort") || type.startsWith("DP")) { return KScreen::Output::DisplayPort; } else if (type.contains("unknown")) { return KScreen::Output::Unknown; } else { // qCDebug(KSCREEN_XRANDR) << "Output Type not translated:" << type; } return KScreen::Output::Unknown; } QByteArray XRandROutput::typeFromProperty(xcb_randr_output_t outputId) { QByteArray type; XCB::InternAtom atomType(true, 13, "ConnectorType"); if (!atomType) { return type; } char *connectorType; auto cookie = xcb_randr_get_output_property(XCB::connection(), outputId, atomType, XCB_ATOM_ANY, 0, 100, false, false); XCB::ScopedPointer reply(xcb_randr_get_output_property_reply(XCB::connection(), cookie, NULL)); if (!reply) { return type; } if (!(reply->type == XCB_ATOM_ATOM && reply->format == 32 && reply->num_items == 1)) { return type; } const uint8_t *prop = xcb_randr_get_output_property_data(reply.data()); XCB::AtomName atomName(*reinterpret_cast(prop)); if (!atomName) { return type; } connectorType = xcb_get_atom_name_name(atomName); if (!connectorType) { return type; } type = connectorType; return type; } KScreen::OutputPtr XRandROutput::toKScreenOutput() const { KScreen::OutputPtr kscreenOutput(new KScreen::Output); const bool signalsBlocked = kscreenOutput->signalsBlocked(); kscreenOutput->blockSignals(true); kscreenOutput->setId(m_id); kscreenOutput->setType(m_type); kscreenOutput->setSizeMm(QSize(m_widthMm, m_heightMm)); kscreenOutput->setName(m_name); kscreenOutput->setIcon(m_icon); kscreenOutput->setConnected(isConnected()); if (isConnected()) { KScreen::ModeList kscreenModes; for (auto iter = m_modes.constBegin(), end = m_modes.constEnd(); iter != end; ++iter) { XRandRMode *mode = iter.value(); kscreenModes.insert(QString::number(iter.key()), mode->toKScreenMode()); } kscreenOutput->setModes(kscreenModes); kscreenOutput->setPreferredModes(m_preferredModes); kscreenOutput->setPrimary(m_primary); kscreenOutput->setClones([](const QList &clones) { QList kclones; kclones.reserve(clones.size()); for (xcb_randr_output_t o : clones) { kclones.append(static_cast(o)); } return kclones; }(m_clones)); kscreenOutput->setEnabled(isEnabled()); if (isEnabled()) { kscreenOutput->setSize(size()); kscreenOutput->setPos(position()); kscreenOutput->setRotation(rotation()); kscreenOutput->setCurrentModeId(currentModeId()); } } kscreenOutput->blockSignals(signalsBlocked); return kscreenOutput; } diff --git a/src/output.cpp b/src/output.cpp index a45150e..1fcf30b 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1,534 +1,569 @@ /************************************************************************************* * 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), 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), 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; 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(); } 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; + } - // 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(); } 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 bb109e2..13b4a72 100644 --- a/src/output.h +++ b/src/output.h @@ -1,224 +1,232 @@ /************************************************************************************* * 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 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) 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) 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; 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(); + /** 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