diff --git a/backends/kwayland/waylandconfig.cpp b/backends/kwayland/waylandconfig.cpp index ef9e201..51b19dd 100644 --- a/backends/kwayland/waylandconfig.cpp +++ b/backends/kwayland/waylandconfig.cpp @@ -1,366 +1,366 @@ /************************************************************************************* * 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) { - connect(this, &WaylandConfig::initialized, &m_syncLoop, &QEventLoop::quit); + initKWinTabletMode(); + 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(); } }); - initKWinTabletMode(); 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 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; // 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(); }