diff --git a/backends/kwayland/waylandconfig.cpp b/backends/kwayland/waylandconfig.cpp index b5d2446..bf1b605 100644 --- a/backends/kwayland/waylandconfig.cpp +++ b/backends/kwayland/waylandconfig.cpp @@ -1,359 +1,318 @@ /************************************************************************************* * 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 "waylandbackend.h" #include "waylandoutput.h" #include "waylandscreen.h" #include #include #include #include #include #include -#include #include #include using namespace KScreen; WaylandConfig::WaylandConfig(QObject *parent) : QObject(parent) , m_outputManagement(nullptr) , m_registryInitialized(false) , m_blockSignals(true) , m_kscreenConfig(new Config) , 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_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(); } 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::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(); } int s_outputId = 0; void WaylandConfig::addOutput(quint32 name, quint32 version) { WaylandOutput *waylandoutput = new WaylandOutput(++s_outputId, this); m_initializingOutputs << waylandoutput; connect(waylandoutput, &WaylandOutput::deviceRemoved, this, [this, waylandoutput]() { removeOutput(waylandoutput); }); waylandoutput->createOutputDevice(m_registry, 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]{ m_outputMap.insert(waylandoutput->id(), waylandoutput); m_initializingOutputs.removeOne(waylandoutput); checkInitialized(); if (!m_blockSignals && m_initializingOutputs.empty()) { m_screen->setOutputs(m_outputMap.values()); Q_EMIT configChanged(); } connect(waylandoutput, &WaylandOutput::changed, this, [this]() { if (!m_blockSignals) { Q_EMIT configChanged(); } }); }); } void WaylandConfig::removeOutput(WaylandOutput *output) { if (m_initializingOutputs.removeOne(output)) { // output was not yet fully initialized, just remove here and return delete output; return; } // remove the output from output mapping Q_ASSERT(m_outputMap.take(output->id()) == output); m_screen->setOutputs(m_outputMap.values()); delete output; if (!m_blockSignals) { Q_EMIT configChanged(); } } 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::currentConfig() { // TODO: do this setScreen call less clunky m_kscreenConfig->setScreen(m_screen->toKScreenScreen(m_kscreenConfig)); auto features = Config::Feature::Writable | Config::Feature::PerOutputScaling; m_kscreenConfig->setSupportedFeatures(features); m_kscreenConfig->setValid(m_connection->display()); KScreen::ScreenPtr screen = m_kscreenConfig->screen(); m_screen->updateKScreenScreen(screen); //Removing removed outputs const KScreen::OutputList outputs = m_kscreenConfig->outputs(); for (const auto &output : outputs) { if (!m_outputMap.contains(output->id())) { m_kscreenConfig->removeOutput(output->id()); } } // Add KScreen::Outputs that aren't in the list yet, handle primaryOutput KScreen::OutputList kscreenOutputs = m_kscreenConfig->outputs(); for (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 } output->updateKScreenOutput(kscreenOutput); } m_kscreenConfig->setOutputs(kscreenOutputs); return m_kscreenConfig; } 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 wlConfig = m_outputManagement->createConfiguration(); bool changed = false; if (m_blockSignals) { /* Last apply still pending, remember new changes and apply afterwards */ m_kscreenPendingConfig = newConfig; return; } for (const 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; - wlConfig->setEnabled(o_old->outputDevice(), _enablement); - } - - // position - if (device->globalPosition() != output->pos()) { - changed = true; - wlConfig->setPosition(o_old->outputDevice(), output->pos()); - } - - // scale - if (!qFuzzyCompare(device->scaleF(), output->scale())) { - changed = true; - wlConfig->setScaleF(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; - wlConfig->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; - wlConfig->setMode(device, w_newmodeid); - } + changed |= m_outputMap[output->id()]->setWlConfig(wlConfig, output); } 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(wlConfig, &OutputConfiguration::applied, this, [this, wlConfig] { wlConfig->deleteLater(); unblockSignals(); Q_EMIT configChanged(); tryPendingConfig(); }); connect(wlConfig, &OutputConfiguration::failed, this, [this, wlConfig] { wlConfig->deleteLater(); unblockSignals(); Q_EMIT configChanged(); tryPendingConfig(); }); // Now block signals and ask the compositor to apply the changes. blockSignals(); wlConfig->apply(); } diff --git a/backends/kwayland/waylandoutput.cpp b/backends/kwayland/waylandoutput.cpp index bc12110..1a87f1c 100644 --- a/backends/kwayland/waylandoutput.cpp +++ b/backends/kwayland/waylandoutput.cpp @@ -1,196 +1,225 @@ /************************************************************************************* * Copyright 2014-2015 Sebastian Kügler * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) 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 "waylandoutput.h" #include "waylandbackend.h" #include "waylandconfig.h" #include "../utils.h" #include #include +#include +#include + using namespace KScreen; namespace Wl = KWayland::Client; const QMap s_rotationMap = { {Wl::OutputDevice::Transform::Normal, Output::None}, {Wl::OutputDevice::Transform::Rotated90, Output::Right}, {Wl::OutputDevice::Transform::Rotated180, Output::Inverted}, {Wl::OutputDevice::Transform::Rotated270, Output::Left}, {Wl::OutputDevice::Transform::Flipped, Output::None}, {Wl::OutputDevice::Transform::Flipped90, Output::Right}, {Wl::OutputDevice::Transform::Flipped180, Output::Inverted}, {Wl::OutputDevice::Transform::Flipped270, Output::Left} }; -WaylandOutput::WaylandOutput(quint32 id, WaylandConfig *parent) - : QObject(parent) - , m_id(id) - , m_output(nullptr) -{ -} - -Output::Rotation -WaylandOutput::toKScreenRotation(const Wl::OutputDevice::Transform transform) const +Output::Rotation toKScreenRotation(const Wl::OutputDevice::Transform transform) { auto it = s_rotationMap.constFind(transform); return it.value(); } -Wl::OutputDevice::Transform -WaylandOutput::toKWaylandTransform(const Output::Rotation rotation) const +Wl::OutputDevice::Transform toKWaylandTransform(const Output::Rotation rotation) { return s_rotationMap.key(rotation); } -QString WaylandOutput::toKScreenModeId(int kwaylandmodeid) const -{ - auto it = std::find(m_modeIdMap.constBegin(), m_modeIdMap.constEnd(), kwaylandmodeid); - if (it == m_modeIdMap.constEnd()) { - qCWarning(KSCREEN_WAYLAND) << "Invalid kwayland mode id:" << kwaylandmodeid << m_modeIdMap; - return QStringLiteral("invalid_mode_id"); - } - return it.key(); -} - -int WaylandOutput::toKWaylandModeId(const QString &kscreenmodeid) const +WaylandOutput::WaylandOutput(quint32 id, WaylandConfig *parent) + : QObject(parent) + , m_id(id) + , m_output(nullptr) { - if (!m_modeIdMap.contains(kscreenmodeid)) { - qCWarning(KSCREEN_WAYLAND) << "Invalid kscreen mode id:" << kscreenmodeid << m_modeIdMap; - } - return m_modeIdMap.value(kscreenmodeid, -1); } quint32 WaylandOutput::id() const { Q_ASSERT(m_output); return m_id; } bool WaylandOutput::enabled() const { return m_output != nullptr; } Wl::OutputDevice* WaylandOutput::outputDevice() const { return m_output; } void WaylandOutput::createOutputDevice(Wl::Registry *registry, quint32 name, quint32 version) { Q_ASSERT(!m_output); m_output = registry->createOutputDevice(name, version); connect(m_output, &Wl::OutputDevice::removed, this, &WaylandOutput::deviceRemoved); connect(m_output, &Wl::OutputDevice::done, this, [this]() { Q_EMIT complete(); connect(m_output, &Wl::OutputDevice::changed, this, &WaylandOutput::changed); }); } OutputPtr WaylandOutput::toKScreenOutput() { OutputPtr output(new Output()); output->setId(m_id); updateKScreenOutput(output); return output; } void WaylandOutput::updateKScreenOutput(OutputPtr &output) { // Initialize primary output output->setId(m_id); output->setEnabled(m_output->enabled() == Wl::OutputDevice::Enablement::Enabled); output->setConnected(true); output->setPrimary(true); // FIXME: wayland doesn't have the concept of a primary display output->setName(name()); output->setSizeMm(m_output->physicalSize()); output->setPos(m_output->globalPosition()); output->setRotation(s_rotationMap[m_output->transform()]); ModeList modeList; QStringList preferredModeIds; m_modeIdMap.clear(); QString currentModeId = QStringLiteral("-1"); for (const Wl::OutputDevice::Mode &wlMode : m_output->modes()) { ModePtr mode(new Mode()); const QString name = modeName(wlMode); QString modeId = QString::number(wlMode.id); if (modeId.isEmpty()) { qCDebug(KSCREEN_WAYLAND) << "Could not create mode id from" << wlMode.id << ", using" << name << "instead."; modeId = name; } if (m_modeIdMap.contains(modeId)) { qCWarning(KSCREEN_WAYLAND) << "Mode id already in use:" << modeId; } mode->setId(modeId); // KWayland gives the refresh rate as int in mHz mode->setRefreshRate(wlMode.refreshRate / 1000.0); mode->setSize(wlMode.size); mode->setName(name); if (wlMode.flags.testFlag(Wl::OutputDevice::Mode::Flag::Current)) { currentModeId = modeId; } if (wlMode.flags.testFlag(Wl::OutputDevice::Mode::Flag::Preferred)) { preferredModeIds << modeId; } // Update the kscreen => kwayland mode id translation map m_modeIdMap.insert(modeId, wlMode.id); // Add to the modelist which gets set on the output modeList[modeId] = mode; } if (currentModeId == QLatin1String("-1")) { qCWarning(KSCREEN_WAYLAND) << "Could not find the current mode id" << modeList; } output->setCurrentModeId(currentModeId); output->setPreferredModes(preferredModeIds); output->setModes(modeList); output->setScale(m_output->scale()); output->setType(Utils::guessOutputType(m_output->model(), m_output->model())); } +bool WaylandOutput::setWlConfig(Wl::OutputConfiguration *wlConfig, + const KScreen::OutputPtr &output) +{ + bool changed = false; + + // enabled? + if ((m_output->enabled() == Wl::OutputDevice::Enablement::Enabled) + != output->isEnabled()) { + changed = true; + const auto enablement = output->isEnabled() ? Wl::OutputDevice::Enablement::Enabled : + Wl::OutputDevice::Enablement::Disabled; + wlConfig->setEnabled(m_output, enablement); + } + + // position + if (m_output->globalPosition() != output->pos()) { + changed = true; + wlConfig->setPosition(m_output, output->pos()); + } + + // scale + if (!qFuzzyCompare(m_output->scaleF(), output->scale())) { + changed = true; + wlConfig->setScaleF(m_output, output->scale()); + } + + // rotation + if (toKScreenRotation(m_output->transform()) != output->rotation()) { + changed = true; + wlConfig->setTransform(m_output, toKWaylandTransform(output->rotation())); + } + + // mode + if (m_modeIdMap.contains(output->currentModeId())) { + const int newModeId = m_modeIdMap.value(output->currentModeId(), -1); + if (newModeId != m_output->currentMode().id) { + changed = true; + wlConfig->setMode(m_output, newModeId); + } + } else { + qCWarning(KSCREEN_WAYLAND) << "Invalid kscreen mode id:" << output->currentModeId() + << "\n\n" << m_modeIdMap; + } + return changed; +} + QString WaylandOutput::modeName(const Wl::OutputDevice::Mode &m) const { return QString::number(m.size.width()) + QLatin1Char('x') + QString::number(m.size.height()) + QLatin1Char('@') + QString::number(qRound(m.refreshRate/1000.0)); } QString WaylandOutput::name() const { Q_ASSERT(m_output); return QStringLiteral("%1 %2").arg(m_output->manufacturer(), m_output->model()); } QDebug operator<<(QDebug dbg, const WaylandOutput *output) { dbg << "WaylandOutput(Id:" << output->id() <<", Name:" << \ QString(output->outputDevice()->manufacturer() + QLatin1Char(' ') + \ output->outputDevice()->model()) << ")"; return dbg; } diff --git a/backends/kwayland/waylandoutput.h b/backends/kwayland/waylandoutput.h index dc6e001..b70e0e2 100644 --- a/backends/kwayland/waylandoutput.h +++ b/backends/kwayland/waylandoutput.h @@ -1,83 +1,85 @@ /************************************************************************************* * Copyright 2014-2015 Sebastian Kügler * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) 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 * *************************************************************************************/ #pragma once #include "waylandconfig.h" #include "abstractbackend.h" #include "output.h" #include #include #include #include #include +namespace KWayland +{ +namespace Client +{ +class OutputConfiguration; +} +} + namespace KScreen { class WaylandOutput : public QObject { Q_OBJECT public: explicit WaylandOutput(quint32 id, WaylandConfig *parent = nullptr); ~WaylandOutput() override = default; KScreen::OutputPtr toKScreenOutput(); void updateKScreenOutput(KScreen::OutputPtr &output); quint32 id() const; QString name() const; bool enabled() const; KWayland::Client::OutputDevice* outputDevice() const; void createOutputDevice(KWayland::Client::Registry *registry, quint32 name, quint32 version); - // translation methods - KScreen::Output::Rotation toKScreenRotation( - const KWayland::Client::OutputDevice::Transform transform) const; - KWayland::Client::OutputDevice::Transform toKWaylandTransform( - const KScreen::Output::Rotation rotation) const; - - QString toKScreenModeId(int kwaylandmodeid) const; - int toKWaylandModeId(const QString &kscreenmodeid) const; + bool setWlConfig(KWayland::Client::OutputConfiguration *wlConfig, + const KScreen::OutputPtr &output); Q_SIGNALS: void deviceRemoved(); void complete(); // only emitted after complete signal void changed(); private: void showOutput(); QString modeName(const KWayland::Client::OutputDevice::Mode &m) const; quint32 m_id; KWayland::Client::OutputDevice *m_output; KWayland::Client::Registry *m_registry; // left-hand-side: KScreen::Mode, right-hand-side: KWayland's mode.id QMap m_modeIdMap; }; } KSCREEN_EXPORT QDebug operator<<(QDebug dbg, const KScreen::WaylandOutput *output);