/* SPDX-FileCopyrightText: 2013 Marco Martin SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez SPDX-License-Identifier: LGPL-2.0-or-later */ #include "primaryoutputwatcher.h" #include "debug.h" #include #include #include #include "qwayland-kde-primary-output-v1.h" #include #include #include #if HAVE_X11 #include //Used only in x11 case #include #include #include #include #endif class WaylandPrimaryOutput : public QObject, public QtWayland::kde_primary_output_v1 { Q_OBJECT public: WaylandPrimaryOutput(struct ::wl_registry *registry, int id, int version, QObject *parent) : QObject(parent) , QtWayland::kde_primary_output_v1(registry, id, version) { } void kde_primary_output_v1_primary_output(const QString &outputName) override { Q_EMIT primaryOutputChanged(outputName); } Q_SIGNALS: void primaryOutputChanged(const QString &outputName); }; PrimaryOutputWatcher::PrimaryOutputWatcher(QObject *parent) : QObject(parent) { #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { m_primaryOutputName = qGuiApp->primaryScreen()->name(); qGuiApp->installNativeEventFilter(this); const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id); m_xrandrExtensionOffset = reply->first_event; setPrimaryOutputName(qGuiApp->primaryScreen()->name()); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, [this](QScreen *newPrimary) { setPrimaryOutputName(newPrimary->name()); }); } #endif if (KWindowSystem::isPlatformWayland()) { setupRegistry(); } } void PrimaryOutputWatcher::setPrimaryOutputName(const QString &newOutputName) { if (newOutputName != m_primaryOutputName) { const QString oldOutputName = m_primaryOutputName; m_primaryOutputName = newOutputName; Q_EMIT primaryOutputNameChanged(oldOutputName, newOutputName); } } void PrimaryOutputWatcher::setupRegistry() { auto m_connection = KWayland::Client::ConnectionThread::fromApplication(this); if (!m_connection) { return; } // Asking for primaryOutputName() before this happened, will return qGuiApp->primaryScreen()->name() anyways, so set it so the primaryOutputNameChange will // have parameters that are coherent m_primaryOutputName = qGuiApp->primaryScreen()->name(); m_registry = new KWayland::Client::Registry(this); connect(m_registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this](const QByteArray &interface, quint32 name, quint32 version) { if (interface == WaylandPrimaryOutput::interface()->name) { auto m_outputManagement = new WaylandPrimaryOutput(m_registry->registry(), name, version, this); connect(m_outputManagement, &WaylandPrimaryOutput::primaryOutputChanged, this, [this](const QString &outputName) { m_primaryOutputWayland = outputName; // Only set the outputName when there's a QScreen attached to it if (screenForName(outputName)) { setPrimaryOutputName(outputName); } }); } }); // In case the outputName was received before Qt reported the screen connect(qGuiApp, &QGuiApplication::screenAdded, this, [this](QScreen *screen) { if (screen->name() == m_primaryOutputWayland) { setPrimaryOutputName(m_primaryOutputWayland); } }); m_registry->create(m_connection); m_registry->setup(); } bool PrimaryOutputWatcher::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) { Q_UNUSED(result); #if HAVE_X11 // a particular edge case: when we switch the only enabled screen // we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled // see https://bugs.kde.org/show_bug.cgi?id=373880 // if this slot will be invoked many times, their//second time on will do nothing as name and primaryOutputName will be the same by then if (eventType[0] != 'x') { return false; } xcb_generic_event_t *ev = static_cast(message); const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); if (responseType == m_xrandrExtensionOffset + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { QTimer::singleShot(0, this, [this]() { setPrimaryOutputName(qGuiApp->primaryScreen()->name()); }); } #endif return false; } QScreen *PrimaryOutputWatcher::screenForName(const QString &outputName) const { const auto screens = qGuiApp->screens(); for (auto screen : screens) { if (screen->name() == outputName) { return screen; } } return nullptr; } QScreen *PrimaryOutputWatcher::primaryScreen() const { auto screen = screenForName(m_primaryOutputName); if (!screen) { #ifdef PLASMASHELL qCWarning(PLASMASHELL) << "could not find primary screen" << m_primaryOutputName; #endif return qGuiApp->primaryScreen(); } return screen; } #include "primaryoutputwatcher.moc"