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
101 QT_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'.
runningUnderDebugger()105 static 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
129 QXcbIntegration *QXcbIntegration::m_instance = nullptr;
130
QXcbIntegration(const QStringList & parameters,int & argc,char ** argv)131 QXcbIntegration::QXcbIntegration(const QStringList ¶meters, 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(Qt::AA_CompressHighFrequencyEvents, true);
139
140 qRegisterMetaType<QXcbWindow*>();
141 #if QT_CONFIG(xcb_xlib)
142 XInitThreads();
143 #endif
144 m_nativeInterface.reset(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("--"))
155 arg.remove(0, 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, 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("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("QT_XCB_NO_GRAB_SERVER");
191 if (canNotGrabEnv)
192 m_canGrab = false;
193
194 const int numParameters = parameters.size();
195 m_connections.reserve(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+1);
207 QString display = parameters.at(i) + QLatin1Char(':') + parameters.at(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(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
~QXcbIntegration()225 QXcbIntegration::~QXcbIntegration()
226 {
227 qDeleteAll(m_connections);
228 m_instance = nullptr;
229 }
230
createPlatformPixmap(QPlatformPixmap::PixelType type) const231 QPlatformPixmap *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
createPlatformWindow(QWindow * window) const241 QPlatformWindow *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
createForeignWindow(QWindow * window,WId nativeHandle) const269 QPlatformWindow *QXcbIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
270 {
271 return new QXcbForeignWindow(window, nativeHandle);
272 }
273
274 #ifndef QT_NO_OPENGL
createPlatformOpenGLContext(QOpenGLContext * context) const275 QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
276 {
277 QXcbGlIntegration *glIntegration = defaultConnection()->glIntegration();
278 if (!glIntegration) {
279 qWarning("QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled");
280 return nullptr;
281 }
282 return glIntegration->createPlatformOpenGLContext(context);
283 }
284 #endif
285
createPlatformBackingStore(QWindow * window) const286 QPlatformBackingStore *QXcbIntegration::createPlatformBackingStore(QWindow *window) const
287 {
288 const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);
289 if (isTrayIconWindow)
290 return new QXcbSystemTrayBackingStore(window);
291
292 #if QT_CONFIG(xcb_native_painting)
293 if (nativePaintingEnabled())
294 return new QXcbNativeBackingStore(window);
295 #endif
296
297 return new QXcbBackingStore(window);
298 }
299
createPlatformOffscreenSurface(QOffscreenSurface * surface) const300 QPlatformOffscreenSurface *QXcbIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
301 {
302 QXcbScreen *screen = static_cast<QXcbScreen *>(surface->screen()->handle());
303 QXcbGlIntegration *glIntegration = screen->connection()->glIntegration();
304 if (!glIntegration) {
305 qWarning("QXcbIntegration: Cannot create platform offscreen surface, neither GLX nor EGL are enabled");
306 return nullptr;
307 }
308 return glIntegration->createPlatformOffscreenSurface(surface);
309 }
310
hasCapability(QPlatformIntegration::Capability cap) const311 bool QXcbIntegration::hasCapability(QPlatformIntegration::Capability cap) const
312 {
313 switch (cap) {
314 case OpenGL:
315 case ThreadedOpenGL:
316 {
317 if (const auto *integration = defaultConnection()->glIntegration())
318 return cap != ThreadedOpenGL || integration->supportsThreadedOpenGL();
319 return false;
320 }
321
322 case ThreadedPixmaps:
323 case WindowMasks:
324 case MultipleWindows:
325 case ForeignWindows:
326 case SyncState:
327 case RasterGLSurface:
328 return true;
329
330 case SwitchableWidgetComposition:
331 {
332 return m_connections.at(0)->glIntegration()
333 && m_connections.at(0)->glIntegration()->supportsSwitchableWidgetComposition();
334 }
335
336 default: return QPlatformIntegration::hasCapability(cap);
337 }
338 }
339
createEventDispatcher() const340 QAbstractEventDispatcher *QXcbIntegration::createEventDispatcher() const
341 {
342 return QXcbEventDispatcher::createEventDispatcher(defaultConnection());
343 }
344
initialize()345 void QXcbIntegration::initialize()
346 {
347 const QLatin1String defaultInputContext("compose");
348 // Perform everything that may potentially need the event dispatcher (timers, socket
349 // notifiers) here instead of the constructor.
350 QString icStr = QPlatformInputContextFactory::requested();
351 if (icStr.isNull())
352 icStr = defaultInputContext;
353 m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
354 if (!m_inputContext && icStr != defaultInputContext && icStr != QLatin1String("none"))
355 m_inputContext.reset(QPlatformInputContextFactory::create(defaultInputContext));
356
357 defaultConnection()->keyboard()->initialize();
358 }
359
moveToScreen(QWindow * window,int screen)360 void QXcbIntegration::moveToScreen(QWindow *window, int screen)
361 {
362 Q_UNUSED(window);
363 Q_UNUSED(screen);
364 }
365
fontDatabase() const366 QPlatformFontDatabase *QXcbIntegration::fontDatabase() const
367 {
368 return m_fontDatabase.data();
369 }
370
nativeInterface() const371 QPlatformNativeInterface * QXcbIntegration::nativeInterface() const
372 {
373 return m_nativeInterface.data();
374 }
375
376 #ifndef QT_NO_CLIPBOARD
clipboard() const377 QPlatformClipboard *QXcbIntegration::clipboard() const
378 {
379 return m_connections.at(0)->clipboard();
380 }
381 #endif
382
383 #if QT_CONFIG(draganddrop)
384 #include <private/qsimpledrag_p.h>
drag() const385 QPlatformDrag *QXcbIntegration::drag() const
386 {
387 static const bool useSimpleDrag = qEnvironmentVariableIsSet("QT_XCB_USE_SIMPLE_DRAG");
388 if (Q_UNLIKELY(useSimpleDrag)) { // This is useful for testing purposes
389 static QSimpleDrag *simpleDrag = nullptr;
390 if (!simpleDrag)
391 simpleDrag = new QSimpleDrag();
392 return simpleDrag;
393 }
394
395 return m_connections.at(0)->drag();
396 }
397 #endif
398
inputContext() const399 QPlatformInputContext *QXcbIntegration::inputContext() const
400 {
401 return m_inputContext.data();
402 }
403
404 #ifndef QT_NO_ACCESSIBILITY
accessibility() const405 QPlatformAccessibility *QXcbIntegration::accessibility() const
406 {
407 #if !defined(QT_NO_ACCESSIBILITY_ATSPI_BRIDGE)
408 if (!m_accessibility) {
409 Q_ASSERT_X(QCoreApplication::eventDispatcher(), "QXcbIntegration",
410 "Initializing accessibility without event-dispatcher!");
411 m_accessibility.reset(new QSpiAccessibleBridge());
412 }
413 #endif
414
415 return m_accessibility.data();
416 }
417 #endif
418
services() const419 QPlatformServices *QXcbIntegration::services() const
420 {
421 return m_services.data();
422 }
423
queryKeyboardModifiers() const424 Qt::KeyboardModifiers QXcbIntegration::queryKeyboardModifiers() const
425 {
426 return m_connections.at(0)->queryKeyboardModifiers();
427 }
428
possibleKeys(const QKeyEvent * e) const429 QList<int> QXcbIntegration::possibleKeys(const QKeyEvent *e) const
430 {
431 return m_connections.at(0)->keyboard()->possibleKeys(e);
432 }
433
themeNames() const434 QStringList QXcbIntegration::themeNames() const
435 {
436 return QGenericUnixTheme::themeNames();
437 }
438
createPlatformTheme(const QString & name) const439 QPlatformTheme *QXcbIntegration::createPlatformTheme(const QString &name) const
440 {
441 return QGenericUnixTheme::createUnixTheme(name);
442 }
443
styleHint(QPlatformIntegration::StyleHint hint) const444 QVariant QXcbIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
445 {
446 switch (hint) {
447 case QPlatformIntegration::CursorFlashTime:
448 case QPlatformIntegration::KeyboardInputInterval:
449 case QPlatformIntegration::MouseDoubleClickInterval:
450 case QPlatformIntegration::StartDragTime:
451 case QPlatformIntegration::KeyboardAutoRepeatRate:
452 case QPlatformIntegration::PasswordMaskDelay:
453 case QPlatformIntegration::StartDragVelocity:
454 case QPlatformIntegration::UseRtlExtensions:
455 case QPlatformIntegration::PasswordMaskCharacter:
456 // TODO using various xcb, gnome or KDE settings
457 break; // Not implemented, use defaults
458 case QPlatformIntegration::StartDragDistance: {
459 // The default (in QPlatformTheme::defaultThemeHint) is 10 pixels, but
460 // on a high-resolution screen it makes sense to increase it.
461 qreal dpi = 100.0;
462 if (const QXcbScreen *screen = defaultConnection()->primaryScreen()) {
463 if (screen->logicalDpi().first > dpi)
464 dpi = screen->logicalDpi().first;
465 if (screen->logicalDpi().second > dpi)
466 dpi = screen->logicalDpi().second;
467 }
468 return 10.0 * dpi / 100.0;
469 }
470 case QPlatformIntegration::ShowIsFullScreen:
471 // X11 always has support for windows, but the
472 // window manager could prevent it (e.g. matchbox)
473 return false;
474 case QPlatformIntegration::ReplayMousePressOutsidePopup:
475 return false;
476 default:
477 break;
478 }
479 return QPlatformIntegration::styleHint(hint);
480 }
481
argv0BaseName()482 static QString argv0BaseName()
483 {
484 QString result;
485 const QStringList arguments = QCoreApplication::arguments();
486 if (!arguments.isEmpty() && !arguments.front().isEmpty()) {
487 result = arguments.front();
488 const int lastSlashPos = result.lastIndexOf(QLatin1Char('/'));
489 if (lastSlashPos != -1)
490 result.remove(0, lastSlashPos + 1);
491 }
492 return result;
493 }
494
495 static const char resourceNameVar[] = "RESOURCE_NAME";
496
wmClass() const497 QByteArray QXcbIntegration::wmClass() const
498 {
499 if (m_wmClass.isEmpty()) {
500 // Instance name according to ICCCM 4.1.2.5
501 QString name;
502 if (m_instanceName)
503 name = QString::fromLocal8Bit(m_instanceName);
504 if (name.isEmpty() && qEnvironmentVariableIsSet(resourceNameVar))
505 name = QString::fromLocal8Bit(qgetenv(resourceNameVar));
506 if (name.isEmpty())
507 name = argv0BaseName();
508
509 // Note: QCoreApplication::applicationName() cannot be called from the QGuiApplication constructor,
510 // hence this delayed initialization.
511 QString className = QCoreApplication::applicationName();
512 if (className.isEmpty()) {
513 className = argv0BaseName();
514 if (!className.isEmpty() && className.at(0).isLower())
515 className[0] = className.at(0).toUpper();
516 }
517
518 if (!name.isEmpty() && !className.isEmpty())
519 m_wmClass = std::move(name).toLocal8Bit() + '\0' + std::move(className).toLocal8Bit() + '\0';
520 }
521 return m_wmClass;
522 }
523
524 #if QT_CONFIG(xcb_sm)
createPlatformSessionManager(const QString & id,const QString & key) const525 QPlatformSessionManager *QXcbIntegration::createPlatformSessionManager(const QString &id, const QString &key) const
526 {
527 return new QXcbSessionManager(id, key);
528 }
529 #endif
530
sync()531 void QXcbIntegration::sync()
532 {
533 for (int i = 0; i < m_connections.size(); i++) {
534 m_connections.at(i)->sync();
535 }
536 }
537
538 // For QApplication::beep()
beep() const539 void QXcbIntegration::beep() const
540 {
541 QScreen *priScreen = QGuiApplication::primaryScreen();
542 if (!priScreen)
543 return;
544 QPlatformScreen *screen = priScreen->handle();
545 if (!screen)
546 return;
547 xcb_connection_t *connection = static_cast<QXcbScreen *>(screen)->xcb_connection();
548 xcb_bell(connection, 0);
549 xcb_flush(connection);
550 }
551
nativePaintingEnabled() const552 bool QXcbIntegration::nativePaintingEnabled() const
553 {
554 #if QT_CONFIG(xcb_native_painting)
555 static bool enabled = qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING");
556 return enabled;
557 #else
558 return false;
559 #endif
560 }
561
562 #if QT_CONFIG(vulkan)
createPlatformVulkanInstance(QVulkanInstance * instance) const563 QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
564 {
565 return new QXcbVulkanInstance(instance);
566 }
567 #endif
568
569 QT_END_NAMESPACE
570