File indexing completed on 2024-05-12 09:36:12

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