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, ®istryListener, 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"