File indexing completed on 2025-01-12 11:03:14
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