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(QLatin1String("/gdb"));
112 QFile f(parentProc + QLatin1String("/cmdline"));
113 if (!f.open(QIODevice::ReadOnly))
114 return false;
115 QByteArray s;
116 char c;
117 while (f.getChar(&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(0)
134 , m_canGrab(true)
135 , m_defaultVisualId(UINT_MAX)
136{
137 m_instance = this;
138 qApp->setAttribute(Qt::AA_CompressHighFrequencyEvents, true);
139
140 QWindowSystemInterface::setPlatformFiltersEvents(true);
141
142 qRegisterMetaType<QXcbWindow*>();
143#if QT_CONFIG(xcb_xlib)
144 XInitThreads();
145#endif
146 m_nativeInterface.reset(new QXcbNativeInterface);
147
148 // Parse arguments
149 const char *displayName = 0;
150 bool noGrabArg = false;
151 bool doGrabArg = false;
152 if (argc) {
153 int j = 1;
154 for (int i = 1; i < argc; i++) {
155 QByteArray arg(argv[i]);
156 if (arg.startsWith("--"))
157 arg.remove(0, 1);
158 if (arg == "-display" && i < argc - 1)
159 displayName = argv[++i];
160 else if (arg == "-name" && i < argc - 1)
161 m_instanceName = argv[++i];
162 else if (arg == "-nograb")
163 noGrabArg = true;
164 else if (arg == "-dograb")
165 doGrabArg = true;
166 else if (arg == "-visual" && i < argc - 1) {
167 bool ok = false;
168 m_defaultVisualId = QByteArray(argv[++i]).toUInt(&ok, 0);
169 if (!ok)
170 m_defaultVisualId = UINT_MAX;
171 }
172 else
173 argv[j++] = argv[i];
174 }
175 argc = j;
176 } // argc
177
178 bool underDebugger = runningUnderDebugger();
179 if (noGrabArg && doGrabArg && underDebugger) {
180 qWarning("Both -nograb and -dograb command line arguments specified. Please pick one. -nograb takes prcedence");
181 doGrabArg = false;
182 }
183
184#if defined(QT_DEBUG)
185 if (!noGrabArg && !doGrabArg && underDebugger) {
186 qCDebug(lcQpaXcb, "Qt: gdb: -nograb added to command-line options.\n"
187 "\t Use the -dograb option to enforce grabbing.");
188 }
189#endif
190 m_canGrab = (!underDebugger && !noGrabArg) || (underDebugger && doGrabArg);
191
192 static bool canNotGrabEnv = qEnvironmentVariableIsSet("QT_XCB_NO_GRAB_SERVER");
193 if (canNotGrabEnv)
194 m_canGrab = false;
195
196 const int numParameters = parameters.size();
197 m_connections.reserve(1 + numParameters / 2);
198
199 auto conn = new QXcbConnection(m_nativeInterface.data(), m_canGrab, m_defaultVisualId, displayName);
200 if (!conn->isConnected()) {
201 delete conn;
202 return;
203 }
204 m_connections << conn;
205
206 // ### Qt 6 (QTBUG-52408) remove this multi-connection code path
207 for (int i = 0; i < numParameters - 1; i += 2) {
208 qCDebug(lcQpaXcb) << "connecting to additional display: " << parameters.at(i) << parameters.at(i+1);
209 QString display = parameters.at(i) + QLatin1Char(':') + parameters.at(i+1);
210 conn = new QXcbConnection(m_nativeInterface.data(), m_canGrab, m_defaultVisualId, display.toLatin1().constData());
211 if (conn->isConnected())
212 m_connections << conn;
213 else
214 delete conn;
215 }
216
217 m_fontDatabase.reset(new QGenericUnixFontDatabase());
218
219#if QT_CONFIG(xcb_native_painting)
220 if (nativePaintingEnabled()) {
221 qCDebug(lcQpaXcb, "QXCB USING NATIVE PAINTING");
222 qt_xcb_native_x11_info_init(defaultConnection());
223 }
224#endif
225}
226
227QXcbIntegration::~QXcbIntegration()
228{
229 qDeleteAll(m_connections);
230 m_instance = nullptr;
231}
232
233QPlatformPixmap *QXcbIntegration::createPlatformPixmap(QPlatformPixmap::PixelType type) const
234{
235#if QT_CONFIG(xcb_native_painting)
236 if (nativePaintingEnabled())
237 return new QX11PlatformPixmap(type);
238#endif
239
240 return QPlatformIntegration::createPlatformPixmap(type);
241}
242
243QPlatformWindow *QXcbIntegration::createPlatformWindow(QWindow *window) const
244{
245 QXcbGlIntegration *glIntegration = nullptr;
246 const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);;
247 if (window->type() != Qt::Desktop && !isTrayIconWindow) {
248 if (window->supportsOpenGL()) {
249 glIntegration = defaultConnection()->glIntegration();
250 if (glIntegration) {
251 QXcbWindow *xcbWindow = glIntegration->createWindow(window);
252 xcbWindow->create();
253 return xcbWindow;
254 }
255#if QT_CONFIG(vulkan)
256 } else if (window->surfaceType() == QSurface::VulkanSurface) {
257 QXcbWindow *xcbWindow = new QXcbVulkanWindow(window);
258 xcbWindow->create();
259 return xcbWindow;
260#endif
261 }
262 }
263
264 Q_ASSERT(window->type() == Qt::Desktop || isTrayIconWindow || !window->supportsOpenGL()
265 || (!glIntegration && window->surfaceType() == QSurface::RasterGLSurface)); // for VNC
266 QXcbWindow *xcbWindow = new QXcbWindow(window);
267 xcbWindow->create();
268 return xcbWindow;
269}
270
271QPlatformWindow *QXcbIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
272{
273 return new QXcbForeignWindow(window, nativeHandle);
274}
275
276#ifndef QT_NO_OPENGL
277QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
278{
279 QXcbScreen *screen = static_cast<QXcbScreen *>(context->screen()->handle());
280 QXcbGlIntegration *glIntegration = screen->connection()->glIntegration();
281 if (!glIntegration) {
282 qWarning("QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled");
283 return nullptr;
284 }
285 return glIntegration->createPlatformOpenGLContext(context);
286}
287#endif
288
289QPlatformBackingStore *QXcbIntegration::createPlatformBackingStore(QWindow *window) const
290{
291 const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);
292 if (isTrayIconWindow)
293 return new QXcbSystemTrayBackingStore(window);
294
295#if QT_CONFIG(xcb_native_painting)
296 if (nativePaintingEnabled())
297 return new QXcbNativeBackingStore(window);
298#endif
299
300 return new QXcbBackingStore(window);
301}
302
303QPlatformOffscreenSurface *QXcbIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
304{
305 QXcbScreen *screen = static_cast<QXcbScreen *>(surface->screen()->handle());
306 QXcbGlIntegration *glIntegration = screen->connection()->glIntegration();
307 if (!glIntegration) {
308 qWarning("QXcbIntegration: Cannot create platform offscreen surface, neither GLX nor EGL are enabled");
309 return nullptr;
310 }
311 return glIntegration->createPlatformOffscreenSurface(surface);
312}
313
314bool QXcbIntegration::hasCapability(QPlatformIntegration::Capability cap) const
315{
316 switch (cap) {
317 case OpenGL:
318 case ThreadedOpenGL:
319 {
320 const auto *connection = qAsConst(m_connections).first();
321 if (const auto *integration = connection->glIntegration())
322 return cap != ThreadedOpenGL || integration->supportsThreadedOpenGL();
323 return false;
324 }
325
326 case ThreadedPixmaps:
327 case WindowMasks:
328 case MultipleWindows:
329 case ForeignWindows:
330 case SyncState:
331 case RasterGLSurface:
332 return true;
333
334 case SwitchableWidgetComposition:
335 {
336 return m_connections.at(0)->glIntegration()
337 && m_connections.at(0)->glIntegration()->supportsSwitchableWidgetComposition();
338 }
339
340 default: return QPlatformIntegration::hasCapability(cap);
341 }
342}
343
344QAbstractEventDispatcher *QXcbIntegration::createEventDispatcher() const
345{
346 return QXcbEventDispatcher::createEventDispatcher(defaultConnection());
347}
348
349void QXcbIntegration::initialize()
350{
351 const QLatin1String defaultInputContext("compose");
352 // Perform everything that may potentially need the event dispatcher (timers, socket
353 // notifiers) here instead of the constructor.
354 QString icStr = QPlatformInputContextFactory::requested();
355 if (icStr.isNull())
356 icStr = defaultInputContext;
357 m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
358 if (!m_inputContext && icStr != defaultInputContext && icStr != QLatin1String("none"))
359 m_inputContext.reset(QPlatformInputContextFactory::create(defaultInputContext));
360
361 defaultConnection()->keyboard()->initialize();
362}
363
364void QXcbIntegration::moveToScreen(QWindow *window, int screen)
365{
366 Q_UNUSED(window);
367 Q_UNUSED(screen);
368}
369
370QPlatformFontDatabase *QXcbIntegration::fontDatabase() const
371{
372 return m_fontDatabase.data();
373}
374
375QPlatformNativeInterface * QXcbIntegration::nativeInterface() const
376{
377 return m_nativeInterface.data();
378}
379
380#ifndef QT_NO_CLIPBOARD
381QPlatformClipboard *QXcbIntegration::clipboard() const
382{
383 return m_connections.at(0)->clipboard();
384}
385#endif
386
387#if QT_CONFIG(draganddrop)
388#include <private/qsimpledrag_p.h>
389QPlatformDrag *QXcbIntegration::drag() const
390{
391 static const bool useSimpleDrag = qEnvironmentVariableIsSet("QT_XCB_USE_SIMPLE_DRAG");
392 if (Q_UNLIKELY(useSimpleDrag)) { // This is useful for testing purposes
393 static QSimpleDrag *simpleDrag = nullptr;
394 if (!simpleDrag)
395 simpleDrag = new QSimpleDrag();
396 return simpleDrag;
397 }
398
399 return m_connections.at(0)->drag();
400}
401#endif
402
403QPlatformInputContext *QXcbIntegration::inputContext() const
404{
405 return m_inputContext.data();
406}
407
408#ifndef QT_NO_ACCESSIBILITY
409QPlatformAccessibility *QXcbIntegration::accessibility() const
410{
411#if !defined(QT_NO_ACCESSIBILITY_ATSPI_BRIDGE)
412 if (!m_accessibility) {
413 Q_ASSERT_X(QCoreApplication::eventDispatcher(), "QXcbIntegration",
414 "Initializing accessibility without event-dispatcher!");
415 m_accessibility.reset(new QSpiAccessibleBridge());
416 }
417#endif
418
419 return m_accessibility.data();
420}
421#endif
422
423QPlatformServices *QXcbIntegration::services() const
424{
425 return m_services.data();
426}
427
428Qt::KeyboardModifiers QXcbIntegration::queryKeyboardModifiers() const
429{
430 return m_connections.at(0)->queryKeyboardModifiers();
431}
432
433QList<int> QXcbIntegration::possibleKeys(const QKeyEvent *e) const
434{
435 return m_connections.at(0)->keyboard()->possibleKeys(e);
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
448QVariant QXcbIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
449{
450 switch (hint) {
451 case QPlatformIntegration::CursorFlashTime:
452 case QPlatformIntegration::KeyboardInputInterval:
453 case QPlatformIntegration::MouseDoubleClickInterval:
454 case QPlatformIntegration::StartDragTime:
455 case QPlatformIntegration::KeyboardAutoRepeatRate:
456 case QPlatformIntegration::PasswordMaskDelay:
457 case QPlatformIntegration::StartDragVelocity:
458 case QPlatformIntegration::UseRtlExtensions:
459 case QPlatformIntegration::PasswordMaskCharacter:
460 // TODO using various xcb, gnome or KDE settings
461 break; // Not implemented, use defaults
462 case QPlatformIntegration::StartDragDistance: {
463 // The default (in QPlatformTheme::defaultThemeHint) is 10 pixels, but
464 // on a high-resolution screen it makes sense to increase it.
465 qreal dpi = 100.0;
466 if (const QXcbScreen *screen = defaultConnection()->primaryScreen()) {
467 if (screen->logicalDpi().first > dpi)
468 dpi = screen->logicalDpi().first;
469 if (screen->logicalDpi().second > dpi)
470 dpi = screen->logicalDpi().second;
471 }
472 return 10.0 * dpi / 100.0;
473 }
474 case QPlatformIntegration::ShowIsFullScreen:
475 // X11 always has support for windows, but the
476 // window manager could prevent it (e.g. matchbox)
477 return false;
478 case QPlatformIntegration::ReplayMousePressOutsidePopup:
479 return false;
480 default:
481 break;
482 }
483 return QPlatformIntegration::styleHint(hint);
484}
485
486static QString argv0BaseName()
487{
488 QString result;
489 const QStringList arguments = QCoreApplication::arguments();
490 if (!arguments.isEmpty() && !arguments.front().isEmpty()) {
491 result = arguments.front();
492 const int lastSlashPos = result.lastIndexOf(QLatin1Char('/'));
493 if (lastSlashPos != -1)
494 result.remove(0, lastSlashPos + 1);
495 }
496 return result;
497}
498
499static const char resourceNameVar[] = "RESOURCE_NAME";
500
501QByteArray QXcbIntegration::wmClass() const
502{
503 if (m_wmClass.isEmpty()) {
504 // Instance name according to ICCCM 4.1.2.5
505 QString name;
506 if (m_instanceName)
507 name = QString::fromLocal8Bit(m_instanceName);
508 if (name.isEmpty() && qEnvironmentVariableIsSet(resourceNameVar))
509 name = QString::fromLocal8Bit(qgetenv(resourceNameVar));
510 if (name.isEmpty())
511 name = argv0BaseName();
512
513 // Note: QCoreApplication::applicationName() cannot be called from the QGuiApplication constructor,
514 // hence this delayed initialization.
515 QString className = QCoreApplication::applicationName();
516 if (className.isEmpty()) {
517 className = argv0BaseName();
518 if (!className.isEmpty() && className.at(0).isLower())
519 className[0] = className.at(0).toUpper();
520 }
521
522 if (!name.isEmpty() && !className.isEmpty())
523 m_wmClass = std::move(name).toLocal8Bit() + '\0' + std::move(className).toLocal8Bit() + '\0';
524 }
525 return m_wmClass;
526}
527
528#if QT_CONFIG(xcb_sm)
529QPlatformSessionManager *QXcbIntegration::createPlatformSessionManager(const QString &id, const QString &key) const
530{
531 return new QXcbSessionManager(id, key);
532}
533#endif
534
535void QXcbIntegration::sync()
536{
537 for (int i = 0; i < m_connections.size(); i++) {
538 m_connections.at(i)->sync();
539 }
540}
541
542// For QApplication::beep()
543void QXcbIntegration::beep() const
544{
545 QScreen *priScreen = QGuiApplication::primaryScreen();
546 if (!priScreen)
547 return;
548 QPlatformScreen *screen = priScreen->handle();
549 if (!screen)
550 return;
551 xcb_connection_t *connection = static_cast<QXcbScreen *>(screen)->xcb_connection();
552 xcb_bell(connection, 0);
553 xcb_flush(connection);
554}
555
556bool QXcbIntegration::nativePaintingEnabled() const
557{
558#if QT_CONFIG(xcb_native_painting)
559 static bool enabled = qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING");
560 return enabled;
561#else
562 return false;
563#endif
564}
565
566#if QT_CONFIG(vulkan)
567QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
568{
569 return new QXcbVulkanInstance(instance);
570}
571#endif
572
573QT_END_NAMESPACE
574