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 return true;
302 case OffscreenSurface:
303 return m_connection->glIntegration() && m_connection->glIntegration()->canCreatePlatformOffscreenSurface();
304
305 case SwitchableWidgetComposition:
306 {
307 return m_connection->glIntegration()
308 && m_connection->glIntegration()->supportsSwitchableWidgetComposition();
309 }
310
311 default: return QPlatformIntegration::hasCapability(cap);
312 }
313}
314
315QAbstractEventDispatcher *QXcbIntegration::createEventDispatcher() const
316{
317 return QXcbEventDispatcher::createEventDispatcher(connection());
318}
319
320static const auto xsNetCursorBlink = "Net/CursorBlink"_ba;
321static const auto xsNetCursorBlinkTime = "Net/CursorBlinkTime"_ba;
322static const auto xsNetDoubleClickTime = "Net/DoubleClickTime"_ba;
323static const auto xsNetDoubleClickDistance = "Net/DoubleClickDistance"_ba;
324static const auto xsNetDndDragThreshold = "Net/DndDragThreshold"_ba;
325
326void QXcbIntegration::initialize()
327{
328 const auto defaultInputContext = "compose"_L1;
329 // Perform everything that may potentially need the event dispatcher (timers, socket
330 // notifiers) here instead of the constructor.
331 auto icStrs = QPlatformInputContextFactory::requested();
332 if (icStrs.isEmpty())
333 icStrs = { defaultInputContext };
334 m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
335 if (!m_inputContext && !icStrs.contains(defaultInputContext)
336 && icStrs != QStringList{"none"_L1})
337 m_inputContext.reset(QPlatformInputContextFactory::create(defaultInputContext));
338
339 connection()->keyboard()->initialize();
340
341 auto notifyThemeChanged = [](QXcbVirtualDesktop *, const QByteArray &, const QVariant &, void *) {
342 QWindowSystemInterface::handleThemeChange();
343 };
344
345 auto *xsettings = connection()->primaryScreen()->xSettings();
346 xsettings->registerCallbackForProperty(xsNetCursorBlink, notifyThemeChanged, this);
347 xsettings->registerCallbackForProperty(xsNetCursorBlinkTime, notifyThemeChanged, this);
348 xsettings->registerCallbackForProperty(xsNetDoubleClickTime, notifyThemeChanged, this);
349 xsettings->registerCallbackForProperty(xsNetDoubleClickDistance, notifyThemeChanged, this);
350 xsettings->registerCallbackForProperty(xsNetDndDragThreshold, notifyThemeChanged, this);
351}
352
353void QXcbIntegration::moveToScreen(QWindow *window, int screen)
354{
355 Q_UNUSED(window);
356 Q_UNUSED(screen);
357}
358
359QPlatformFontDatabase *QXcbIntegration::fontDatabase() const
360{
361 return m_fontDatabase.data();
362}
363
364QPlatformNativeInterface * QXcbIntegration::nativeInterface() const
365{
366 return m_nativeInterface.data();
367}
368
369#ifndef QT_NO_CLIPBOARD
370QPlatformClipboard *QXcbIntegration::clipboard() const
371{
372 return m_connection->clipboard();
373}
374#endif
375
376#if QT_CONFIG(draganddrop)
377#include <private/qsimpledrag_p.h>
378QPlatformDrag *QXcbIntegration::drag() const
379{
380 static const bool useSimpleDrag = qEnvironmentVariableIsSet("QT_XCB_USE_SIMPLE_DRAG");
381 if (Q_UNLIKELY(useSimpleDrag)) { // This is useful for testing purposes
382 static QSimpleDrag *simpleDrag = nullptr;
383 if (!simpleDrag)
384 simpleDrag = new QSimpleDrag();
385 return simpleDrag;
386 }
387
388 return m_connection->drag();
389}
390#endif
391
392QPlatformInputContext *QXcbIntegration::inputContext() const
393{
394 return m_inputContext.data();
395}
396
397#if QT_CONFIG(accessibility)
398QPlatformAccessibility *QXcbIntegration::accessibility() const
399{
400#if !defined(QT_NO_ACCESSIBILITY_ATSPI_BRIDGE)
401 if (!m_accessibility) {
402 Q_ASSERT_X(QCoreApplication::eventDispatcher(), "QXcbIntegration",
403 "Initializing accessibility without event-dispatcher!");
404 m_accessibility.reset(new QSpiAccessibleBridge());
405 }
406#endif
407
408 return m_accessibility.data();
409}
410#endif
411
412QPlatformServices *QXcbIntegration::services() const
413{
414 return m_services.data();
415}
416
417QPlatformKeyMapper *QXcbIntegration::keyMapper() const
418{
419 return m_connection->keyboard();
420}
421
422QStringList QXcbIntegration::themeNames() const
423{
424 return QGenericUnixTheme::themeNames();
425}
426
427QPlatformTheme *QXcbIntegration::createPlatformTheme(const QString &name) const
428{
429 return QGenericUnixTheme::createUnixTheme(name);
430}
431
432#define RETURN_VALID_XSETTINGS(key) {
433 auto value = connection()->primaryScreen()->xSettings()->setting(key);
434 if (value.isValid()) return value; \
435}
436
437QVariant QXcbIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
438{
439 switch (hint) {
440 case QPlatformIntegration::CursorFlashTime: {
441 bool ok = false;
442 // If cursor blinking is off, returns 0 to keep the cursor awlays display.
443 if (connection()->primaryScreen()->xSettings()->setting(xsNetCursorBlink).toInt(&ok) == 0 && ok)
444 return 0;
445
446 RETURN_VALID_XSETTINGS(xsNetCursorBlinkTime);
447 break;
448 }
449 case QPlatformIntegration::MouseDoubleClickInterval:
450 RETURN_VALID_XSETTINGS(xsNetDoubleClickTime);
451 break;
452 case QPlatformIntegration::MouseDoubleClickDistance:
453 RETURN_VALID_XSETTINGS(xsNetDoubleClickDistance);
454 break;
455 case QPlatformIntegration::KeyboardInputInterval:
456 case QPlatformIntegration::StartDragTime:
457 case QPlatformIntegration::KeyboardAutoRepeatRate:
458 case QPlatformIntegration::PasswordMaskDelay:
459 case QPlatformIntegration::StartDragVelocity:
460 case QPlatformIntegration::UseRtlExtensions:
461 case QPlatformIntegration::PasswordMaskCharacter:
462 case QPlatformIntegration::FlickMaximumVelocity:
463 case QPlatformIntegration::FlickDeceleration:
464 // TODO using various xcb, gnome or KDE settings
465 break; // Not implemented, use defaults
466 case QPlatformIntegration::FlickStartDistance:
467 case QPlatformIntegration::StartDragDistance: {
468 RETURN_VALID_XSETTINGS(xsNetDndDragThreshold);
469 // The default (in QPlatformTheme::defaultThemeHint) is 10 pixels, but
470 // on a high-resolution screen it makes sense to increase it.
471 qreal dpi = 100;
472 if (const QXcbScreen *screen = connection()->primaryScreen()) {
473 if (screen->logicalDpi().first > dpi)
474 dpi = screen->logicalDpi().first;
475 if (screen->logicalDpi().second > dpi)
476 dpi = screen->logicalDpi().second;
477 }
478 return (hint == QPlatformIntegration::FlickStartDistance ? qreal(15) : qreal(10)) * dpi / qreal(100);
479 }
480 case QPlatformIntegration::ShowIsFullScreen:
481 // X11 always has support for windows, but the
482 // window manager could prevent it (e.g. matchbox)
483 return false;
484 case QPlatformIntegration::ReplayMousePressOutsidePopup:
485 return false;
486 default:
487 break;
488 }
489 return QPlatformIntegration::styleHint(hint);
490}
491
493{
494 QString result;
495 const QStringList arguments = QCoreApplication::arguments();
496 if (!arguments.isEmpty() && !arguments.front().isEmpty()) {
497 result = arguments.front();
498 const int lastSlashPos = result.lastIndexOf(u'/');
499 if (lastSlashPos != -1)
500 result.remove(0, lastSlashPos + 1);
501 }
502 return result;
503}
504
505static const char resourceNameVar[] = "RESOURCE_NAME";
506
507QByteArray QXcbIntegration::wmClass() const
508{
509 if (m_wmClass.isEmpty()) {
510 // Instance name according to ICCCM 4.1.2.5
511 QString name;
512 if (m_instanceName)
513 name = QString::fromLocal8Bit(m_instanceName);
514 if (name.isEmpty() && qEnvironmentVariableIsSet(resourceNameVar))
515 name = qEnvironmentVariable(resourceNameVar);
516 if (name.isEmpty())
517 name = argv0BaseName();
518
519 // Note: QCoreApplication::applicationName() cannot be called from the QGuiApplication constructor,
520 // hence this delayed initialization.
521 QString className = QCoreApplication::applicationName();
522 if (className.isEmpty()) {
523 className = argv0BaseName();
524 if (!className.isEmpty() && className.at(0).isLower())
525 className[0] = className.at(0).toUpper();
526 }
527
528 if (!name.isEmpty() && !className.isEmpty())
529 m_wmClass = std::move(name).toLocal8Bit() + '\0' + std::move(className).toLocal8Bit() + '\0';
530 }
531 return m_wmClass;
532}
533
534#if QT_CONFIG(xcb_sm)
535QPlatformSessionManager *QXcbIntegration::createPlatformSessionManager(const QString &id, const QString &key) const
536{
537 return new QXcbSessionManager(id, key);
538}
539#endif
540
541void QXcbIntegration::sync()
542{
543 m_connection->sync();
544}
545
546// For QApplication::beep()
547void QXcbIntegration::beep() const
548{
549 QScreen *priScreen = QGuiApplication::primaryScreen();
550 if (!priScreen)
551 return;
552 QPlatformScreen *screen = priScreen->handle();
553 if (!screen)
554 return;
555 xcb_connection_t *connection = static_cast<QXcbScreen *>(screen)->xcb_connection();
556 xcb_bell(connection, 0);
557 xcb_flush(connection);
558}
559
560#if QT_CONFIG(vulkan)
561QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
562{
563 return new QXcbVulkanInstance(instance);
564}
565#endif
566
567void QXcbIntegration::setApplicationBadge(qint64 number)
568{
569 auto unixServices = dynamic_cast<QDesktopUnixServices *>(services());
570 unixServices->setApplicationBadge(number);
571}
572
574{
575 return "x11:"_L1 + QString::number(window->winId(), 16);
576}
577
578void QXcbUnixServices::registerDBusMenuForWindow(QWindow *window, const QString &service, const QString &path)
579{
580 const QByteArray serviceValue = service.toLatin1();
581 const QByteArray pathValue = path.toLatin1();
582
583 xcb_change_property(xcb_connection(),
584 XCB_PROP_MODE_REPLACE, window->winId(),
586 XCB_ATOM_STRING, 8,
587 serviceValue.length(),
588 serviceValue.constData());
589
590 xcb_change_property(xcb_connection(),
591 XCB_PROP_MODE_REPLACE, window->winId(),
593 XCB_ATOM_STRING, 8,
594 pathValue.length(),
595 pathValue.constData());
596}
597
599{
600 xcb_delete_property(xcb_connection(), window->winId(), atom(QXcbAtom::Atom_KDE_NET_WM_APPMENU_SERVICE_NAME));
601 xcb_delete_property(xcb_connection(), window->winId(), atom(QXcbAtom::Atom_KDE_NET_WM_APPMENU_OBJECT_PATH));
602}
603
604QT_END_NAMESPACE
@ Atom_KDE_NET_WM_APPMENU_SERVICE_NAME
Definition qxcbatom.h:216
@ Atom_KDE_NET_WM_APPMENU_OBJECT_PATH
Definition qxcbatom.h:217
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[]