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 "qxcbintegration.h"
5#include "qxcbconnection.h"
6#include "qxcbscreen.h"
7#include "qxcbwindow.h"
8#include "qxcbcursor.h"
9#include "qxcbkeyboard.h"
10#include "qxcbbackingstore.h"
11#include "qxcbnativeinterface.h"
12#include "qxcbclipboard.h"
13#include "qxcbeventqueue.h"
14#include "qxcbeventdispatcher.h"
15#if QT_CONFIG(draganddrop)
16#include "qxcbdrag.h"
17#endif
18#include "qxcbglintegration.h"
19
20#ifndef QT_NO_SESSIONMANAGER
21#include "qxcbsessionmanager.h"
22#endif
23#include "qxcbxsettings.h"
24
25#include <xcb/xcb.h>
26
27#include <QtGui/private/qgenericunixfontdatabase_p.h>
28#include <QtGui/private/qgenericunixservices_p.h>
29
30#include <stdio.h>
31
32#include <QtGui/private/qguiapplication_p.h>
33
34#if QT_CONFIG(xcb_xlib)
35#define register /* C++17 deprecated register */
36#include <X11/Xlib.h>
37#undef register
38#endif
39#if QT_CONFIG(xcb_native_painting)
40#include "qxcbnativepainting.h"
41#include "qpixmap_x11_p.h"
42#include "qbackingstore_x11_p.h"
43#endif
44
45#include <qpa/qplatforminputcontextfactory_p.h>
46#include <private/qgenericunixthemes_p.h>
47#include <qpa/qplatforminputcontext.h>
48
49#include <QtGui/QOpenGLContext>
50#include <QtGui/QScreen>
51#include <QtGui/QOffscreenSurface>
52#if QT_CONFIG(accessibility)
53#include <qpa/qplatformaccessibility.h>
54#if QT_CONFIG(accessibility_atspi_bridge)
55#include <QtGui/private/qspiaccessiblebridge_p.h>
56#endif
57#endif
58
59#include <QtCore/QFileInfo>
60
61#if QT_CONFIG(vulkan)
62#include "qxcbvulkaninstance.h"
63#include "qxcbvulkanwindow.h"
64#endif
65
66QT_BEGIN_NAMESPACE
67
68using namespace Qt::StringLiterals;
69
70// Find out if our parent process is gdb by looking at the 'exe' symlink under /proc,.
71// or, for older Linuxes, read out 'cmdline'.
72static bool runningUnderDebugger()
73{
74#if defined(QT_DEBUG) && defined(Q_OS_LINUX)
75 const QString parentProc = "/proc/"_L1 + QString::number(getppid());
76 const QFileInfo parentProcExe(parentProc + "/exe"_L1);
77 if (parentProcExe.isSymLink())
78 return parentProcExe.symLinkTarget().endsWith(s: "/gdb"_L1);
79 QFile f(parentProc + "/cmdline"_L1);
80 if (!f.open(flags: QIODevice::ReadOnly))
81 return false;
82 QByteArray s;
83 char c;
84 while (f.getChar(c: &c) && c) {
85 if (c == '/')
86 s.clear();
87 else
88 s += c;
89 }
90 return s == "gdb";
91#else
92 return false;
93#endif
94}
95
96QXcbIntegration *QXcbIntegration::m_instance = nullptr;
97
98QXcbIntegration::QXcbIntegration(const QStringList &parameters, int &argc, char **argv)
99 : m_services(new QGenericUnixServices)
100 , m_instanceName(nullptr)
101 , m_canGrab(true)
102 , m_defaultVisualId(UINT_MAX)
103{
104 Q_UNUSED(parameters);
105
106 m_instance = this;
107 qApp->setAttribute(attribute: Qt::AA_CompressHighFrequencyEvents, on: true);
108
109 qRegisterMetaType<QXcbWindow*>();
110#if QT_CONFIG(xcb_xlib)
111 XInitThreads();
112#endif
113 m_nativeInterface.reset(other: new QXcbNativeInterface);
114
115 // Parse arguments
116 const char *displayName = nullptr;
117 bool noGrabArg = false;
118 bool doGrabArg = false;
119 if (argc) {
120 int j = 1;
121 for (int i = 1; i < argc; i++) {
122 QByteArray arg(argv[i]);
123 if (arg.startsWith(bv: "--"))
124 arg.remove(index: 0, len: 1);
125 if (arg == "-display" && i < argc - 1)
126 displayName = argv[++i];
127 else if (arg == "-name" && i < argc - 1)
128 m_instanceName = argv[++i];
129 else if (arg == "-nograb")
130 noGrabArg = true;
131 else if (arg == "-dograb")
132 doGrabArg = true;
133 else if (arg == "-visual" && i < argc - 1) {
134 bool ok = false;
135 m_defaultVisualId = QByteArray(argv[++i]).toUInt(ok: &ok, base: 0);
136 if (!ok)
137 m_defaultVisualId = UINT_MAX;
138 }
139 else
140 argv[j++] = argv[i];
141 }
142 argc = j;
143 } // argc
144
145 bool underDebugger = runningUnderDebugger();
146 if (noGrabArg && doGrabArg && underDebugger) {
147 qWarning(msg: "Both -nograb and -dograb command line arguments specified. Please pick one. -nograb takes precedence");
148 doGrabArg = false;
149 }
150
151#if defined(QT_DEBUG)
152 if (!noGrabArg && !doGrabArg && underDebugger) {
153 qCDebug(lcQpaXcb, "Qt: gdb: -nograb added to command-line options.\n"
154 "\t Use the -dograb option to enforce grabbing.");
155 }
156#endif
157 m_canGrab = (!underDebugger && !noGrabArg) || (underDebugger && doGrabArg);
158
159 static bool canNotGrabEnv = qEnvironmentVariableIsSet(varName: "QT_XCB_NO_GRAB_SERVER");
160 if (canNotGrabEnv)
161 m_canGrab = false;
162
163 m_connection = new QXcbConnection(m_nativeInterface.data(), m_canGrab, m_defaultVisualId, displayName);
164 if (!m_connection->isConnected()) {
165 delete m_connection;
166 m_connection = nullptr;
167 return;
168 }
169
170 m_fontDatabase.reset(other: new QGenericUnixFontDatabase());
171
172#if QT_CONFIG(xcb_native_painting)
173 if (nativePaintingEnabled()) {
174 qCDebug(lcQpaXcb, "QXCB USING NATIVE PAINTING");
175 qt_xcb_native_x11_info_init(connection());
176 }
177#endif
178}
179
180QXcbIntegration::~QXcbIntegration()
181{
182 delete m_connection;
183 m_connection = nullptr;
184 m_instance = nullptr;
185}
186
187QPlatformPixmap *QXcbIntegration::createPlatformPixmap(QPlatformPixmap::PixelType type) const
188{
189#if QT_CONFIG(xcb_native_painting)
190 if (nativePaintingEnabled())
191 return new QX11PlatformPixmap(type);
192#endif
193
194 return QPlatformIntegration::createPlatformPixmap(type);
195}
196
197QPlatformWindow *QXcbIntegration::createPlatformWindow(QWindow *window) const
198{
199 QXcbGlIntegration *glIntegration = nullptr;
200 const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);;
201 if (window->type() != Qt::Desktop && !isTrayIconWindow) {
202 if (window->supportsOpenGL()) {
203 glIntegration = connection()->glIntegration();
204 if (glIntegration) {
205 QXcbWindow *xcbWindow = glIntegration->createWindow(window);
206 xcbWindow->create();
207 return xcbWindow;
208 }
209#if QT_CONFIG(vulkan)
210 } else if (window->surfaceType() == QSurface::VulkanSurface) {
211 QXcbWindow *xcbWindow = new QXcbVulkanWindow(window);
212 xcbWindow->create();
213 return xcbWindow;
214#endif
215 }
216 }
217
218 Q_ASSERT(window->type() == Qt::Desktop || isTrayIconWindow || !window->supportsOpenGL()
219 || (!glIntegration && window->surfaceType() == QSurface::RasterGLSurface)); // for VNC
220 QXcbWindow *xcbWindow = new QXcbWindow(window);
221 xcbWindow->create();
222 return xcbWindow;
223}
224
225QPlatformWindow *QXcbIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
226{
227 return new QXcbForeignWindow(window, nativeHandle);
228}
229
230#ifndef QT_NO_OPENGL
231QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
232{
233 QXcbGlIntegration *glIntegration = m_connection->glIntegration();
234 if (!glIntegration) {
235 qWarning(msg: "QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled");
236 return nullptr;
237 }
238 return glIntegration->createPlatformOpenGLContext(context);
239}
240
241# if QT_CONFIG(xcb_glx_plugin)
242QOpenGLContext *QXcbIntegration::createOpenGLContext(GLXContext context, void *visualInfo, QOpenGLContext *shareContext) const
243{
244 using namespace QNativeInterface::Private;
245 if (auto *glxIntegration = dynamic_cast<QGLXIntegration*>(m_connection->glIntegration()))
246 return glxIntegration->createOpenGLContext(context, visualInfo, shareContext);
247 else
248 return nullptr;
249}
250# endif
251
252#if QT_CONFIG(egl)
253QOpenGLContext *QXcbIntegration::createOpenGLContext(EGLContext context, EGLDisplay display, QOpenGLContext *shareContext) const
254{
255 using namespace QNativeInterface::Private;
256 if (auto *eglIntegration = dynamic_cast<QEGLIntegration*>(m_connection->glIntegration()))
257 return eglIntegration->createOpenGLContext(context, display, shareContext);
258 else
259 return nullptr;
260}
261#endif
262
263#endif // QT_NO_OPENGL
264
265QPlatformBackingStore *QXcbIntegration::createPlatformBackingStore(QWindow *window) const
266{
267 QPlatformBackingStore *backingStore = nullptr;
268
269 const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);
270 if (isTrayIconWindow) {
271 backingStore = new QXcbSystemTrayBackingStore(window);
272#if QT_CONFIG(xcb_native_painting)
273 } else if (nativePaintingEnabled()) {
274 backingStore = new QXcbNativeBackingStore(window);
275#endif
276 } else {
277 backingStore = new QXcbBackingStore(window);
278 }
279 Q_ASSERT(backingStore);
280 return backingStore;
281}
282
283QPlatformOffscreenSurface *QXcbIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
284{
285 QXcbScreen *screen = static_cast<QXcbScreen *>(surface->screen()->handle());
286 QXcbGlIntegration *glIntegration = screen->connection()->glIntegration();
287 if (!glIntegration) {
288 qWarning(msg: "QXcbIntegration: Cannot create platform offscreen surface, neither GLX nor EGL are enabled");
289 return nullptr;
290 }
291 return glIntegration->createPlatformOffscreenSurface(surface);
292}
293
294bool QXcbIntegration::hasCapability(QPlatformIntegration::Capability cap) const
295{
296 switch (cap) {
297 case OpenGL:
298 case ThreadedOpenGL:
299 {
300 if (const auto *integration = connection()->glIntegration())
301 return cap != ThreadedOpenGL || integration->supportsThreadedOpenGL();
302 return false;
303 }
304
305 case ThreadedPixmaps:
306 case WindowMasks:
307 case MultipleWindows:
308 case ForeignWindows:
309 case SyncState:
310 case RasterGLSurface:
311 return true;
312
313 case SwitchableWidgetComposition:
314 {
315 return m_connection->glIntegration()
316 && m_connection->glIntegration()->supportsSwitchableWidgetComposition();
317 }
318
319 default: return QPlatformIntegration::hasCapability(cap);
320 }
321}
322
323QAbstractEventDispatcher *QXcbIntegration::createEventDispatcher() const
324{
325 return QXcbEventDispatcher::createEventDispatcher(connection: connection());
326}
327
328using namespace Qt::Literals::StringLiterals;
329static const auto xsNetCursorBlink = "Net/CursorBlink"_ba;
330static const auto xsNetCursorBlinkTime = "Net/CursorBlinkTime"_ba;
331static const auto xsNetDoubleClickTime = "Net/DoubleClickTime"_ba;
332static const auto xsNetDoubleClickDistance = "Net/DoubleClickDistance"_ba;
333static const auto xsNetDndDragThreshold = "Net/DndDragThreshold"_ba;
334
335void QXcbIntegration::initialize()
336{
337 const auto defaultInputContext = "compose"_L1;
338 // Perform everything that may potentially need the event dispatcher (timers, socket
339 // notifiers) here instead of the constructor.
340 QString icStr = QPlatformInputContextFactory::requested();
341 if (icStr.isNull())
342 icStr = defaultInputContext;
343 m_inputContext.reset(other: QPlatformInputContextFactory::create(key: icStr));
344 if (!m_inputContext && icStr != defaultInputContext && icStr != "none"_L1)
345 m_inputContext.reset(other: QPlatformInputContextFactory::create(key: defaultInputContext));
346
347 connection()->keyboard()->initialize();
348
349 auto notifyThemeChanged = [](QXcbVirtualDesktop *, const QByteArray &, const QVariant &, void *) {
350 QWindowSystemInterface::handleThemeChange();
351 };
352
353 auto *xsettings = connection()->primaryScreen()->xSettings();
354 xsettings->registerCallbackForProperty(property: xsNetCursorBlink, func: notifyThemeChanged, handle: this);
355 xsettings->registerCallbackForProperty(property: xsNetCursorBlinkTime, func: notifyThemeChanged, handle: this);
356 xsettings->registerCallbackForProperty(property: xsNetDoubleClickTime, func: notifyThemeChanged, handle: this);
357 xsettings->registerCallbackForProperty(property: xsNetDoubleClickDistance, func: notifyThemeChanged, handle: this);
358 xsettings->registerCallbackForProperty(property: xsNetDndDragThreshold, func: notifyThemeChanged, handle: this);
359}
360
361void QXcbIntegration::moveToScreen(QWindow *window, int screen)
362{
363 Q_UNUSED(window);
364 Q_UNUSED(screen);
365}
366
367QPlatformFontDatabase *QXcbIntegration::fontDatabase() const
368{
369 return m_fontDatabase.data();
370}
371
372QPlatformNativeInterface * QXcbIntegration::nativeInterface() const
373{
374 return m_nativeInterface.data();
375}
376
377#ifndef QT_NO_CLIPBOARD
378QPlatformClipboard *QXcbIntegration::clipboard() const
379{
380 return m_connection->clipboard();
381}
382#endif
383
384#if QT_CONFIG(draganddrop)
385#include <private/qsimpledrag_p.h>
386QPlatformDrag *QXcbIntegration::drag() const
387{
388 static const bool useSimpleDrag = qEnvironmentVariableIsSet(varName: "QT_XCB_USE_SIMPLE_DRAG");
389 if (Q_UNLIKELY(useSimpleDrag)) { // This is useful for testing purposes
390 static QSimpleDrag *simpleDrag = nullptr;
391 if (!simpleDrag)
392 simpleDrag = new QSimpleDrag();
393 return simpleDrag;
394 }
395
396 return m_connection->drag();
397}
398#endif
399
400QPlatformInputContext *QXcbIntegration::inputContext() const
401{
402 return m_inputContext.data();
403}
404
405#if QT_CONFIG(accessibility)
406QPlatformAccessibility *QXcbIntegration::accessibility() const
407{
408#if !defined(QT_NO_ACCESSIBILITY_ATSPI_BRIDGE)
409 if (!m_accessibility) {
410 Q_ASSERT_X(QCoreApplication::eventDispatcher(), "QXcbIntegration",
411 "Initializing accessibility without event-dispatcher!");
412 m_accessibility.reset(other: new QSpiAccessibleBridge());
413 }
414#endif
415
416 return m_accessibility.data();
417}
418#endif
419
420QPlatformServices *QXcbIntegration::services() const
421{
422 return m_services.data();
423}
424
425Qt::KeyboardModifiers QXcbIntegration::queryKeyboardModifiers() const
426{
427 return m_connection->queryKeyboardModifiers();
428}
429
430QList<int> QXcbIntegration::possibleKeys(const QKeyEvent *e) const
431{
432 return m_connection->keyboard()->possibleKeys(event: e);
433}
434
435QStringList QXcbIntegration::themeNames() const
436{
437 return QGenericUnixTheme::themeNames();
438}
439
440QPlatformTheme *QXcbIntegration::createPlatformTheme(const QString &name) const
441{
442 return QGenericUnixTheme::createUnixTheme(name);
443}
444
445#define RETURN_VALID_XSETTINGS(key) { \
446 auto value = connection()->primaryScreen()->xSettings()->setting(key); \
447 if (value.isValid()) return value; \
448}
449
450QVariant QXcbIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
451{
452 switch (hint) {
453 case QPlatformIntegration::CursorFlashTime: {
454 bool ok = false;
455 // If cursor blinking is off, returns 0 to keep the cursor awlays display.
456 if (connection()->primaryScreen()->xSettings()->setting(xsNetCursorBlink).toInt(ok: &ok) == 0 && ok)
457 return 0;
458
459 RETURN_VALID_XSETTINGS(xsNetCursorBlinkTime);
460 break;
461 }
462 case QPlatformIntegration::MouseDoubleClickInterval:
463 RETURN_VALID_XSETTINGS(xsNetDoubleClickTime);
464 break;
465 case QPlatformIntegration::MouseDoubleClickDistance:
466 RETURN_VALID_XSETTINGS(xsNetDoubleClickDistance);
467 break;
468 case QPlatformIntegration::KeyboardInputInterval:
469 case QPlatformIntegration::StartDragTime:
470 case QPlatformIntegration::KeyboardAutoRepeatRate:
471 case QPlatformIntegration::PasswordMaskDelay:
472 case QPlatformIntegration::StartDragVelocity:
473 case QPlatformIntegration::UseRtlExtensions:
474 case QPlatformIntegration::PasswordMaskCharacter:
475 case QPlatformIntegration::FlickMaximumVelocity:
476 case QPlatformIntegration::FlickDeceleration:
477 // TODO using various xcb, gnome or KDE settings
478 break; // Not implemented, use defaults
479 case QPlatformIntegration::FlickStartDistance:
480 case QPlatformIntegration::StartDragDistance: {
481 RETURN_VALID_XSETTINGS(xsNetDndDragThreshold);
482 // The default (in QPlatformTheme::defaultThemeHint) is 10 pixels, but
483 // on a high-resolution screen it makes sense to increase it.
484 qreal dpi = 100;
485 if (const QXcbScreen *screen = connection()->primaryScreen()) {
486 if (screen->logicalDpi().first > dpi)
487 dpi = screen->logicalDpi().first;
488 if (screen->logicalDpi().second > dpi)
489 dpi = screen->logicalDpi().second;
490 }
491 return (hint == QPlatformIntegration::FlickStartDistance ? qreal(15) : qreal(10)) * dpi / qreal(100);
492 }
493 case QPlatformIntegration::ShowIsFullScreen:
494 // X11 always has support for windows, but the
495 // window manager could prevent it (e.g. matchbox)
496 return false;
497 case QPlatformIntegration::ReplayMousePressOutsidePopup:
498 return false;
499 default:
500 break;
501 }
502 return QPlatformIntegration::styleHint(hint);
503}
504
505static QString argv0BaseName()
506{
507 QString result;
508 const QStringList arguments = QCoreApplication::arguments();
509 if (!arguments.isEmpty() && !arguments.front().isEmpty()) {
510 result = arguments.front();
511 const int lastSlashPos = result.lastIndexOf(c: u'/');
512 if (lastSlashPos != -1)
513 result.remove(i: 0, len: lastSlashPos + 1);
514 }
515 return result;
516}
517
518static const char resourceNameVar[] = "RESOURCE_NAME";
519
520QByteArray QXcbIntegration::wmClass() const
521{
522 if (m_wmClass.isEmpty()) {
523 // Instance name according to ICCCM 4.1.2.5
524 QString name;
525 if (m_instanceName)
526 name = QString::fromLocal8Bit(ba: m_instanceName);
527 if (name.isEmpty() && qEnvironmentVariableIsSet(varName: resourceNameVar))
528 name = QString::fromLocal8Bit(ba: qgetenv(varName: resourceNameVar));
529 if (name.isEmpty())
530 name = argv0BaseName();
531
532 // Note: QCoreApplication::applicationName() cannot be called from the QGuiApplication constructor,
533 // hence this delayed initialization.
534 QString className = QCoreApplication::applicationName();
535 if (className.isEmpty()) {
536 className = argv0BaseName();
537 if (!className.isEmpty() && className.at(i: 0).isLower())
538 className[0] = className.at(i: 0).toUpper();
539 }
540
541 if (!name.isEmpty() && !className.isEmpty())
542 m_wmClass = std::move(name).toLocal8Bit() + '\0' + std::move(className).toLocal8Bit() + '\0';
543 }
544 return m_wmClass;
545}
546
547#if QT_CONFIG(xcb_sm)
548QPlatformSessionManager *QXcbIntegration::createPlatformSessionManager(const QString &id, const QString &key) const
549{
550 return new QXcbSessionManager(id, key);
551}
552#endif
553
554void QXcbIntegration::sync()
555{
556 m_connection->sync();
557}
558
559// For QApplication::beep()
560void QXcbIntegration::beep() const
561{
562 QScreen *priScreen = QGuiApplication::primaryScreen();
563 if (!priScreen)
564 return;
565 QPlatformScreen *screen = priScreen->handle();
566 if (!screen)
567 return;
568 xcb_connection_t *connection = static_cast<QXcbScreen *>(screen)->xcb_connection();
569 xcb_bell(c: connection, percent: 0);
570 xcb_flush(c: connection);
571}
572
573bool QXcbIntegration::nativePaintingEnabled() const
574{
575#if QT_CONFIG(xcb_native_painting)
576 static bool enabled = qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING");
577 return enabled;
578#else
579 return false;
580#endif
581}
582
583#if QT_CONFIG(vulkan)
584QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
585{
586 return new QXcbVulkanInstance(instance);
587}
588#endif
589
590void QXcbIntegration::setApplicationBadge(qint64 number)
591{
592 auto unixServices = dynamic_cast<QGenericUnixServices *>(services());
593 unixServices->setApplicationBadge(number);
594}
595
596QT_END_NAMESPACE
597

source code of qtbase/src/plugins/platforms/xcb/qxcbintegration.cpp