File indexing completed on 2024-04-28 05:35:35
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 "outputorderwatcher.h" 0009 0010 #include <QGuiApplication> 0011 #include <QScreen> 0012 #include <QTimer> 0013 0014 #include <KWindowSystem> 0015 0016 #include "qwayland-kde-output-order-v1.h" 0017 #include <QtWaylandClient/QWaylandClientExtension> 0018 #include <QtWaylandClient/QtWaylandClientVersion> 0019 0020 #if HAVE_X11 0021 #include <private/qtx11extras_p.h> 0022 0023 #include <xcb/randr.h> 0024 #include <xcb/xcb_event.h> 0025 #endif // HAVE_X11 0026 0027 template<typename T> 0028 using ScopedPointer = QScopedPointer<T, QScopedPointerPodDeleter>; 0029 0030 class WaylandOutputOrder : public QWaylandClientExtensionTemplate<WaylandOutputOrder, &QtWayland::kde_output_order_v1::destroy>, 0031 public QtWayland::kde_output_order_v1 0032 { 0033 Q_OBJECT 0034 public: 0035 WaylandOutputOrder(QObject *parent) 0036 : QWaylandClientExtensionTemplate(1) 0037 { 0038 setParent(parent); 0039 initialize(); 0040 } 0041 0042 protected: 0043 void kde_output_order_v1_output(const QString &outputName) override 0044 { 0045 if (m_done) { 0046 m_outputOrder.clear(); 0047 m_done = false; 0048 } 0049 m_outputOrder.append(outputName); 0050 } 0051 0052 void kde_output_order_v1_done() override 0053 { 0054 // If no output arrived it means we don't have *any* usable output 0055 if (m_done) { 0056 m_outputOrder.clear(); 0057 } 0058 m_done = true; 0059 Q_EMIT outputOrderChanged(m_outputOrder); 0060 } 0061 0062 Q_SIGNALS: 0063 void outputOrderChanged(const QStringList &outputName); 0064 0065 private: 0066 QStringList m_outputOrder; 0067 bool m_done = true; 0068 }; 0069 0070 OutputOrderWatcher::OutputOrderWatcher(QObject *parent) 0071 : QObject(parent) 0072 { 0073 connect(qGuiApp, &QGuiApplication::screenAdded, this, &OutputOrderWatcher::refresh); 0074 connect(qGuiApp, &QGuiApplication::screenRemoved, this, &OutputOrderWatcher::refresh); 0075 } 0076 0077 void OutputOrderWatcher::useFallback(bool fallback) 0078 { 0079 m_orderProtocolPresent = !fallback; 0080 if (fallback) { 0081 connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &OutputOrderWatcher::refresh, Qt::UniqueConnection); 0082 refresh(); 0083 } 0084 } 0085 0086 OutputOrderWatcher *OutputOrderWatcher::instance(QObject *parent) 0087 { 0088 #if HAVE_X11 0089 if (KWindowSystem::isPlatformX11()) { 0090 return new X11OutputOrderWatcher(parent); 0091 } else 0092 #endif 0093 if (KWindowSystem::isPlatformWayland()) { 0094 return new WaylandOutputOrderWatcher(parent); 0095 } 0096 // return default impl that does something at least 0097 return new OutputOrderWatcher(parent); 0098 } 0099 0100 void OutputOrderWatcher::refresh() 0101 { 0102 Q_ASSERT(!m_orderProtocolPresent); 0103 0104 QStringList pendingOutputOrder; 0105 0106 pendingOutputOrder.clear(); 0107 for (auto *s : qApp->screens()) { 0108 pendingOutputOrder.append(s->name()); 0109 } 0110 0111 auto outputLess = [](const QString &c1, const QString &c2) { 0112 if (c1 == qApp->primaryScreen()->name()) { 0113 return true; 0114 } else if (c2 == qApp->primaryScreen()->name()) { 0115 return false; 0116 } else { 0117 return c1 < c2; 0118 } 0119 }; 0120 std::sort(pendingOutputOrder.begin(), pendingOutputOrder.end(), outputLess); 0121 0122 if (m_outputOrder != pendingOutputOrder) { 0123 m_outputOrder = pendingOutputOrder; 0124 Q_EMIT outputOrderChanged(m_outputOrder); 0125 } 0126 return; 0127 } 0128 0129 QStringList OutputOrderWatcher::outputOrder() const 0130 { 0131 return m_outputOrder; 0132 } 0133 0134 X11OutputOrderWatcher::X11OutputOrderWatcher(QObject *parent) 0135 : OutputOrderWatcher(parent) 0136 { 0137 // This timer is used to signal only when a qscreen for every output is already created, perhaps by monitoring 0138 // screenadded/screenremoved and tracking the outputs still missing 0139 m_delayTimer = new QTimer(this); 0140 m_delayTimer->setSingleShot(true); 0141 m_delayTimer->setInterval(0); 0142 connect(m_delayTimer, &QTimer::timeout, this, [this]() { 0143 refresh(); 0144 }); 0145 0146 // By default try to use the protocol on x11 0147 m_orderProtocolPresent = true; 0148 0149 qGuiApp->installNativeEventFilter(this); 0150 const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id); 0151 m_xrandrExtensionOffset = reply->first_event; 0152 0153 const QByteArray effectName = QByteArrayLiteral("_KDE_SCREEN_INDEX"); 0154 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(QX11Info::connection(), false, effectName.length(), effectName); 0155 xcb_intern_atom_reply_t *atom(xcb_intern_atom_reply(QX11Info::connection(), atomCookie, nullptr)); 0156 if (!atom) { 0157 useFallback(true); 0158 return; 0159 } 0160 0161 m_kdeScreenAtom = atom->atom; 0162 m_delayTimer->start(); 0163 } 0164 0165 void X11OutputOrderWatcher::refresh() 0166 { 0167 if (!m_orderProtocolPresent) { 0168 OutputOrderWatcher::refresh(); 0169 return; 0170 } 0171 QMap<int, QString> orderMap; 0172 0173 ScopedPointer<xcb_randr_get_screen_resources_current_reply_t> reply( 0174 xcb_randr_get_screen_resources_current_reply(QX11Info::connection(), 0175 xcb_randr_get_screen_resources_current(QX11Info::connection(), QX11Info::appRootWindow()), 0176 NULL)); 0177 0178 xcb_timestamp_t timestamp = reply->config_timestamp; 0179 int len = xcb_randr_get_screen_resources_current_outputs_length(reply.data()); 0180 xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(reply.data()); 0181 0182 for (int i = 0; i < len; i++) { 0183 ScopedPointer<xcb_randr_get_output_info_reply_t> output( 0184 xcb_randr_get_output_info_reply(QX11Info::connection(), xcb_randr_get_output_info(QX11Info::connection(), randr_outputs[i], timestamp), NULL)); 0185 0186 if (output == NULL || output->connection == XCB_RANDR_CONNECTION_DISCONNECTED || output->crtc == 0) { 0187 continue; 0188 } 0189 0190 const auto screenName = 0191 QString::fromUtf8((const char *)xcb_randr_get_output_info_name(output.get()), xcb_randr_get_output_info_name_length(output.get())); 0192 0193 auto orderCookie = xcb_randr_get_output_property(QX11Info::connection(), randr_outputs[i], m_kdeScreenAtom, XCB_ATOM_ANY, 0, 100, false, false); 0194 ScopedPointer<xcb_randr_get_output_property_reply_t> orderReply(xcb_randr_get_output_property_reply(QX11Info::connection(), orderCookie, nullptr)); 0195 // If there is even a single screen without _KDE_SCREEN_INDEX info, fall back to alphabetical ordering 0196 if (!orderReply) { 0197 useFallback(true); 0198 return; 0199 } 0200 0201 if (!(orderReply->type == XCB_ATOM_INTEGER && orderReply->format == 32 && orderReply->num_items == 1)) { 0202 useFallback(true); 0203 return; 0204 } 0205 0206 const uint32_t order = *xcb_randr_get_output_property_data(orderReply.data()); 0207 0208 if (order > 0) { // 0 is the special case for disabled, so we ignore it 0209 orderMap[order] = screenName; 0210 } 0211 } 0212 0213 QStringList pendingOutputOrder; 0214 0215 for (const auto &screenName : orderMap) { 0216 pendingOutputOrder.append(screenName); 0217 } 0218 0219 for (const auto &name : std::as_const(pendingOutputOrder)) { 0220 bool present = false; 0221 for (auto *s : qApp->screens()) { 0222 if (s->name() == name) { 0223 present = true; 0224 break; 0225 } 0226 } 0227 // if the pending output order refers to screens 0228 // we don't know of yet, try again next time a screen is added 0229 0230 // this seems unlikely given we have the server lock and the timing thing 0231 if (!present) { 0232 m_delayTimer->start(); 0233 return; 0234 } 0235 } 0236 0237 if (pendingOutputOrder != m_outputOrder) { 0238 m_outputOrder = pendingOutputOrder; 0239 Q_EMIT outputOrderChanged(m_outputOrder); 0240 } 0241 } 0242 0243 bool X11OutputOrderWatcher::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) 0244 { 0245 Q_UNUSED(result); 0246 // a particular edge case: when we switch the only enabled screen 0247 // we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled 0248 // see https://bugs.kde.org/show_bug.cgi?id=373880 0249 // 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 0250 if (eventType[0] != 'x') { 0251 return false; 0252 } 0253 0254 xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message); 0255 0256 const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); 0257 0258 if (responseType == m_xrandrExtensionOffset + XCB_RANDR_NOTIFY) { 0259 auto *randrEvent = reinterpret_cast<xcb_randr_notify_event_t *>(ev); 0260 if (randrEvent->subCode == XCB_RANDR_NOTIFY_OUTPUT_PROPERTY) { 0261 xcb_randr_output_property_t property = randrEvent->u.op; 0262 0263 if (property.atom == m_kdeScreenAtom) { 0264 // Force an X11 roundtrip to make sure we have all other 0265 // screen events in the buffer when we process the deferred refresh 0266 useFallback(false); 0267 QX11Info::getTimestamp(); 0268 m_delayTimer->start(); 0269 } 0270 } 0271 } 0272 return false; 0273 } 0274 0275 WaylandOutputOrderWatcher::WaylandOutputOrderWatcher(QObject *parent) 0276 : OutputOrderWatcher(parent) 0277 { 0278 // Asking for primaryOutputName() before this happened, will return qGuiApp->primaryScreen()->name() anyways, so set it so the outputOrderChanged will 0279 // have parameters that are coherent 0280 OutputOrderWatcher::refresh(); 0281 0282 auto outputListManagement = new WaylandOutputOrder(this); 0283 m_orderProtocolPresent = outputListManagement->isActive(); 0284 if (!m_orderProtocolPresent) { 0285 useFallback(true); 0286 return; 0287 } 0288 connect(outputListManagement, &WaylandOutputOrder::outputOrderChanged, this, [this](const QStringList &order) { 0289 m_pendingOutputOrder = order; 0290 0291 if (hasAllScreens()) { 0292 if (m_pendingOutputOrder != m_outputOrder) { 0293 m_outputOrder = m_pendingOutputOrder; 0294 Q_EMIT outputOrderChanged(m_outputOrder); 0295 } 0296 } 0297 // otherwise wait for next QGuiApp screenAdded/removal 0298 // to keep things in sync 0299 }); 0300 } 0301 0302 bool WaylandOutputOrderWatcher::hasAllScreens() const 0303 { 0304 // for each name in our ordered list, find a screen with that name 0305 for (const auto &name : std::as_const(m_pendingOutputOrder)) { 0306 bool present = false; 0307 for (auto *s : qApp->screens()) { 0308 if (s->name() == name) { 0309 present = true; 0310 break; 0311 } 0312 } 0313 if (!present) { 0314 return false; 0315 } 0316 } 0317 return true; 0318 } 0319 0320 void WaylandOutputOrderWatcher::refresh() 0321 { 0322 if (!m_orderProtocolPresent) { 0323 OutputOrderWatcher::refresh(); 0324 return; 0325 } 0326 0327 if (!hasAllScreens()) { 0328 return; 0329 } 0330 0331 if (m_outputOrder != m_pendingOutputOrder) { 0332 m_outputOrder = m_pendingOutputOrder; 0333 Q_EMIT outputOrderChanged(m_outputOrder); 0334 } 0335 } 0336 0337 #include "outputorderwatcher.moc"