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