diff --git a/backends/kwayland/waylandconfig.cpp b/backends/kwayland/waylandconfig.cpp index 1a62361..ed66c31 100644 --- a/backends/kwayland/waylandconfig.cpp +++ b/backends/kwayland/waylandconfig.cpp @@ -1,364 +1,364 @@ /************************************************************************************* * Copyright 2014-2015 Sebastian Kügler * * Copyright 2013 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) 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 "waylandconfig.h" #include "waylandoutput.h" #include "waylandscreen.h" #include "waylandbackend.h" // KWayland #include #include #include #include #include #include // Qt #include #include #include using namespace KScreen; WaylandConfig::WaylandConfig(QObject *parent) : QObject(parent) , m_outputManagement(nullptr) , m_registryInitialized(false) , m_blockSignals(true) , m_newOutputId(0) , m_kscreenConfig(nullptr) , m_kscreenPendingConfig(nullptr) , m_screen(new WaylandScreen(this)) { connect(this, &WaylandConfig::initialized, &m_syncLoop, &QEventLoop::quit); QTimer::singleShot(1000, this, [this] { if (m_syncLoop.isRunning()) { qCWarning(KSCREEN_WAYLAND) << "Connection to Wayland server at socket:" << m_connection->socketName() << "timed out."; m_syncLoop.quit(); m_thread->quit(); m_thread->wait(); } }); initConnection(); m_syncLoop.exec(); } WaylandConfig::~WaylandConfig() { m_thread->quit(); m_thread->wait(); m_syncLoop.quit(); } void WaylandConfig::initConnection() { m_thread = new QThread(this); //m_queue = new KWayland::Client::EventQueue(this); m_connection = new KWayland::Client::ConnectionThread; connect(m_connection, &KWayland::Client::ConnectionThread::connected, this, &WaylandConfig::setupRegistry, Qt::QueuedConnection); connect(m_connection, &KWayland::Client::ConnectionThread::connectionDied, this, &WaylandConfig::disconnected, Qt::QueuedConnection); connect(m_connection, &KWayland::Client::ConnectionThread::failed, this, [this] { qCWarning(KSCREEN_WAYLAND) << "Failed to connect to Wayland server at socket:" << m_connection->socketName(); m_syncLoop.quit(); m_thread->quit(); m_thread->wait(); }); m_thread->start(); m_connection->moveToThread(m_thread); m_connection->initConnection(); } void WaylandConfig::blockSignals() { Q_ASSERT(m_blockSignals == false); m_blockSignals = true; } void WaylandConfig::unblockSignals() { Q_ASSERT(m_blockSignals == true); m_blockSignals = false; } void WaylandConfig::disconnected() { qCWarning(KSCREEN_WAYLAND) << "Wayland disconnected, cleaning up."; qDeleteAll(m_outputMap); m_outputMap.clear(); // Clean up if (m_queue) { delete m_queue; m_queue = nullptr; } m_connection->deleteLater(); m_connection = nullptr; if (m_thread) { m_thread->quit(); if (!m_thread->wait(3000)) { m_thread->terminate(); m_thread->wait(); } delete m_thread; m_thread = nullptr; } Q_EMIT configChanged(toKScreenConfig()); Q_EMIT gone(); } void WaylandConfig::setupRegistry() { m_queue = new KWayland::Client::EventQueue(this); m_queue->setup(m_connection); m_registry = new KWayland::Client::Registry(this); connect(m_registry, &KWayland::Client::Registry::outputDeviceAnnounced, this, &WaylandConfig::addOutput); connect(m_registry, &KWayland::Client::Registry::outputDeviceRemoved, this, &WaylandConfig::removeOutput); connect(m_registry, &KWayland::Client::Registry::outputManagementAnnounced, this, [this](quint32 name, quint32 version) { m_outputManagement = m_registry->createOutputManagement(name, version, m_registry); checkInitialized(); } ); connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, this, [this] { m_registryInitialized = true; unblockSignals(); checkInitialized(); } ); m_registry->create(m_connection); m_registry->setEventQueue(m_queue); m_registry->setup(); } void WaylandConfig::addOutput(quint32 name, quint32 version) { ++m_newOutputId; quint32 new_id = m_newOutputId; m_outputIds[name] = new_id; if (m_outputMap.contains(new_id)) { return; } if (!m_initializingOutputs.contains(name)) { m_initializingOutputs << name; } auto op = new KWayland::Client::OutputDevice(this); WaylandOutput *waylandoutput = new WaylandOutput(new_id, this); waylandoutput->bindOutputDevice(m_registry, op, name, version); // finalize: when the output is done, we put it in the known outputs map, // remove if from the list of initializing outputs, and emit configChanged() connect(waylandoutput, &WaylandOutput::complete, this, [this, waylandoutput, name]{ m_outputMap.insert(waylandoutput->id(), waylandoutput); m_initializingOutputs.removeAll(name); checkInitialized(); if (!m_blockSignals && m_initializingOutputs.empty()) { m_screen->setOutputs(m_outputMap.values()); Q_EMIT configChanged(toKScreenConfig()); } connect(waylandoutput, &WaylandOutput::changed, this, [this]() { if (!m_blockSignals) { Q_EMIT configChanged(toKScreenConfig()); } }); }); } void WaylandConfig::checkInitialized() { if (!m_blockSignals && m_registryInitialized && m_initializingOutputs.isEmpty() && m_outputMap.count() && m_outputManagement != nullptr) { m_screen->setOutputs(m_outputMap.values()); Q_EMIT initialized(); } } KScreen::ConfigPtr WaylandConfig::toKScreenConfig() { if (m_kscreenConfig == nullptr) { m_kscreenConfig = KScreen::ConfigPtr(new Config); } m_kscreenConfig->setScreen(m_screen->toKScreenScreen(m_kscreenConfig)); updateKScreenConfig(m_kscreenConfig); return m_kscreenConfig; } void WaylandConfig::removeOutput(quint32 name) { const int kscreen_id = m_outputIds[name]; auto output = m_outputMap.take(kscreen_id); m_screen->setOutputs(m_outputMap.values()); delete output; if (!m_blockSignals) { Q_EMIT configChanged(toKScreenConfig()); } } void WaylandConfig::updateKScreenConfig(KScreen::ConfigPtr &config) const { auto features = Config::Feature::Writable | Config::Feature::PerOutputScaling; config->setSupportedFeatures(features); config->setValid(m_connection->display()); KScreen::ScreenPtr screen = config->screen(); m_screen->updateKScreenScreen(screen); //Removing removed outputs const KScreen::OutputList outputs = config->outputs(); Q_FOREACH (const KScreen::OutputPtr &output, outputs) { if (!m_outputMap.contains(output->id())) { config->removeOutput(output->id()); } } // Add KScreen::Outputs that aren't in the list yet, handle primaryOutput KScreen::OutputList kscreenOutputs = config->outputs(); Q_FOREACH (const auto &output, m_outputMap) { KScreen::OutputPtr kscreenOutput = kscreenOutputs[output->id()]; if (!kscreenOutput) { kscreenOutput = output->toKScreenOutput(); kscreenOutputs.insert(kscreenOutput->id(), kscreenOutput); } if (kscreenOutput && m_outputMap.count() == 1) { kscreenOutput->setPrimary(true); } else if (m_outputMap.count() > 1) { // primaryScreen concept doesn't exist in kwayland, so we don't set one //qCWarning(KSCREEN_WAYLAND) << "Multiple outputs, but no way to figure out the primary one. :/"; } output->updateKScreenOutput(kscreenOutput); } config->setOutputs(kscreenOutputs); } QMap WaylandConfig::outputMap() const { return m_outputMap; } void WaylandConfig::tryPendingConfig() { if (!m_kscreenPendingConfig) { return; } applyConfig(m_kscreenPendingConfig); m_kscreenPendingConfig = nullptr; } void WaylandConfig::applyConfig(const KScreen::ConfigPtr &newConfig) { using namespace KWayland::Client; // Create a new configuration object auto wlOutputConfiguration = m_outputManagement->createConfiguration(); bool changed = false; if (m_blockSignals) { /* Last apply still pending, remember new changes and apply afterwards */ m_kscreenPendingConfig = newConfig; return; } Q_FOREACH (auto output, newConfig->outputs()) { auto o_old = m_outputMap[output->id()]; auto device = o_old->outputDevice(); Q_ASSERT(o_old != nullptr); // enabled? bool old_enabled = (o_old->outputDevice()->enabled() == OutputDevice::Enablement::Enabled); if (old_enabled != output->isEnabled()) { changed = true; auto _enablement = output->isEnabled() ? OutputDevice::Enablement::Enabled : OutputDevice::Enablement::Disabled; wlOutputConfiguration->setEnabled(o_old->outputDevice(), _enablement); } // position if (device->globalPosition() != output->pos()) { changed = true; wlOutputConfiguration->setPosition(o_old->outputDevice(), output->pos()); } - if (device->scale() != output->scale()) { + if (!qFuzzyCompare(device->scale(), output->scale())) { changed = true; wlOutputConfiguration->setScale(o_old->outputDevice(), output->scale()); } // rotation auto r_current = o_old->toKScreenRotation(device->transform()); auto r_new = output->rotation(); if (r_current != r_new) { changed = true; wlOutputConfiguration->setTransform(device, o_old->toKWaylandTransform(r_new)); } // mode int w_currentmodeid = device->currentMode().id; QString l_newmodeid = output->currentModeId(); int w_newmodeid = o_old->toKWaylandModeId(l_newmodeid); if (w_newmodeid != w_currentmodeid) { changed = true; wlOutputConfiguration->setMode(device, w_newmodeid); } } if (!changed) { return; } // We now block changes in order to compress events while the compositor is doing its thing // once it's done or failed, we'll trigger configChanged() only once, and not per individual // property change. connect(wlOutputConfiguration, &OutputConfiguration::applied, this, [this, wlOutputConfiguration] { wlOutputConfiguration->deleteLater(); unblockSignals(); Q_EMIT configChanged(toKScreenConfig()); tryPendingConfig(); }); connect(wlOutputConfiguration, &OutputConfiguration::failed, this, [this, wlOutputConfiguration] { wlOutputConfiguration->deleteLater(); unblockSignals(); Q_EMIT configChanged(toKScreenConfig()); tryPendingConfig(); }); blockSignals(); // Now ask the compositor to apply the changes wlOutputConfiguration->apply(); } diff --git a/src/mode.cpp b/src/mode.cpp index 45aec6e..d55b87d 100644 --- a/src/mode.cpp +++ b/src/mode.cpp @@ -1,140 +1,140 @@ /************************************************************************************* * 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 "mode.h" using namespace KScreen; class Q_DECL_HIDDEN Mode::Private { public: Private(): rate(0) { } Private(const Private &other): id(other.id), name(other.name), size(other.size), rate(other.rate) { } QString id; QString name; QSize size; float rate; }; Mode::Mode() : QObject(nullptr) , d(new Private()) { } Mode::Mode(Mode::Private *dd) : QObject() , d(dd) { } Mode::~Mode() { delete d; } ModePtr Mode::clone() const { return ModePtr(new Mode(new Private(*d))); } const QString Mode::id() const { return d->id; } void Mode::setId(const QString& id) { if (d->id == id) { return; } d->id = id; Q_EMIT modeChanged(); } QString Mode::name() const { return d->name; } void Mode::setName(const QString& name) { if (d->name == name) { return; } d->name = name; Q_EMIT modeChanged(); } QSize Mode::size() const { return d->size; } void Mode::setSize(const QSize& size) { if (d->size == size) { return; } d->size = size; Q_EMIT modeChanged(); } float Mode::refreshRate() const { return d->rate; } void Mode::setRefreshRate(float refresh) { - if (d->rate == refresh) { + if (qFuzzyCompare(d->rate, refresh)) { return; } d->rate = refresh; Q_EMIT modeChanged(); } QDebug operator<<(QDebug dbg, const KScreen::ModePtr &mode) { if (mode) { dbg << "KScreen::Mode(Id:" << mode->id() << ", Size:" << mode->size() << "@" << mode->refreshRate() << ")"; } else { dbg << "KScreen::Mode(NULL)"; } return dbg; } diff --git a/src/output.cpp b/src/output.cpp index 767d28c..78762f1 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1,596 +1,596 @@ /************************************************************************************* * 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 Q_DECL_HIDDEN Output::Private { public: Private(): id(0), type(Unknown), rotation(None), scale(1.0), connected(false), enabled(false), primary(false), edid(nullptr) {} 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()) { + if (!qFuzzyCompare(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 QString(); } return biggest->id(); } Output::Output() : QObject(nullptr) , 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) { + if (qFuzzyCompare(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.isNull()); 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 QSize size = currentMode()->size() / d->scale; if (!isHorizontal()) { size = size.transposed(); } return QRect(d->pos, size); } 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->scale != other->d->scale) { + if (!qFuzzyCompare(d->scale, other->d->scale)) { changes << &Output::scaleChanged; setScale(other->d->scale); } 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->isPrimary() ? "primary" : "") << "pos:" << output->pos() << "res:" << output->size() << "modeId:" << output->currentModeId() << "scale:" << output->scale() << ")"; } else { dbg << "KScreen::Output(NULL)"; } return dbg; }