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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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