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