diff --git a/backends/kwayland/waylandbackend.cpp b/backends/kwayland/waylandbackend.cpp index 80232e8..98ffce8 100644 --- a/backends/kwayland/waylandbackend.cpp +++ b/backends/kwayland/waylandbackend.cpp @@ -1,85 +1,84 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2012, 2013 by Daniel Vrátil * * 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 "waylandbackend.h" #include "waylandconfig.h" #include "waylandoutput.h" #include #include #include #include using namespace KScreen; Q_LOGGING_CATEGORY(KSCREEN_WAYLAND, "kscreen.kwayland") WaylandBackend::WaylandBackend() : KScreen::AbstractBackend() - , m_isValid(true) , m_internalConfig(new WaylandConfig(this)) { qCDebug(KSCREEN_WAYLAND) << "Loading Wayland backend."; connect(m_internalConfig, &WaylandConfig::configChanged, this, [this]() { Q_EMIT configChanged(m_internalConfig->currentConfig()); }); } QString WaylandBackend::name() const { return QStringLiteral("kwayland"); } QString WaylandBackend::serviceName() const { return QStringLiteral("org.kde.KScreen.Backend.KWayland"); } ConfigPtr WaylandBackend::config() const { // Note: This should ONLY be called from GetConfigOperation! return m_internalConfig->currentConfig(); } void WaylandBackend::setConfig(const KScreen::ConfigPtr &newconfig) { if (!newconfig) { return; } m_internalConfig->applyConfig(newconfig); } QByteArray WaylandBackend::edid(int outputId) const { WaylandOutput *output = m_internalConfig->outputMap().value(outputId); if (!output) { return QByteArray(); } return output->outputDevice()->edid(); } bool WaylandBackend::isValid() const { - return m_isValid; + return m_internalConfig->isInitialized(); } diff --git a/backends/kwayland/waylandbackend.h b/backends/kwayland/waylandbackend.h index e52169f..3f21aed 100644 --- a/backends/kwayland/waylandbackend.h +++ b/backends/kwayland/waylandbackend.h @@ -1,54 +1,53 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2012, 2013 by Daniel Vrátil * * 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 "abstractbackend.h" #include namespace KScreen { class WaylandConfig; class WaylandBackend : public KScreen::AbstractBackend { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kf5.kscreen.backends.kwayland") public: explicit WaylandBackend(); ~WaylandBackend() override = default; QString name() const override; QString serviceName() const override; KScreen::ConfigPtr config() const override; void setConfig(const KScreen::ConfigPtr &config) override; bool isValid() const override; QByteArray edid(int outputId) const override; private: - bool m_isValid; WaylandConfig *m_internalConfig; }; } Q_DECLARE_LOGGING_CATEGORY(KSCREEN_WAYLAND) diff --git a/backends/kwayland/waylandconfig.cpp b/backends/kwayland/waylandconfig.cpp index 9ad041d..e182d02 100644 --- a/backends/kwayland/waylandconfig.cpp +++ b/backends/kwayland/waylandconfig.cpp @@ -1,367 +1,375 @@ /************************************************************************************* * 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 "tabletmodemanager_interface.h" #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)) , m_tabletModeAvailable(false) , m_tabletModeEngaged(false) { initKWinTabletMode(); connect(this, &WaylandConfig::initialized, &m_syncLoop, &QEventLoop::quit); - QTimer::singleShot(1000, this, [this] { + QTimer::singleShot(3000, 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::initKWinTabletMode() { auto *interface = new OrgKdeKWinTabletModeManagerInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/org/kde/KWin"), QDBusConnection::sessionBus(), this); if (!interface->isValid()) { m_tabletModeAvailable = false; m_tabletModeEngaged = false; return; } m_tabletModeAvailable = interface->tabletModeAvailable(); m_tabletModeEngaged = interface->tabletMode(); connect(interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeChanged, this, [this](bool tabletMode) { if (m_tabletModeEngaged == tabletMode) { return; } m_tabletModeEngaged = tabletMode; if (!m_blockSignals && m_initializingOutputs.empty()) { Q_EMIT configChanged(); } } ); connect(interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeAvailableChanged, this, [this](bool available) { if (m_tabletModeAvailable == available) { return; } m_tabletModeAvailable = available; if (!m_blockSignals && m_initializingOutputs.empty()) { Q_EMIT configChanged(); } }); } 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 const auto removedOutput = m_outputMap.take(output->id()); Q_ASSERT(removedOutput == output); Q_UNUSED(removedOutput); m_screen->setOutputs(m_outputMap.values()); delete output; if (!m_blockSignals) { Q_EMIT configChanged(); } } +bool WaylandConfig::isInitialized() const +{ + return !m_blockSignals + && m_registryInitialized + && m_initializingOutputs.isEmpty() + && m_outputMap.count() > 0 + && m_outputManagement != nullptr; +} + void WaylandConfig::checkInitialized() { - if (!m_blockSignals && m_registryInitialized && - m_initializingOutputs.isEmpty() && m_outputMap.count() && m_outputManagement != nullptr) { + if (isInitialized()) { 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; // TODO: enable new features when all patches have landed // const auto features = Config::Feature::Writable | Config::Feature::PerOutputScaling // | Config::Feature::AutoRotation | Config::Feature::TabletMode; 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); m_kscreenConfig->setTabletModeAvailable(m_tabletModeAvailable); m_kscreenConfig->setTabletModeEngaged(m_tabletModeEngaged); 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()) { 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/waylandconfig.h b/backends/kwayland/waylandconfig.h index 36353b8..0986bc4 100644 --- a/backends/kwayland/waylandconfig.h +++ b/backends/kwayland/waylandconfig.h @@ -1,118 +1,120 @@ /************************************************************************************* * 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 "abstractbackend.h" #include "config.h" #include #include #include #include #include #include #include namespace KWayland { namespace Client { class ConnectionThread; class EventQueue; class Registry; class OutputManagement; } } namespace KScreen { class Output; class WaylandOutput; class WaylandScreen; /** * @class WaylandConfig * * This class holds the basic skeleton of the configuration and takes care of * fetching the information from the Wayland server and synchronizing the * configuration out to the "clients" that receive the config from the backend. * We initialize a wayland connection, using a threaded event queue when * querying the wayland server for data. * Initially, the creation of a WaylandConfig blocks until all data has been * received, signalled by the initialized() signal. This means that the * wayland client has received information about all interfaces, and that all * outputs are completely initialized. From then on, we properly notifyUpdate(). */ class WaylandConfig : public QObject { Q_OBJECT public: explicit WaylandConfig(QObject *parent = nullptr); ~WaylandConfig() override; KScreen::ConfigPtr currentConfig(); QMap outputMap() const; void applyConfig(const KScreen::ConfigPtr &newConfig); + bool isInitialized() const; + Q_SIGNALS: void configChanged(); void initialized(); private: void setupRegistry(); void checkInitialized(); void disconnected(); void initKWinTabletMode(); void initConnection(); void addOutput(quint32 name, quint32 version); void removeOutput(WaylandOutput *output); void blockSignals(); void unblockSignals(); void tryPendingConfig(); KWayland::Client::ConnectionThread *m_connection; KWayland::Client::EventQueue *m_queue; QThread *m_thread; KWayland::Client::Registry *m_registry; KWayland::Client::OutputManagement *m_outputManagement; // KWayland names as keys QMap m_outputMap; // KWayland names QList m_initializingOutputs; int m_lastOutputId = -1; bool m_registryInitialized; bool m_blockSignals; QEventLoop m_syncLoop; KScreen::ConfigPtr m_kscreenConfig; KScreen::ConfigPtr m_kscreenPendingConfig; WaylandScreen *m_screen; bool m_tabletModeAvailable; bool m_tabletModeEngaged; }; }