1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwaylandintegration_p.h"
5
6#include "qwaylanddisplay_p.h"
7#include "qwaylandshmwindow_p.h"
8#include "qwaylandinputdevice_p.h"
9#include "qwaylandinputcontext_p.h"
10#include "qwaylandinputmethodcontext_p.h"
11#include "qwaylandshmbackingstore_p.h"
12#include "qwaylandnativeinterface_p.h"
13#if QT_CONFIG(clipboard)
14#include "qwaylandclipboard_p.h"
15#endif
16#include "qwaylanddnd_p.h"
17#include "qwaylandwindowmanagerintegration_p.h"
18#include "qwaylandplatformservices_p.h"
19#include "qwaylandscreen_p.h"
20#include "qwaylandcursor_p.h"
21#include "qwaylandeventdispatcher_p.h"
22
23#if defined(Q_OS_MACOS)
24# include <QtGui/private/qcoretextfontdatabase_p.h>
25# include <QtGui/private/qfontengine_coretext_p.h>
26#else
27# include <QtGui/private/qgenericunixfontdatabase_p.h>
28#endif
29#include <QtGui/private/qgenericunixtheme_p.h>
30
31#include <QtGui/private/qguiapplication_p.h>
32
33#include <qpa/qwindowsysteminterface.h>
34#include <qpa/qplatformcursor.h>
35#include <QtGui/QSurfaceFormat>
36#if QT_CONFIG(opengl)
37#include <QtGui/QOpenGLContext>
38#endif // QT_CONFIG(opengl)
39#include <QSocketNotifier>
40
41#include <qpa/qplatforminputcontextfactory_p.h>
42#include <qpa/qplatformaccessibility.h>
43#include <qpa/qplatforminputcontext.h>
44
45#include "qwaylandhardwareintegration_p.h"
46#include "qwaylandclientbufferintegration_p.h"
47#include "qwaylandclientbufferintegrationfactory_p.h"
48
49#include "qwaylandserverbufferintegration_p.h"
50#include "qwaylandserverbufferintegrationfactory_p.h"
51#include "qwaylandshellsurface_p.h"
52
53#include "qwaylandshellintegration_p.h"
54#include "qwaylandshellintegrationfactory_p.h"
55
56#include "qwaylandinputdeviceintegration_p.h"
57#include "qwaylandinputdeviceintegrationfactory_p.h"
58#include "qwaylandwindow_p.h"
59
60#include <QtWaylandClient/private/qwayland-xdg-system-bell-v1.h>
61
62#if QT_CONFIG(accessibility_atspi_bridge)
63#include <QtGui/private/qspiaccessiblebridge_p.h>
64#endif
65
66#if QT_CONFIG(xkbcommon)
67#include <QtGui/private/qxkbcommon_p.h>
68#endif
69
70#if QT_CONFIG(vulkan)
71#include "qwaylandvulkaninstance_p.h"
72#include "qwaylandvulkanwindow_p.h"
73#endif
74
75QT_BEGIN_NAMESPACE
76
77using namespace Qt::StringLiterals;
78
79namespace QtWaylandClient {
80
81QWaylandIntegration *QWaylandIntegration::sInstance = nullptr;
82
83QWaylandIntegration::QWaylandIntegration(const QString &platformName)
84#if defined(Q_OS_MACOS)
85 : mFontDb(new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>)
86#else
87 : mPlatformName(platformName), mFontDb(new QGenericUnixFontDatabase())
88#endif
89{
90 mDisplay.reset(other: new QWaylandDisplay(this));
91 mPlatformServices.reset(other: new QWaylandPlatformServices(mDisplay.data()));
92
93 QWaylandWindow::fixedToplevelPositions =
94 !qEnvironmentVariableIsSet(varName: "QT_WAYLAND_DISABLE_FIXED_POSITIONS");
95
96 sInstance = this;
97 if (platformName != "wayland"_L1)
98 initializeClientBufferIntegration();
99}
100
101QWaylandIntegration::~QWaylandIntegration()
102{
103 sInstance = nullptr;
104}
105
106bool QWaylandIntegration::init()
107{
108 return mDisplay->initialize();
109}
110
111QPlatformNativeInterface * QWaylandIntegration::nativeInterface() const
112{
113 return mNativeInterface.data();
114}
115
116bool QWaylandIntegration::hasCapability(QPlatformIntegration::Capability cap) const
117{
118 switch (cap) {
119 case ThreadedPixmaps: return true;
120 case OpenGL:
121 return mDisplay->clientBufferIntegration();
122 case ThreadedOpenGL:
123 return mDisplay->clientBufferIntegration() && mDisplay->clientBufferIntegration()->supportsThreadedOpenGL();
124 case BufferQueueingOpenGL:
125 return true;
126 case MultipleWindows:
127 case NonFullScreenWindows:
128 return true;
129 case RasterGLSurface:
130 return true;
131 case WindowActivation:
132 return true;
133 case ScreenWindowGrabbing: // whether QScreen::grabWindow() is supported
134 return false;
135 default: return QPlatformIntegration::hasCapability(cap);
136 }
137}
138
139QPlatformWindow *QWaylandIntegration::createPlatformWindow(QWindow *window) const
140{
141 if ((window->surfaceType() == QWindow::OpenGLSurface || window->surfaceType() == QWindow::RasterGLSurface)
142 && mDisplay->clientBufferIntegration())
143 return mDisplay->clientBufferIntegration()->createEglWindow(window);
144
145#if QT_CONFIG(vulkan)
146 if (window->surfaceType() == QSurface::VulkanSurface)
147 return new QWaylandVulkanWindow(window, mDisplay.data());
148#endif // QT_CONFIG(vulkan)
149
150 return new QWaylandShmWindow(window, mDisplay.data());
151}
152
153#if QT_CONFIG(opengl)
154QPlatformOpenGLContext *QWaylandIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
155{
156 if (mDisplay->clientBufferIntegration())
157 return mDisplay->clientBufferIntegration()->createPlatformOpenGLContext(glFormat: context->format(), share: context->shareHandle());
158 return nullptr;
159}
160
161QOpenGLContext *QWaylandIntegration::createOpenGLContext(EGLContext context, EGLDisplay contextDisplay, QOpenGLContext *shareContext) const
162{
163 return mClientBufferIntegration->createOpenGLContext(context, contextDisplay, shareContext);
164}
165#endif // opengl
166
167QPlatformBackingStore *QWaylandIntegration::createPlatformBackingStore(QWindow *window) const
168{
169 return new QWaylandShmBackingStore(window, mDisplay.data());
170}
171
172QAbstractEventDispatcher *QWaylandIntegration::createEventDispatcher() const
173{
174 return QWaylandEventDispatcher::createEventDispatcher();
175}
176
177QPlatformNativeInterface *QWaylandIntegration::createPlatformNativeInterface()
178{
179 return new QWaylandNativeInterface(this);
180}
181
182// Support platform specific initialization
183void QWaylandIntegration::initializePlatform()
184{
185 mDisplay->initEventThread();
186
187 mNativeInterface.reset(other: createPlatformNativeInterface());
188 initializeInputDeviceIntegration();
189#if QT_CONFIG(clipboard)
190 mClipboard.reset(other: new QWaylandClipboard(mDisplay.data()));
191#endif
192#if QT_CONFIG(draganddrop)
193 mDrag.reset(other: new QWaylandDrag(mDisplay.data()));
194#endif
195
196 reconfigureInputContext();
197}
198
199void QWaylandIntegration::initialize()
200{
201 initializePlatform();
202
203 // Call this after initializing event thread for QWaylandDisplay::flushRequests()
204 QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::eventDispatcher;
205 QObject::connect(dispatcher, SIGNAL(aboutToBlock()), mDisplay.data(), SLOT(flushRequests()));
206 QObject::connect(dispatcher, SIGNAL(awake()), mDisplay.data(), SLOT(flushRequests()));
207
208 // Qt does not support running with no screens
209 mDisplay->ensureScreen();
210}
211
212QPlatformFontDatabase *QWaylandIntegration::fontDatabase() const
213{
214 return mFontDb.data();
215}
216
217#if QT_CONFIG(clipboard)
218QPlatformClipboard *QWaylandIntegration::clipboard() const
219{
220 return mClipboard.data();
221}
222#endif
223
224#if QT_CONFIG(draganddrop)
225QPlatformDrag *QWaylandIntegration::drag() const
226{
227 return mDrag.data();
228}
229#endif // draganddrop
230
231QPlatformInputContext *QWaylandIntegration::inputContext() const
232{
233 return mInputContext.data();
234}
235
236QVariant QWaylandIntegration::styleHint(StyleHint hint) const
237{
238 if (hint == ShowIsFullScreen && mDisplay->windowManagerIntegration())
239 return mDisplay->windowManagerIntegration()->showIsFullScreen();
240
241 return QPlatformIntegration::styleHint(hint);
242}
243
244#if QT_CONFIG(accessibility)
245QPlatformAccessibility *QWaylandIntegration::accessibility() const
246{
247 if (!mAccessibility) {
248#if QT_CONFIG(accessibility_atspi_bridge)
249 Q_ASSERT_X(QCoreApplication::eventDispatcher(), "QWaylandIntegration",
250 "Initializing accessibility without event-dispatcher!");
251 mAccessibility.reset(other: new QSpiAccessibleBridge());
252#else
253 mAccessibility.reset(new QPlatformAccessibility());
254#endif
255 }
256 return mAccessibility.data();
257}
258#endif
259
260QPlatformServices *QWaylandIntegration::services() const
261{
262 return mPlatformServices.data();
263}
264
265QWaylandDisplay *QWaylandIntegration::display() const
266{
267 return mDisplay.data();
268}
269
270Qt::KeyboardModifiers QWaylandIntegration::queryKeyboardModifiers() const
271{
272 if (auto *seat = mDisplay->currentInputDevice(); seat && seat->keyboardFocus()) {
273 return seat->modifiers();
274 }
275 return Qt::NoModifier;
276}
277
278QList<int> QWaylandIntegration::possibleKeys(const QKeyEvent *event) const
279{
280 if (auto *seat = mDisplay->currentInputDevice())
281 return seat->possibleKeys(event);
282 return {};
283}
284
285QStringList QWaylandIntegration::themeNames() const
286{
287 return QGenericUnixTheme::themeNames();
288}
289
290QPlatformTheme *QWaylandIntegration::createPlatformTheme(const QString &name) const
291{
292 return QGenericUnixTheme::createUnixTheme(name);
293}
294
295QWaylandScreen *QWaylandIntegration::createPlatformScreen(QWaylandDisplay *waylandDisplay, int version, uint32_t id) const
296{
297 return new QWaylandScreen(waylandDisplay, version, id);
298}
299
300QWaylandCursor *QWaylandIntegration::createPlatformCursor(QWaylandDisplay *display) const
301{
302 return new QWaylandCursor(display);
303}
304
305#if QT_CONFIG(vulkan)
306QPlatformVulkanInstance *QWaylandIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
307{
308 return new QWaylandVulkanInstance(instance);
309}
310#endif // QT_CONFIG(vulkan)
311
312// May be called from non-GUI threads
313QWaylandClientBufferIntegration *QWaylandIntegration::clientBufferIntegration() const
314{
315 // Do an inexpensive check first to avoid locking whenever possible
316 if (Q_UNLIKELY(!mClientBufferIntegrationInitialized))
317 const_cast<QWaylandIntegration *>(this)->initializeClientBufferIntegration();
318
319 Q_ASSERT(mClientBufferIntegrationInitialized);
320 return mClientBufferIntegration && mClientBufferIntegration->isValid() ? mClientBufferIntegration.data() : nullptr;
321}
322
323QWaylandServerBufferIntegration *QWaylandIntegration::serverBufferIntegration() const
324{
325 if (!mServerBufferIntegrationInitialized)
326 const_cast<QWaylandIntegration *>(this)->initializeServerBufferIntegration();
327
328 return mServerBufferIntegration.data();
329}
330
331QWaylandShellIntegration *QWaylandIntegration::shellIntegration() const
332{
333 if (!mShellIntegrationInitialized)
334 const_cast<QWaylandIntegration *>(this)->initializeShellIntegration();
335
336 return mShellIntegration.data();
337}
338
339// May be called from non-GUI threads
340void QWaylandIntegration::initializeClientBufferIntegration()
341{
342 QMutexLocker lock(&mClientBufferInitLock);
343 if (mClientBufferIntegrationInitialized)
344 return;
345
346 QString targetKey = QString::fromLocal8Bit(ba: qgetenv(varName: "QT_WAYLAND_CLIENT_BUFFER_INTEGRATION"));
347 if (mPlatformName == "wayland-egl"_L1)
348 targetKey = "wayland-egl"_L1;
349 else if (mPlatformName == "wayland-brcm"_L1)
350 targetKey = "brcm"_L1;
351
352 if (targetKey.isEmpty()) {
353 if (mDisplay->hardwareIntegration()
354 && mDisplay->hardwareIntegration()->clientBufferIntegration() != QLatin1String("wayland-eglstream-controller")
355 && mDisplay->hardwareIntegration()->clientBufferIntegration() != QLatin1String("linux-dmabuf-unstable-v1")) {
356 targetKey = mDisplay->hardwareIntegration()->clientBufferIntegration();
357 } else {
358 targetKey = QLatin1String("wayland-egl");
359 }
360 }
361
362 if (targetKey.isEmpty()) {
363 qWarning(msg: "Failed to determine what client buffer integration to use");
364 } else {
365 QStringList keys = QWaylandClientBufferIntegrationFactory::keys();
366 qCDebug(lcQpaWayland) << "Available client buffer integrations:" << keys;
367
368 if (keys.contains(str: targetKey))
369 mClientBufferIntegration.reset(other: QWaylandClientBufferIntegrationFactory::create(name: targetKey, args: QStringList()));
370
371 if (mClientBufferIntegration) {
372 qCDebug(lcQpaWayland) << "Initializing client buffer integration" << targetKey;
373 mClientBufferIntegration->initialize(display: mDisplay.data());
374 } else {
375 qCWarning(lcQpaWayland) << "Failed to load client buffer integration:" << targetKey;
376 qCWarning(lcQpaWayland) << "Available client buffer integrations:" << keys;
377 }
378 }
379
380 // This must be set last to make sure other threads don't use the
381 // integration before initialization is complete.
382 mClientBufferIntegrationInitialized = true;
383}
384
385void QWaylandIntegration::initializeServerBufferIntegration()
386{
387 mServerBufferIntegrationInitialized = true;
388
389 QString targetKey = QString::fromLocal8Bit(ba: qgetenv(varName: "QT_WAYLAND_SERVER_BUFFER_INTEGRATION"));
390
391 if (targetKey.isEmpty() && mDisplay->hardwareIntegration())
392 targetKey = mDisplay->hardwareIntegration()->serverBufferIntegration();
393
394 if (targetKey.isEmpty()) {
395 qWarning(msg: "Failed to determine what server buffer integration to use");
396 return;
397 }
398
399 QStringList keys = QWaylandServerBufferIntegrationFactory::keys();
400 qCDebug(lcQpaWayland) << "Available server buffer integrations:" << keys;
401
402 if (keys.contains(str: targetKey))
403 mServerBufferIntegration.reset(other: QWaylandServerBufferIntegrationFactory::create(name: targetKey, args: QStringList()));
404
405 if (mServerBufferIntegration) {
406 qCDebug(lcQpaWayland) << "Initializing server buffer integration" << targetKey;
407 mServerBufferIntegration->initialize(display: mDisplay.data());
408 } else {
409 qCWarning(lcQpaWayland) << "Failed to load server buffer integration: " << targetKey;
410 qCWarning(lcQpaWayland) << "Available server buffer integrations:" << keys;
411 }
412}
413
414void QWaylandIntegration::initializeShellIntegration()
415{
416 mShellIntegrationInitialized = true;
417
418 QByteArray integrationNames = qgetenv(varName: "QT_WAYLAND_SHELL_INTEGRATION");
419 QString targetKeys = QString::fromLocal8Bit(ba: integrationNames);
420
421 QStringList preferredShells;
422 if (!targetKeys.isEmpty()) {
423 preferredShells = targetKeys.split(sep: QLatin1Char(';'));
424 } else {
425 preferredShells << QLatin1String("xdg-shell");
426 preferredShells << QLatin1String("wl-shell") << QLatin1String("ivi-shell");
427 preferredShells << QLatin1String("qt-shell");
428 }
429
430 for (const QString &preferredShell : std::as_const(t&: preferredShells)) {
431 mShellIntegration.reset(other: createShellIntegration(interfaceName: preferredShell));
432 if (mShellIntegration) {
433 qCDebug(lcQpaWayland, "Using the '%s' shell integration", qPrintable(preferredShell));
434 break;
435 }
436 }
437
438 if (!mShellIntegration) {
439 qCWarning(lcQpaWayland) << "Loading shell integration failed.";
440 qCWarning(lcQpaWayland) << "Attempted to load the following shells" << preferredShells;
441 }
442
443 QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
444}
445
446QWaylandInputDevice *QWaylandIntegration::createInputDevice(QWaylandDisplay *display, int version, uint32_t id) const
447{
448 if (mInputDeviceIntegration) {
449 return mInputDeviceIntegration->createInputDevice(d: display, version, id);
450 }
451 return new QWaylandInputDevice(display, version, id);
452}
453
454void QWaylandIntegration::initializeInputDeviceIntegration()
455{
456 QByteArray integrationName = qgetenv(varName: "QT_WAYLAND_INPUTDEVICE_INTEGRATION");
457 QString targetKey = QString::fromLocal8Bit(ba: integrationName);
458
459 if (targetKey.isEmpty()) {
460 return;
461 }
462
463 QStringList keys = QWaylandInputDeviceIntegrationFactory::keys();
464 if (keys.contains(str: targetKey)) {
465 mInputDeviceIntegration.reset(other: QWaylandInputDeviceIntegrationFactory::create(name: targetKey, args: QStringList()));
466 qDebug(msg: "Using the '%s' input device integration", qPrintable(targetKey));
467 } else {
468 qWarning(msg: "Wayland inputdevice integration '%s' not found, using default", qPrintable(targetKey));
469 }
470}
471
472void QWaylandIntegration::reconfigureInputContext()
473{
474 if (!mDisplay) {
475 // This function can be called from QWaylandDisplay::registry_global() when we
476 // are in process of constructing QWaylandDisplay. Configuring input context
477 // in that case is done by calling reconfigureInputContext() from QWaylandIntegration
478 // constructor, after QWaylandDisplay has been constructed.
479 return;
480 }
481
482 auto requested = QPlatformInputContextFactory::requested();
483 if (requested.contains(str: QLatin1String("qtvirtualkeyboard")))
484 qCWarning(lcQpaWayland) << "qtvirtualkeyboard currently is not supported at client-side,"
485 " use QT_IM_MODULES=qtvirtualkeyboard at compositor-side.";
486
487 if (mDisplay->isWaylandInputContextRequested()
488 && !requested.contains(str: QLatin1String(WAYLAND_IM_KEY)))
489 requested.append(t: QLatin1String(WAYLAND_IM_KEY));
490
491 const QString defaultInputContext(QStringLiteral("compose"));
492 if (!requested.contains(str: defaultInputContext))
493 requested.append(t: defaultInputContext);
494
495 for (const QString &imKey : requested) {
496 if (imKey == QLatin1String(WAYLAND_IM_KEY)) {
497 Q_ASSERT(mDisplay->isWaylandInputContextRequested());
498 if (mDisplay->textInputMethodManager() != nullptr)
499 mInputContext.reset(other: new QWaylandInputMethodContext(mDisplay.data()));
500 else if (mDisplay->textInputManagerv1() != nullptr
501 || mDisplay->textInputManagerv2() != nullptr
502 || mDisplay->textInputManagerv3() != nullptr)
503 mInputContext.reset(other: new QWaylandInputContext(mDisplay.data()));
504 } else {
505 mInputContext.reset(other: QPlatformInputContextFactory::create(key: imKey));
506 }
507
508 if (mInputContext && mInputContext->isValid())
509 break;
510 }
511
512#if QT_CONFIG(xkbcommon)
513 QXkbCommon::setXkbContext(inputContext: mInputContext.data(), context: mDisplay->xkbContext());
514 if (QWaylandInputContext* waylandInput = qobject_cast<QWaylandInputContext*>(object: mInputContext.get())) {
515 waylandInput->setXkbContext(mDisplay->xkbContext());
516 }
517#endif
518
519 qCDebug(lcQpaWayland) << "using input method:" << (inputContext() ? inputContext()->metaObject()->className() : "<none>");
520}
521
522QWaylandShellIntegration *QWaylandIntegration::createShellIntegration(const QString &integrationName)
523{
524 if (QWaylandShellIntegrationFactory::keys().contains(str: integrationName)) {
525 return QWaylandShellIntegrationFactory::create(name: integrationName, display: mDisplay.data());
526 } else {
527 qCWarning(lcQpaWayland) << "No shell integration named" << integrationName << "found";
528 return nullptr;
529 }
530}
531
532void QWaylandIntegration::reset()
533{
534 mServerBufferIntegration.reset();
535 mServerBufferIntegrationInitialized = false;
536
537 mInputDeviceIntegration.reset();
538
539 mClientBufferIntegration.reset();
540 mClientBufferIntegrationInitialized = false;
541}
542
543void QWaylandIntegration::setApplicationBadge(qint64 number)
544{
545 mPlatformServices->setApplicationBadge(number);
546}
547
548void QWaylandIntegration::beep() const
549{
550 if (auto bell = mDisplay->systemBell()) {
551 bell->ring(nullptr);
552 }
553}
554
555}
556
557QT_END_NAMESPACE
558

source code of qtbase/src/plugins/platforms/wayland/qwaylandintegration.cpp