1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qxcbintegration.h"
41#include "qxcbconnection.h"
42#include "qxcbscreen.h"
43#include "qxcbwindow.h"
44#include "qxcbcursor.h"
45#include "qxcbkeyboard.h"
46#include "qxcbbackingstore.h"
47#include "qxcbnativeinterface.h"
48#include "qxcbclipboard.h"
49#include "qxcbeventqueue.h"
50#include "qxcbeventdispatcher.h"
51#if QT_CONFIG(draganddrop)
52#include "qxcbdrag.h"
53#endif
54#include "qxcbglintegration.h"
55
56#ifndef QT_NO_SESSIONMANAGER
57#include "qxcbsessionmanager.h"
58#endif
59
60#include <xcb/xcb.h>
61
62#include <QtFontDatabaseSupport/private/qgenericunixfontdatabase_p.h>
63#include <QtServiceSupport/private/qgenericunixservices_p.h>
64
65#include <stdio.h>
66
67#include <QtGui/private/qguiapplication_p.h>
68
69#if QT_CONFIG(xcb_xlib)
70#define register /* C++17 deprecated register */
71#include <X11/Xlib.h>
72#undef register
73#endif
74#if QT_CONFIG(xcb_native_painting)
75#include "qxcbnativepainting.h"
76#include "qpixmap_x11_p.h"
77#include "qbackingstore_x11_p.h"
78#endif
79
80#include <qpa/qplatforminputcontextfactory_p.h>
81#include <private/qgenericunixthemes_p.h>
82#include <qpa/qplatforminputcontext.h>
83
84#include <QtGui/QOpenGLContext>
85#include <QtGui/QScreen>
86#include <QtGui/QOffscreenSurface>
87#ifndef QT_NO_ACCESSIBILITY
88#include <qpa/qplatformaccessibility.h>
89#ifndef QT_NO_ACCESSIBILITY_ATSPI_BRIDGE
90#include <QtLinuxAccessibilitySupport/private/bridge_p.h>
91#endif
92#endif
93
94#include <QtCore/QFileInfo>
95
96#if QT_CONFIG(vulkan)
97#include "qxcbvulkaninstance.h"
98#include "qxcbvulkanwindow.h"
99#endif
100
101QT_BEGIN_NAMESPACE
102
103// Find out if our parent process is gdb by looking at the 'exe' symlink under /proc,.
104// or, for older Linuxes, read out 'cmdline'.
105static bool runningUnderDebugger()
106{
107#if defined(QT_DEBUG) && defined(Q_OS_LINUX)
108 const QString parentProc = QLatin1String("/proc/") + QString::number(getppid());
109 const QFileInfo parentProcExe(parentProc + QLatin1String("/exe"));
110 if (parentProcExe.isSymLink())
111 return parentProcExe.symLinkTarget().endsWith(s: QLatin1String("/gdb"));
112 QFile f(parentProc + QLatin1String("/cmdline"));
113 if (!f.open(flags: QIODevice::ReadOnly))
114 return false;
115 QByteArray s;
116 char c;
117 while (f.getChar(c: &c) && c) {
118 if (c == '/')
119 s.clear();
120 else
121 s += c;
122 }
123 return s == "gdb";
124#else
125 return false;
126#endif
127}
128
129QXcbIntegration *QXcbIntegration::m_instance = nullptr;
130
131QXcbIntegration::QXcbIntegration(const QStringList &parameters, int &argc, char **argv)
132 : m_services(new QGenericUnixServices)
133 , m_instanceName(nullptr)
134 , m_canGrab(true)
135 , m_defaultVisualId(UINT_MAX)
136{
137 m_instance = this;
138 qApp->setAttribute(attribute: Qt::AA_CompressHighFrequencyEvents, on: true);
139
140 qRegisterMetaType<QXcbWindow*>();
141#if QT_CONFIG(xcb_xlib)
142 XInitThreads();
143#endif
144 m_nativeInterface.reset(other: new QXcbNativeInterface);
145
146 // Parse arguments
147 const char *displayName = nullptr;
148 bool noGrabArg = false;
149 bool doGrabArg = false;
150 if (argc) {
151 int j = 1;
152 for (int i = 1; i < argc; i++) {
153 QByteArray arg(argv[i]);
154 if (arg.startsWith(c: "--"))
155 arg.remove(index: 0, len: 1);
156 if (arg == "-display" && i < argc - 1)
157 displayName = argv[++i];
158 else if (arg == "-name" && i < argc - 1)
159 m_instanceName = argv[++i];
160 else if (arg == "-nograb")
161 noGrabArg = true;
162 else if (arg == "-dograb")
163 doGrabArg = true;
164 else if (arg == "-visual" && i < argc - 1) {
165 bool ok = false;
166 m_defaultVisualId = QByteArray(argv[++i]).toUInt(ok: &ok, base: 0);
167 if (!ok)
168 m_defaultVisualId = UINT_MAX;
169 }
170 else
171 argv[j++] = argv[i];
172 }
173 argc = j;
174 } // argc
175
176 bool underDebugger = runningUnderDebugger();
177 if (noGrabArg && doGrabArg && underDebugger) {
178 qWarning(msg: "Both -nograb and -dograb command line arguments specified. Please pick one. -nograb takes prcedence");
179 doGrabArg = false;
180 }
181
182#if defined(QT_DEBUG)
183 if (!noGrabArg && !doGrabArg && underDebugger) {
184 qCDebug(lcQpaXcb, "Qt: gdb: -nograb added to command-line options.\n"
185 "\t Use the -dograb option to enforce grabbing.");
186 }
187#endif
188 m_canGrab = (!underDebugger && !noGrabArg) || (underDebugger && doGrabArg);
189
190 static bool canNotGrabEnv = qEnvironmentVariableIsSet(varName: "QT_XCB_NO_GRAB_SERVER");
191 if (canNotGrabEnv)
192 m_canGrab = false;
193
194 const int numParameters = parameters.size();
195 m_connections.reserve(alloc: 1 + numParameters / 2);
196
197 auto conn = new QXcbConnection(m_nativeInterface.data(), m_canGrab, m_defaultVisualId, displayName);
198 if (!conn->isConnected()) {
199 delete conn;
200 return;
201 }
202 m_connections << conn;
203
204 // ### Qt 6 (QTBUG-52408) remove this multi-connection code path
205 for (int i = 0; i < numParameters - 1; i += 2) {
206 qCDebug(lcQpaXcb) << "connecting to additional display: " << parameters.at(i) << parameters.at(i: i+1);
207 QString display = parameters.at(i) + QLatin1Char(':') + parameters.at(i: i+1);
208 conn = new QXcbConnection(m_nativeInterface.data(), m_canGrab, m_defaultVisualId, display.toLatin1().constData());
209 if (conn->isConnected())
210 m_connections << conn;
211 else
212 delete conn;
213 }
214
215 m_fontDatabase.reset(other: new QGenericUnixFontDatabase());
216
217#if QT_CONFIG(xcb_native_painting)
218 if (nativePaintingEnabled()) {
219 qCDebug(lcQpaXcb, "QXCB USING NATIVE PAINTING");
220 qt_xcb_native_x11_info_init(defaultConnection());
221 }
222#endif
223}
224
225QXcbIntegration::~QXcbIntegration()
226{
227 qDeleteAll(c: m_connections);
228 m_instance = nullptr;
229}
230
231QPlatformPixmap *QXcbIntegration::createPlatformPixmap(QPlatformPixmap::PixelType type) const
232{
233#if QT_CONFIG(xcb_native_painting)
234 if (nativePaintingEnabled())
235 return new QX11PlatformPixmap(type);
236#endif
237
238 return QPlatformIntegration::createPlatformPixmap(type);
239}
240
241QPlatformWindow *QXcbIntegration::createPlatformWindow(QWindow *window) const
242{
243 QXcbGlIntegration *glIntegration = nullptr;
244 const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);;
245 if (window->type() != Qt::Desktop && !isTrayIconWindow) {
246 if (window->supportsOpenGL()) {
247 glIntegration = defaultConnection()->glIntegration();
248 if (glIntegration) {
249 QXcbWindow *xcbWindow = glIntegration->createWindow(window);
250 xcbWindow->create();
251 return xcbWindow;
252 }
253#if QT_CONFIG(vulkan)
254 } else if (window->surfaceType() == QSurface::VulkanSurface) {
255 QXcbWindow *xcbWindow = new QXcbVulkanWindow(window);
256 xcbWindow->create();
257 return xcbWindow;
258#endif
259 }
260 }
261
262 Q_ASSERT(window->type() == Qt::Desktop || isTrayIconWindow || !window->supportsOpenGL()
263 || (!glIntegration && window->surfaceType() == QSurface::RasterGLSurface)); // for VNC
264 QXcbWindow *xcbWindow = new QXcbWindow(window);
265 xcbWindow->create();
266 return xcbWindow;
267}
268
269QPlatformWindow *QXcbIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
270{
271 return new QXcbForeignWindow(window, nativeHandle);
272}
273
274#ifndef QT_NO_OPENGL
275QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
276{
277 QXcbScreen *screen = static_cast<QXcbScreen *>(context->screen()->handle());
278 QXcbGlIntegration *glIntegration = screen->connection()->glIntegration();
279 if (!glIntegration) {
280 qWarning(msg: "QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled");
281 return nullptr;
282 }
283 return glIntegration->createPlatformOpenGLContext(context);
284}
285#endif
286
287QPlatformBackingStore *QXcbIntegration::createPlatformBackingStore(QWindow *window) const
288{
289 const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);
290 if (isTrayIconWindow)
291 return new QXcbSystemTrayBackingStore(window);
292
293#if QT_CONFIG(xcb_native_painting)
294 if (nativePaintingEnabled())
295 return new QXcbNativeBackingStore(window);
296#endif
297
298 return new QXcbBackingStore(window);
299}
300
301QPlatformOffscreenSurface *QXcbIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
302{
303 QXcbScreen *screen = static_cast<QXcbScreen *>(surface->screen()->handle());
304 QXcbGlIntegration *glIntegration = screen->connection()->glIntegration();
305 if (!glIntegration) {
306 qWarning(msg: "QXcbIntegration: Cannot create platform offscreen surface, neither GLX nor EGL are enabled");
307 return nullptr;
308 }
309 return glIntegration->createPlatformOffscreenSurface(surface);
310}
311
312bool QXcbIntegration::hasCapability(QPlatformIntegration::Capability cap) const
313{
314 switch (cap) {
315 case OpenGL:
316 case ThreadedOpenGL:
317 {
318 if (const auto *integration = defaultConnection()->glIntegration())
319 return cap != ThreadedOpenGL || integration->supportsThreadedOpenGL();
320 return false;
321 }
322
323 case ThreadedPixmaps:
324 case WindowMasks:
325 case MultipleWindows:
326 case ForeignWindows:
327 case SyncState:
328 case RasterGLSurface:
329 return true;
330
331 case SwitchableWidgetComposition:
332 {
333 return m_connections.at(i: 0)->glIntegration()
334 && m_connections.at(i: 0)->glIntegration()->supportsSwitchableWidgetComposition();
335 }
336
337 default: return QPlatformIntegration::hasCapability(cap);
338 }
339}
340
341QAbstractEventDispatcher *QXcbIntegration::createEventDispatcher() const
342{
343 return QXcbEventDispatcher::createEventDispatcher(connection: defaultConnection());
344}
345
346void QXcbIntegration::initialize()
347{
348 const QLatin1String defaultInputContext("compose");
349 // Perform everything that may potentially need the event dispatcher (timers, socket
350 // notifiers) here instead of the constructor.
351 QString icStr = QPlatformInputContextFactory::requested();
352 if (icStr.isNull())
353 icStr = defaultInputContext;
354 m_inputContext.reset(other: QPlatformInputContextFactory::create(key: icStr));
355 if (!m_inputContext && icStr != defaultInputContext && icStr != QLatin1String("none"))
356 m_inputContext.reset(other: QPlatformInputContextFactory::create(key: defaultInputContext));
357
358 defaultConnection()->keyboard()->initialize();
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_connections.at(i: 0)->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_connections.at(i: 0)->drag();
397}
398#endif
399
400QPlatformInputContext *QXcbIntegration::inputContext() const
401{
402 return m_inputContext.data();
403}
404
405#ifndef QT_NO_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_connections.at(i: 0)->queryKeyboardModifiers();
428}
429
430QList<int> QXcbIntegration::possibleKeys(const QKeyEvent *e) const
431{
432 return m_connections.at(i: 0)->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
445QVariant QXcbIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
446{
447 switch (hint) {
448 case QPlatformIntegration::CursorFlashTime:
449 case QPlatformIntegration::KeyboardInputInterval:
450 case QPlatformIntegration::MouseDoubleClickInterval:
451 case QPlatformIntegration::StartDragTime:
452 case QPlatformIntegration::KeyboardAutoRepeatRate:
453 case QPlatformIntegration::PasswordMaskDelay:
454 case QPlatformIntegration::StartDragVelocity:
455 case QPlatformIntegration::UseRtlExtensions:
456 case QPlatformIntegration::PasswordMaskCharacter:
457 // TODO using various xcb, gnome or KDE settings
458 break; // Not implemented, use defaults
459 case QPlatformIntegration::StartDragDistance: {
460 // The default (in QPlatformTheme::defaultThemeHint) is 10 pixels, but
461 // on a high-resolution screen it makes sense to increase it.
462 qreal dpi = 100.0;
463 if (const QXcbScreen *screen = defaultConnection()->primaryScreen()) {
464 if (screen->logicalDpi().first > dpi)
465 dpi = screen->logicalDpi().first;
466 if (screen->logicalDpi().second > dpi)
467 dpi = screen->logicalDpi().second;
468 }
469 return 10.0 * dpi / 100.0;
470 }
471 case QPlatformIntegration::ShowIsFullScreen:
472 // X11 always has support for windows, but the
473 // window manager could prevent it (e.g. matchbox)
474 return false;
475 case QPlatformIntegration::ReplayMousePressOutsidePopup:
476 return false;
477 default:
478 break;
479 }
480 return QPlatformIntegration::styleHint(hint);
481}
482
483static QString argv0BaseName()
484{
485 QString result;
486 const QStringList arguments = QCoreApplication::arguments();
487 if (!arguments.isEmpty() && !arguments.front().isEmpty()) {
488 result = arguments.front();
489 const int lastSlashPos = result.lastIndexOf(c: QLatin1Char('/'));
490 if (lastSlashPos != -1)
491 result.remove(i: 0, len: lastSlashPos + 1);
492 }
493 return result;
494}
495
496static const char resourceNameVar[] = "RESOURCE_NAME";
497
498QByteArray QXcbIntegration::wmClass() const
499{
500 if (m_wmClass.isEmpty()) {
501 // Instance name according to ICCCM 4.1.2.5
502 QString name;
503 if (m_instanceName)
504 name = QString::fromLocal8Bit(str: m_instanceName);
505 if (name.isEmpty() && qEnvironmentVariableIsSet(varName: resourceNameVar))
506 name = QString::fromLocal8Bit(str: qgetenv(varName: resourceNameVar));
507 if (name.isEmpty())
508 name = argv0BaseName();
509
510 // Note: QCoreApplication::applicationName() cannot be called from the QGuiApplication constructor,
511 // hence this delayed initialization.
512 QString className = QCoreApplication::applicationName();
513 if (className.isEmpty()) {
514 className = argv0BaseName();
515 if (!className.isEmpty() && className.at(i: 0).isLower())
516 className[0] = className.at(i: 0).toUpper();
517 }
518
519 if (!name.isEmpty() && !className.isEmpty())
520 m_wmClass = std::move(name).toLocal8Bit() + '\0' + std::move(className).toLocal8Bit() + '\0';
521 }
522 return m_wmClass;
523}
524
525#if QT_CONFIG(xcb_sm)
526QPlatformSessionManager *QXcbIntegration::createPlatformSessionManager(const QString &id, const QString &key) const
527{
528 return new QXcbSessionManager(id, key);
529}
530#endif
531
532void QXcbIntegration::sync()
533{
534 for (int i = 0; i < m_connections.size(); i++) {
535 m_connections.at(i)->sync();
536 }
537}
538
539// For QApplication::beep()
540void QXcbIntegration::beep() const
541{
542 QScreen *priScreen = QGuiApplication::primaryScreen();
543 if (!priScreen)
544 return;
545 QPlatformScreen *screen = priScreen->handle();
546 if (!screen)
547 return;
548 xcb_connection_t *connection = static_cast<QXcbScreen *>(screen)->xcb_connection();
549 xcb_bell(c: connection, percent: 0);
550 xcb_flush(c: connection);
551}
552
553bool QXcbIntegration::nativePaintingEnabled() const
554{
555#if QT_CONFIG(xcb_native_painting)
556 static bool enabled = qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING");
557 return enabled;
558#else
559 return false;
560#endif
561}
562
563#if QT_CONFIG(vulkan)
564QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
565{
566 return new QXcbVulkanInstance(instance);
567}
568#endif
569
570QT_END_NAMESPACE
571

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