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

source code of qtwayland/src/client/qwaylandintegration.cpp