File indexing completed on 2024-04-21 05:31:16

0001 /*
0002     SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "primaryoutputwatcher.h"
0009 
0010 #include <KWindowSystem>
0011 #include <QDebug>
0012 #include <QGuiApplication>
0013 #include <QScreen>
0014 
0015 #include "qwayland-kde-primary-output-v1.h"
0016 #include <KWayland/Client/connection_thread.h>
0017 #include <KWayland/Client/registry.h>
0018 
0019 #include <config-latte.h>
0020 #if HAVE_X11
0021 #include <QTimer> //Used only in x11 case
0022 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0023 #include <private/qtx11extras_p.h>
0024 #else
0025 #include <QX11Info>
0026 #endif
0027 #include <xcb/randr.h>
0028 #include <xcb/xcb.h>
0029 #include <xcb/xcb_event.h>
0030 #endif
0031 
0032 class WaylandPrimaryOutput : public QObject, public QtWayland::kde_primary_output_v1
0033 {
0034     Q_OBJECT
0035 public:
0036     WaylandPrimaryOutput(struct ::wl_registry *registry, int id, int version, QObject *parent)
0037         : QObject(parent)
0038         , QtWayland::kde_primary_output_v1(registry, id, version)
0039     {
0040     }
0041 
0042     void kde_primary_output_v1_primary_output(const QString &outputName) override
0043     {
0044         Q_EMIT primaryOutputChanged(outputName);
0045     }
0046 
0047 Q_SIGNALS:
0048     void primaryOutputChanged(const QString &outputName);
0049 };
0050 
0051 PrimaryOutputWatcher::PrimaryOutputWatcher(QObject *parent)
0052     : QObject(parent)
0053 {
0054 #if HAVE_X11
0055     if (KWindowSystem::isPlatformX11()) {
0056         m_primaryOutputName = qGuiApp->primaryScreen()->name();
0057         qGuiApp->installNativeEventFilter(this);
0058         const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id);
0059         m_xrandrExtensionOffset = reply->first_event;
0060         setPrimaryOutputName(qGuiApp->primaryScreen()->name());
0061         connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, [this](QScreen *newPrimary) {
0062             setPrimaryOutputName(newPrimary->name());
0063         });
0064     }
0065 #endif
0066     if (KWindowSystem::isPlatformWayland()) {
0067         setupRegistry();
0068     }
0069 }
0070 
0071 void PrimaryOutputWatcher::setPrimaryOutputName(const QString &newOutputName)
0072 {
0073     if (newOutputName != m_primaryOutputName) {
0074         const QString oldOutputName = m_primaryOutputName;
0075         m_primaryOutputName = newOutputName;
0076         Q_EMIT primaryOutputNameChanged(oldOutputName, newOutputName);
0077     }
0078 }
0079 
0080 void PrimaryOutputWatcher::setupRegistry()
0081 {
0082     auto m_connection = KWayland::Client::ConnectionThread::fromApplication(this);
0083     if (!m_connection) {
0084         return;
0085     }
0086 
0087     // Asking for primaryOutputName() before this happened, will return qGuiApp->primaryScreen()->name() anyways, so set it so the primaryOutputNameChange will
0088     // have parameters that are coherent
0089     m_primaryOutputName = qGuiApp->primaryScreen()->name();
0090     m_registry = new KWayland::Client::Registry(this);
0091     connect(m_registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this](const QByteArray &interface, quint32 name, quint32 version) {
0092         if (interface == WaylandPrimaryOutput::interface()->name) {
0093             auto m_outputManagement = new WaylandPrimaryOutput(m_registry->registry(), name, version, this);
0094             connect(m_outputManagement, &WaylandPrimaryOutput::primaryOutputChanged, this, [this](const QString &outputName) {
0095                 m_primaryOutputWayland = outputName;
0096                 // Only set the outputName when there's a QScreen attached to it
0097                 if (screenForName(outputName)) {
0098                     setPrimaryOutputName(outputName);
0099                 }
0100             });
0101         }
0102     });
0103 
0104     // In case the outputName was received before Qt reported the screen
0105     connect(qGuiApp, &QGuiApplication::screenAdded, this, [this](QScreen *screen) {
0106         if (screen->name() == m_primaryOutputWayland) {
0107             setPrimaryOutputName(m_primaryOutputWayland);
0108         }
0109     });
0110 
0111     m_registry->create(m_connection);
0112     m_registry->setup();
0113 }
0114 
0115 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0116 bool PrimaryOutputWatcher::nativeEventFilter(const QByteArray &eventType, void *message, long int *result)
0117 #else
0118 bool PrimaryOutputWatcher::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
0119 #endif
0120 {
0121     Q_UNUSED(result);
0122 #if HAVE_X11
0123     // a particular edge case: when we switch the only enabled screen
0124     // we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled
0125     // see https://bugs.kde.org/show_bug.cgi?id=373880
0126     // 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
0127     if (eventType[0] != 'x') {
0128         return false;
0129     }
0130 
0131     xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message);
0132 
0133     const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev);
0134 
0135     if (responseType == m_xrandrExtensionOffset + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
0136         QTimer::singleShot(0, this, [this]() {
0137             setPrimaryOutputName(qGuiApp->primaryScreen()->name());
0138         });
0139     }
0140 #endif
0141     return false;
0142 }
0143 
0144 QScreen *PrimaryOutputWatcher::screenForName(const QString &outputName) const
0145 {
0146     const auto screens = qGuiApp->screens();
0147     for (auto screen : screens) {
0148         if (screen->name() == outputName) {
0149             return screen;
0150         }
0151     }
0152     return nullptr;
0153 }
0154 
0155 QScreen *PrimaryOutputWatcher::primaryScreen() const
0156 {
0157     auto screen = screenForName(m_primaryOutputName);
0158     if (!screen) {
0159         qDebug() << "PrimaryOutputWatcher: Could not find primary screen:" << m_primaryOutputName;
0160         return qGuiApp->primaryScreen();
0161     }
0162     return screen;
0163 }
0164 
0165 #include "primaryoutputwatcher.moc"
0166