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 &parameters, 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