Warning, file /plasma/libkscreen/backends/kwayland/waylandconfig.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
0003  *  SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0004  *  SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
0005  *
0006  *  SPDX-License-Identifier: LGPL-2.1-or-later
0007  */
0008 #include "waylandconfig.h"
0009 
0010 #include "kscreen_kwayland_logging.h"
0011 
0012 #include "waylandbackend.h"
0013 #include "waylandoutputdevice.h"
0014 #include "waylandoutputmanagement.h"
0015 #include "waylandscreen.h"
0016 
0017 #include "tabletmodemanager_interface.h"
0018 
0019 #include <QThread>
0020 #include <QTimer>
0021 #include <configmonitor.h>
0022 #include <mode.h>
0023 #include <output.h>
0024 
0025 #include <KWayland/Client/connection_thread.h>
0026 #include <KWayland/Client/event_queue.h>
0027 #include <KWayland/Client/registry.h>
0028 
0029 #include <utility>
0030 
0031 using namespace KScreen;
0032 
0033 WaylandConfig::WaylandConfig(QObject *parent)
0034     : QObject(parent)
0035     , m_outputManagement(nullptr)
0036     , m_registryInitialized(false)
0037     , m_blockSignals(true)
0038     , m_kscreenConfig(new Config)
0039     , m_kscreenPendingConfig(nullptr)
0040     , m_screen(new WaylandScreen(this))
0041     , m_tabletModeAvailable(false)
0042     , m_tabletModeEngaged(false)
0043 {
0044     initKWinTabletMode();
0045 
0046     connect(this, &WaylandConfig::initialized, &m_syncLoop, &QEventLoop::quit);
0047     QTimer::singleShot(3000, this, [this] {
0048         if (m_syncLoop.isRunning()) {
0049             qCWarning(KSCREEN_WAYLAND) << "Connection to Wayland server timed out.";
0050             m_syncLoop.quit();
0051         }
0052     });
0053 
0054     initConnection();
0055     m_syncLoop.exec();
0056 }
0057 
0058 WaylandConfig::~WaylandConfig()
0059 {
0060     m_syncLoop.quit();
0061 }
0062 
0063 void WaylandConfig::initKWinTabletMode()
0064 {
0065     auto *interface =
0066         new OrgKdeKWinTabletModeManagerInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/org/kde/KWin"), QDBusConnection::sessionBus(), this);
0067     if (!interface->isValid()) {
0068         m_tabletModeAvailable = false;
0069         m_tabletModeEngaged = false;
0070         return;
0071     }
0072 
0073     m_tabletModeAvailable = interface->tabletModeAvailable();
0074     m_tabletModeEngaged = interface->tabletMode();
0075 
0076     connect(interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeChanged, this, [this](bool tabletMode) {
0077         if (m_tabletModeEngaged == tabletMode) {
0078             return;
0079         }
0080         m_tabletModeEngaged = tabletMode;
0081         if (!m_blockSignals && m_initializingOutputs.empty()) {
0082             Q_EMIT configChanged();
0083         }
0084     });
0085     connect(interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeAvailableChanged, this, [this](bool available) {
0086         if (m_tabletModeAvailable == available) {
0087             return;
0088         }
0089         m_tabletModeAvailable = available;
0090         if (!m_blockSignals && m_initializingOutputs.empty()) {
0091             Q_EMIT configChanged();
0092         }
0093     });
0094 }
0095 
0096 void WaylandConfig::initConnection()
0097 {
0098     m_connection = KWayland::Client::ConnectionThread::fromApplication(this);
0099     setupRegistry();
0100 }
0101 
0102 void WaylandConfig::blockSignals()
0103 {
0104     Q_ASSERT(m_blockSignals == false);
0105     m_blockSignals = true;
0106 }
0107 
0108 void WaylandConfig::unblockSignals()
0109 {
0110     Q_ASSERT(m_blockSignals == true);
0111     m_blockSignals = false;
0112 }
0113 
0114 void WaylandConfig::setupRegistry()
0115 {
0116     if (!m_connection) {
0117         return;
0118     }
0119 
0120     m_registry = new KWayland::Client::Registry(this);
0121 
0122     connect(m_registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this](const QByteArray &interface, quint32 name, quint32 version) {
0123         if (interface == WaylandOutputDevice::interface()->name) {
0124             addOutput(name, std::min(2u, version));
0125         }
0126         if (interface == WaylandOutputManagement::interface()->name) {
0127             m_outputManagement = new WaylandOutputManagement(m_registry->registry(), name, std::min(3u, version));
0128         }
0129         if (interface == WaylandOutputOrder::interface()->name) {
0130             m_outputOrder = std::make_unique<WaylandOutputOrder>(m_registry->registry(), name, std::min(1u, version));
0131             connect(m_outputOrder.get(), &WaylandOutputOrder::outputOrderChanged, this, [this](const QVector<QString> &names) {
0132                 bool change = false;
0133                 for (const auto &output : std::as_const(m_outputMap)) {
0134                     const uint32_t newIndex = names.indexOf(output->name()) + 1;
0135                     change = change || output->index() != newIndex;
0136                     output->setIndex(newIndex);
0137                 }
0138                 if (change && !m_blockSignals) {
0139                     Q_EMIT configChanged();
0140                 }
0141             });
0142         }
0143     });
0144 
0145     connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, this, [this] {
0146         m_registryInitialized = true;
0147         unblockSignals();
0148         checkInitialized();
0149     });
0150 
0151     m_registry->create(m_connection);
0152     m_registry->setup();
0153 }
0154 
0155 int s_outputId = 0;
0156 
0157 void WaylandConfig::addOutput(quint32 name, quint32 version)
0158 {
0159     qCDebug(KSCREEN_WAYLAND) << "adding output" << name;
0160 
0161     auto device = new WaylandOutputDevice(++s_outputId);
0162     m_initializingOutputs << device;
0163 
0164     connect(m_registry, &KWayland::Client::Registry::interfaceRemoved, this, [name, device, this](const quint32 &interfaceName) {
0165         if (name == interfaceName) {
0166             removeOutput(device);
0167         }
0168     });
0169 
0170     QMetaObject::Connection *const connection = new QMetaObject::Connection;
0171     *connection = connect(device, &WaylandOutputDevice::done, this, [this, connection, device]() {
0172         QObject::disconnect(*connection);
0173         delete connection;
0174 
0175         m_initializingOutputs.removeOne(device);
0176         m_outputMap.insert(device->id(), device);
0177         if (m_outputOrder) {
0178             device->setIndex(m_outputOrder->order().indexOf(device->name()) + 1);
0179         }
0180         checkInitialized();
0181 
0182         if (m_initializingOutputs.isEmpty()) {
0183             m_screen->setOutputs(m_outputMap.values());
0184         }
0185         if (!m_blockSignals && m_initializingOutputs.isEmpty()) {
0186             Q_EMIT configChanged();
0187         }
0188 
0189         connect(device, &WaylandOutputDevice::done, this, [this]() {
0190             // output got update must update current config
0191             if (!m_blockSignals) {
0192                 Q_EMIT configChanged();
0193             }
0194         });
0195     });
0196 
0197     device->init(*m_registry, name, version);
0198 }
0199 
0200 void WaylandConfig::removeOutput(WaylandOutputDevice *output)
0201 {
0202     qCDebug(KSCREEN_WAYLAND) << "removing output" << output->name();
0203 
0204     if (m_initializingOutputs.removeOne(output)) {
0205         // output was not yet fully initialized, just remove here and return
0206         delete output;
0207         return;
0208     }
0209 
0210     // remove the output from output mapping
0211     const auto removedOutput = m_outputMap.take(output->id());
0212     Q_ASSERT(removedOutput == output);
0213     Q_UNUSED(removedOutput);
0214     m_screen->setOutputs(m_outputMap.values());
0215     delete output;
0216 
0217     if (!m_blockSignals) {
0218         Q_EMIT configChanged();
0219     }
0220 }
0221 
0222 bool WaylandConfig::isReady() const
0223 {
0224     // clang-format off
0225     return !m_blockSignals
0226             && m_registryInitialized
0227             && m_initializingOutputs.isEmpty()
0228             && m_outputMap.count() > 0
0229             && m_outputManagement != nullptr;
0230     // clang-format on
0231 }
0232 
0233 void WaylandConfig::checkInitialized()
0234 {
0235     if (!m_initialized && isReady()) {
0236         m_initialized = true;
0237         m_screen->setOutputs(m_outputMap.values());
0238         Q_EMIT initialized();
0239     }
0240 }
0241 
0242 KScreen::ConfigPtr WaylandConfig::currentConfig()
0243 {
0244     m_kscreenConfig->setScreen(m_screen->toKScreenScreen(m_kscreenConfig));
0245 
0246     const auto features = Config::Feature::Writable | Config::Feature::PerOutputScaling | Config::Feature::AutoRotation | Config::Feature::TabletMode
0247         | Config::Feature::PrimaryDisplay | Config::Feature::XwaylandScales | Config::Feature::SynchronousOutputChanges;
0248     m_kscreenConfig->setSupportedFeatures(features);
0249     m_kscreenConfig->setValid(m_connection->display());
0250 
0251     KScreen::ScreenPtr screen = m_kscreenConfig->screen();
0252     m_screen->updateKScreenScreen(screen);
0253 
0254     // Removing removed outputs
0255     const KScreen::OutputList outputs = m_kscreenConfig->outputs();
0256     for (const auto &output : outputs) {
0257         if (!m_outputMap.contains(output->id())) {
0258             m_kscreenConfig->removeOutput(output->id());
0259         }
0260     }
0261 
0262     // Add KScreen::Outputs that aren't in the list yet
0263     KScreen::OutputList kscreenOutputs = m_kscreenConfig->outputs();
0264     QMap<OutputPtr, uint32_t> priorities;
0265     for (const auto &output : m_outputMap) {
0266         KScreen::OutputPtr kscreenOutput;
0267         if (m_kscreenConfig->outputs().contains(output->id())) {
0268             kscreenOutput = m_kscreenConfig->outputs()[output->id()];
0269             output->updateKScreenOutput(kscreenOutput);
0270         } else {
0271             kscreenOutput = output->toKScreenOutput();
0272             m_kscreenConfig->addOutput(kscreenOutput);
0273         }
0274         priorities[kscreenOutput] = output->index();
0275     }
0276     m_kscreenConfig->setOutputPriorities(priorities);
0277 
0278     m_kscreenConfig->setTabletModeAvailable(m_tabletModeAvailable);
0279     m_kscreenConfig->setTabletModeEngaged(m_tabletModeEngaged);
0280 
0281     return m_kscreenConfig;
0282 }
0283 
0284 QMap<int, WaylandOutputDevice *> WaylandConfig::outputMap() const
0285 {
0286     return m_outputMap;
0287 }
0288 
0289 void WaylandConfig::tryPendingConfig()
0290 {
0291     if (!m_kscreenPendingConfig) {
0292         return;
0293     }
0294     applyConfig(m_kscreenPendingConfig);
0295     m_kscreenPendingConfig = nullptr;
0296 }
0297 
0298 WaylandOutputDevice *WaylandConfig::findOutputDevice(struct ::kde_output_device_v2 *outputdevice) const
0299 {
0300     for (WaylandOutputDevice *device : m_outputMap) {
0301         if (device->object() == outputdevice) {
0302             return device;
0303         }
0304     }
0305     return nullptr;
0306 }
0307 
0308 void WaylandConfig::applyConfig(const KScreen::ConfigPtr &newConfig)
0309 {
0310     using namespace KWayland::Client;
0311 
0312     newConfig->adjustPriorities(); // never trust input
0313 
0314     // Create a new configuration object
0315     auto wlConfig = m_outputManagement->createConfiguration();
0316     bool changed = false;
0317 
0318     if (m_blockSignals) {
0319         // Last apply still pending, remember new changes and apply afterwards
0320         m_kscreenPendingConfig = newConfig;
0321         return;
0322     }
0323 
0324     for (const auto &output : newConfig->outputs()) {
0325         changed |= m_outputMap[output->id()]->setWlConfig(wlConfig, output);
0326     }
0327 
0328     if (!changed) {
0329         return;
0330     }
0331 
0332     // We now block changes in order to compress events while the compositor is doing its thing
0333     // once it's done or failed, we'll trigger configChanged() only once, and not per individual
0334     // property change.
0335     connect(wlConfig, &WaylandOutputConfiguration::applied, this, [this, wlConfig] {
0336         wlConfig->deleteLater();
0337         unblockSignals();
0338         Q_EMIT configChanged();
0339         tryPendingConfig();
0340     });
0341     connect(wlConfig, &WaylandOutputConfiguration::failed, this, [this, wlConfig] {
0342         wlConfig->deleteLater();
0343         unblockSignals();
0344         Q_EMIT configChanged();
0345         tryPendingConfig();
0346     });
0347 
0348     // Now block signals and ask the compositor to apply the changes.
0349     blockSignals();
0350     wlConfig->apply();
0351 }