Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwaylanddisplay.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2024 Jie Liu <liujie01@kylinos.cn>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:significant reason:default
5
7
16#if QT_CONFIG(clipboard)
17#include "qwaylandclipboard_p.h"
18#include "qwaylanddatacontrolv1_p.h"
19#endif
20#if QT_CONFIG(wayland_datadevice)
21#include "qwaylanddatadevicemanager_p.h"
22#include "qwaylanddatadevice_p.h"
23#endif // QT_CONFIG(wayland_datadevice)
24#if QT_CONFIG(wayland_client_primary_selection)
25#include "qwaylandprimaryselectionv1_p.h"
26#endif // QT_CONFIG(wayland_client_primary_selection)
27#if QT_CONFIG(cursor)
28#include <wayland-cursor.h>
29#endif
36
40
43#if QT_CONFIG(tabletevent)
44#include "qwaylandtabletv2_p.h"
45#endif
46
48
49#include <QtWaylandClient/private/qwayland-text-input-unstable-v1.h>
50#include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h>
51#include <QtWaylandClient/private/qwayland-text-input-unstable-v3.h>
52#include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h>
53#include <QtWaylandClient/private/qwayland-qt-text-input-method-unstable-v1.h>
54#include <QtWaylandClient/private/qwayland-fractional-scale-v1.h>
55#include <QtWaylandClient/private/qwayland-viewporter.h>
56#include <QtWaylandClient/private/qwayland-cursor-shape-v1.h>
57#include <QtWaylandClient/private/qwayland-xdg-session-management-v1.h>
58#include <QtWaylandClient/private/qwayland-xdg-system-bell-v1.h>
59#include <QtWaylandClient/private/qwayland-xdg-toplevel-drag-v1.h>
60#include <QtWaylandClient/private/qwayland-wlr-data-control-unstable-v1.h>
61#include <QtWaylandClient/private/qwayland-pointer-warp-v1.h>
62
63#include <QtCore/private/qcore_unix_p.h>
64
65#include <QtCore/QAbstractEventDispatcher>
66#include <QtGui/qpa/qwindowsysteminterface.h>
67#include <QtGui/private/qguiapplication_p.h>
68
69#include <QtCore/QDebug>
70
71#include <errno.h>
72
73#include <tuple> // for std::tie
74
75QT_BEGIN_NAMESPACE
76
77namespace QtWaylandClient {
78
79class EventThread : public QThread
80{
82public:
84 EmitToDispatch, // Emit the signal, allow dispatching in a differnt thread.
85 SelfDispatch, // Dispatch the events inside this thread.
86 };
87
88 EventThread(struct wl_display * wl,
89 struct wl_event_queue * ev_queue,
90 OperatingMode mode,
91 QObject *parent = nullptr)
92 : QThread(parent)
93 , m_fd(wl_display_get_fd(wl))
94 , m_pipefd{ -1, -1 }
95 , m_wldisplay(wl)
96 , m_wlevqueue(ev_queue)
97 , m_mode(mode)
98 , m_reading(true)
99 , m_quitting(false)
100 {
101 setObjectName(QStringLiteral("WaylandEventThread"));
102
103 // The pipe lets stop() interrupt the poll() in run(), which would otherwise never
104 // return once the compositor stops sending events.
105 if (qt_safe_pipe(m_pipefd) != 0)
106 qErrnoWarning("Pipe creation failed. Quitting may hang.");
107 }
108
110 {
111 // run()'s poll() uses the pipe, so the thread must be stopped by now.
112 Q_ASSERT(!isRunning());
113
114 if (m_pipefd[0] != -1) {
115 qt_safe_close(m_pipefd[0]);
116 qt_safe_close(m_pipefd[1]);
117 }
118 }
119
121 {
122 /*
123 * Dispatch pending events and flush the requests at least once. If the event thread
124 * is not reading, try to call _prepare_read() to allow the event thread to poll().
125 * If that fails, re-try dispatch & flush again until _prepare_read() is successful.
126 *
127 * This allow any call to readAndDispatchEvents() to start event thread's polling,
128 * not only the one issued from event thread's waitForReading(), which means functions
129 * called from dispatch_pending() can safely spin an event loop.
130 */
131 if (m_quitting.loadRelaxed())
132 return;
133
134 for (;;) {
135 if (dispatchQueuePending() < 0) {
136 Q_EMIT waylandError();
137 QMutexLocker lock(&m_mutex);
138 m_quitting.storeRelaxed(true);
139 m_cond.wakeOne();
140 return;
141 }
142
143 wl_display_flush(m_wldisplay);
144
145 // We have to check if event thread is reading every time we dispatch
146 // something, as that may recursively call this function.
147 if (m_reading.loadAcquire())
148 break;
149
150 if (prepareReadQueue() == 0) {
151 QMutexLocker l(&m_mutex);
152 m_reading.storeRelease(true);
153 m_cond.wakeOne();
154 break;
155 }
156 }
157 }
158
159 void stop()
160 {
161 // We have to both write to the pipe and set the flag, as the thread may be
162 // either in the poll() or waiting for _prepare_read().
163 if (m_pipefd[1] != -1 && qt_safe_write(m_pipefd[1], "\0", 1) == -1)
164 qErrnoWarning("Failed to write to the pipe");
165
166 {
167 // The lock makes sure the event thread doesn't start waiting just after it
168 // checked m_quitting, which would make it miss the wakeOne() here.
169 QMutexLocker lock(&m_mutex);
170 m_quitting.storeRelaxed(true);
171 m_cond.wakeOne();
172 }
173
174 wait();
175 }
176
180
181protected:
183 {
184 // Make the main thread call wl_prepare_read(), dispatch the pending messages and flush the
185 // outbound ones. Wait until it's done before proceeding, unless we're told to quit.
186 while (waitForReading()) {
187 if (!m_reading.loadRelaxed())
188 break;
189
190 // m_pipefd[0] is what makes this poll() return when stop() is called.
191 pollfd fds[2] = { { m_fd, POLLIN, 0 }, { m_pipefd[0], POLLIN, 0 } };
192 poll(fds, 2, -1);
193
194 if (fds[1].revents & POLLIN) {
195 // we don't really care to read the byte that was written here since we're closing down
196 wl_display_cancel_read(m_wldisplay);
197 break;
198 }
199
200 if (fds[0].revents & POLLIN)
201 wl_display_read_events(m_wldisplay);
202 // The poll was succesfull and the event thread did the wl_display_read_events(). On the next iteration of the loop
203 // the event sent to the main thread will cause it to dispatch the messages just read, unless the loop exits in which
204 // case we don't care anymore about them.
205 else
206 wl_display_cancel_read(m_wldisplay);
207 }
208 }
209
210private:
211 bool waitForReading()
212 {
213 Q_ASSERT(QThread::currentThread() == this);
214
215 m_reading.storeRelease(false);
216
217 if (m_mode == SelfDispatch) {
219 } else {
220 Q_EMIT needReadAndDispatch();
221
222 QMutexLocker lock(&m_mutex);
223 // m_reading might be set from our emit or some other invocation of
224 // readAndDispatchEvents().
225 while (!m_reading.loadRelaxed() && !m_quitting.loadRelaxed())
226 m_cond.wait(&m_mutex);
227 }
228
229 return !m_quitting.loadRelaxed();
230 }
231
232 int dispatchQueuePending()
233 {
234 if (m_wlevqueue)
235 return wl_display_dispatch_queue_pending(m_wldisplay, m_wlevqueue);
236 else
237 return wl_display_dispatch_pending(m_wldisplay);
238 }
239
240 int prepareReadQueue()
241 {
242 if (m_wlevqueue)
243 return wl_display_prepare_read_queue(m_wldisplay, m_wlevqueue);
244 else
245 return wl_display_prepare_read(m_wldisplay);
246 }
247
248 int m_fd;
249 int m_pipefd[2]; // set in the constructor, closed in the destructor, constant in between
250 wl_display *m_wldisplay;
251 wl_event_queue *m_wlevqueue;
252 OperatingMode m_mode;
253
254 /* Concurrency note when operating in EmitToDispatch mode:
255 * m_reading is set to false inside event thread's waitForReading(), and is
256 * set to true inside main thread's readAndDispatchEvents().
257 * The lock is not taken when setting m_reading to false, as the main thread
258 * is not actively waiting for it to turn false. However, the lock is taken
259 * inside readAndDispatchEvents() before setting m_reading to true,
260 * as the event thread is actively waiting for it under the wait condition.
261 * m_quitting is set inside stop() and readAndDispatchEvents(), which both take the
262 * lock for the same reason: the event thread waits on it too.
263 */
264
265 QAtomicInteger<bool> m_reading;
266 QAtomicInteger<bool> m_quitting;
267 QMutex m_mutex;
268 QWaitCondition m_cond;
269};
270
271Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland"); // for general (uncategorized) Wayland platform logging
272
279
280struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion)
281{
283
284 for (const QRect &rect : qregion)
286
287 return region;
288}
289
291{
292 if (!mGlobals.subCompositor) {
293 qCWarning(lcQpaWayland) << "Can't create subsurface, not supported by the compositor.";
294 return nullptr;
295 }
296
297 // Make sure we don't pass NULL surfaces to libwayland (crashes)
300
302}
303
305{
306 if (!mGlobals.viewporter) {
307 qCWarning(lcQpaWayland) << "Can't create wp_viewport, not supported by the compositor.";
308 return nullptr;
309 }
310
313}
314
319
324
329
331 : QObject(parent)
333{
334 qRegisterMetaType<uint32_t>("uint32_t");
335
336 mDisplay = wl_display_connect(nullptr);
337 if (mDisplay) {
339 } else {
340 qErrnoWarning(errno, "Failed to create wl_display");
341 }
342
343 mWaylandTryReconnect = qEnvironmentVariableIsSet("QT_WAYLAND_RECONNECT");
344 mPreferWlrDataControl = qEnvironmentVariableIntValue("QT_WAYLAND_USE_DATA_CONTROL") > 0;
345}
346
348{
350 init(registry);
351
352#if QT_CONFIG(xkbcommon)
354 if (!mXkbContext)
355 qCWarning(lcQpaWayland, "failed to create xkb context");
356#endif
359}
360
362{
363 if (m_eventThread)
365
368
369 if (mSyncCallback)
371
373
374 for (QWaylandScreen *screen : std::exchange(mScreens, {})) {
376 }
378
379#if QT_CONFIG(cursor)
381#endif
382
385
386 // Reset the globals manually since they need to be destroyed before the wl_display
387 mGlobals = {};
388
389 if (object()) {
390 if (mFixes)
392
394 }
395
396 mFixes.reset();
397
398 if (mDisplay)
400}
401
402// Steps which is called just after constructor. This separates registry_global() out of the constructor
403// so that factory functions in integration can be overridden.
405{
406 if (!isInitialized())
407 return false;
408
410
411 emit connected();
412
413 if (!mWaitingScreens.isEmpty()) {
414 // Give wl_output.done and zxdg_output_v1.done events a chance to arrive
416 }
419
420 return qEnvironmentVariableIntValue("QT_WAYLAND_DONT_CHECK_SHELL_INTEGRATION") || shellIntegration();
421}
422
424{
426 return; // There are real screens or we already have a fake one
427
428 qCInfo(lcQpaWayland) << "There are no outputs - creating placeholder screen";
429
433}
434
436{
437 qCWarning(lcQpaWayland) << "Attempting wayland reconnect";
442
445
446 while (!mScreens.isEmpty()) {
447 auto screen = mScreens.takeLast();
448 ensureScreen();
450 }
451
453 mCursor.reset();
454
456
458
460 mLastInputDevice = nullptr;
461
462 for (const RegistryGlobal &global : mRegistryGlobals) {
464 }
466
472
473 const auto windows = QGuiApplication::allWindows();
475 for (auto window : windows) {
476 if (auto waylandWindow = static_cast<QWaylandWindow *>(window->handle())) {
479 }
480 }
481
482 // Remove windows that do not need to be recreated and now closed popups
484 for (auto window : std::as_const(allPlatformWindows)) {
487 }
488 window->reset();
489 }
490
491 if (mSyncCallback) {
493 mSyncCallback = nullptr;
494 }
495
496 if (object()) {
497 if (mFixes)
499
501 }
502
503 mFixes.reset();
504
505 mDisplay = wl_display_connect(nullptr);
506 if (!mDisplay)
507 _exit(1);
508
510 connect(
511 this, &QWaylandDisplay::connected, this,
512 [this, &allPlatformWindows] {
513 for (auto &window : std::as_const(allPlatformWindows)) {
515 }
516 forceRoundTrip(); // we need a roundtrip to receive the color space features the compositor supports
517 for (auto &window : std::as_const(allPlatformWindows)) {
519 }
520 },
522
524
528
529 initialize();
530
532 auto waylandWindow = static_cast<QWaylandWindow *>(window);
534 };
536 while (!recreateWindows.isEmpty()) {
538 (*window)->reinit();
540 } else {
541 ++window;
542 }
543 if (window == recreateWindows.end())
545 }
546
548
549 // Failsafe: Make sure we don't have a lingering connection referencing local variables
550 // (This will have been executed and disconnected in initialize())
552}
553
558
559// We have to wait until we have an eventDispatcher before creating the eventThread,
560// otherwise forceRoundTrip() may block inside _events_read() because eventThread is
561// polling.
578
580{
582 if ((ecode == EPIPE || ecode == ECONNRESET)) {
583 qWarning("The Wayland connection broke. Did the Wayland compositor die?");
585 reconnect();
586 return;
587 }
588 } else {
589 qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode));
590 }
591 _exit(-1);
592}
593
595{
596 QStringList tips, timps; // for text input protocols and text input manager protocols
597 // zwp_text_input_v2 is preferred over zwp_text_input_v3 because:
598 // - Currently, v3 is not as feature rich as v2.
599 // - While v2 is not upstreamed, it is well supported by KWin since Plasma 5 and Plasma
600 // Mobile uses some v2 only.
609
610 QString tiProtocols = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_TEXT_INPUT_PROTOCOL"));
611 qCDebug(lcQpaWayland) << "QT_WAYLAND_TEXT_INPUT_PROTOCOL=" << tiProtocols;
613 if (!tiProtocols.isEmpty()) {
616 while (it != keys.end()) {
617 if (tips.contains(*it))
619 else
620 qCDebug(lcQpaWayland) << "text input: unknown protocol - " << *it;
621 ++it;
622 }
623 }
624 if (mTextInputManagerList.isEmpty()) // fallback
626}
627
629{
630 for (auto screen : std::as_const(mScreens)) {
631 if (screen->output() == output)
632 return screen;
633 }
634 return nullptr;
635}
636
638{
640 return;
643 if (mPlaceholderScreen) {
644 // handleScreenRemoved deletes the platform screen
646 mPlaceholderScreen = nullptr;
648
649 }
650}
651
652template <typename T, auto f>
653struct WithDestructor : public T
654{
655 using T::T;
657 {
658 f(this->object());
659 }
660
661private:
663};
664
666{
667 struct ::wl_registry *registry = object();
668
669 static QStringList interfaceBlacklist = qEnvironmentVariable("QT_WAYLAND_DISABLED_INTERFACES").split(u',');
671 return;
672 }
673
679 registry, id, qMin((int)version, 6)));
680 } else if (interface == QLatin1String(QWaylandShm::interface()->name)) {
681 mGlobals.shm.reset(new QWaylandShm(this, version, id));
685#if QT_CONFIG(wayland_datadevice)
688#endif
692 id, 1));
693#if QT_CONFIG(tabletevent)
699#endif
702#if QT_CONFIG(wayland_client_primary_selection)
709#endif
712 qCDebug(lcQpaWayland) << "text input: register qt_text_input_method_manager_v1";
718 inputDevice->setTextInput(nullptr);
719 }
720
726 this,
728 inputDevice->wl_seat())));
733 qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v1";
740 }
741
746 auto textInput =
750 }
751
756 qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v2";
763 }
764
775 qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v3";
781 }
788
792 bool disableHardwareIntegration = qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_HW_INTEGRATION");
795 // make a roundtrip here since we need to receive the events sent by
796 // qt_hardware_integration before creating windows
798 }
801 for (auto *screen : std::as_const(mWaitingScreens))
807 } else if (interface == QLatin1String("wp_viewporter")) {
810 registry, id, qMin(1u, version)));
814 registry, id, std::min(1u, version)));
815 } else if (
825 } else if (
828#if QT_CONFIG(clipboard)
833 }
834#endif
839 registry, id, 1));
840 }
841#ifndef QT_NO_SESSIONMANAGER
845 registry, id, 1));
846 }
847#endif
850 }
851
852
855
856 const auto copy = mRegistryListeners; // be prepared for listeners unregistering on notification
857 for (Listener l : copy)
859}
860
862{
863 for (int i = 0, ie = mRegistryGlobals.size(); i != ie; ++i) {
865 if (global.id == id) {
867 for (auto *screen : mWaitingScreens) {
868 if (screen->outputId() == id) {
870 delete screen;
871 break;
872 }
873 }
874
876 if (screen->outputId() == id) {
878 // If this is the last screen, we have to add a fake screen, or Qt will break.
879 ensureScreen();
881 break;
882 }
883 }
884 }
888 inputDevice->setTextInput(nullptr);
890 }
894 inputDevice->setTextInput(nullptr);
896 }
900 inputDevice->setTextInput(nullptr);
902 }
908 }
909#if QT_CONFIG(wayland_client_primary_selection)
914 }
915#endif
916#if QT_CONFIG(clipboard)
921 }
922#endif
924 break;
925 }
926 }
927
930}
931
933{
936 return true;
937
938 return false;
939}
940
949
957
962
964{
965 static bool disabled = qgetenv("QT_WAYLAND_DISABLE_WINDOWDECORATION").toInt();
966 // Stop early when disabled via the environment. Do not try to load the integration in
967 // order to play nice with SHM-only, buffer integration-less systems.
968 if (disabled)
969 return false;
970
971 // Don't initialize client buffer integration just to check whether it can have a decoration.
973 return true;
974
975 // We can do software-rendered decorations, only disable them if the integration explicitly says it can't.
977 return integrationSupport;
978}
979
984
989
996
1001
1014
1031
1046
1059
1065
1070
1072{
1073 // This callback is used to set the window activation because we may get an activate/deactivate
1074 // pair, and the latter one would be lost in the QWindowSystemInterface queue, if we issue the
1075 // handleWindowActivated() calls immediately.
1081}
1082
1084 [](void *data, struct wl_callback *callback, uint32_t time){
1085 Q_UNUSED(time);
1087 QWaylandDisplay *display = static_cast<QWaylandDisplay *>(data);
1088 display->mSyncCallback = nullptr;
1090 }
1091};
1092
1094{
1095 if (mSyncCallback)
1096 return;
1097
1100}
1101
1106
1108{
1109 return std::any_of(
1111 [](const QWaylandInputDevice *device) { return device->keyboard() != nullptr; });
1112}
1113
1117
1118#if QT_CONFIG(cursor)
1119
1121{
1122 if (!mCursor)
1124 return mCursor.get();
1125}
1126
1127auto QWaylandDisplay::findExistingCursorTheme(const QString &name, int pixelSize) const noexcept
1129{
1130 const auto byNameAndSize = [](const WaylandCursorTheme &lhs, const WaylandCursorTheme &rhs) {
1132 };
1133
1134 const WaylandCursorTheme prototype = {name, pixelSize, nullptr};
1135
1137 if (it != mCursorThemes.cend() && it->name == name && it->pixelSize == pixelSize)
1138 return {it, true};
1139 else
1140 return {it, false};
1141}
1142
1144{
1146 if (result.found)
1147 return result.theme();
1148
1151
1152 return nullptr;
1153}
1154
1155#endif // QT_CONFIG(cursor)
1156
1157} // namespace QtWaylandClient
1158
1159QT_END_NAMESPACE
1160
1161#include "qwaylanddisplay.moc"
1162#include "moc_qwaylanddisplay_p.cpp"
Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland")