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 <QtGui/private/qguiapplication_p.h>
41#include <QtCore/QDebug>
42#include <QtCore/QCoreApplication>
43
44#include "qxcbconnection.h"
45#include "qxcbkeyboard.h"
46#include "qxcbwindow.h"
47#include "qxcbclipboard.h"
48#if QT_CONFIG(draganddrop)
49#include "qxcbdrag.h"
50#endif
51#include "qxcbwmsupport.h"
52#include "qxcbnativeinterface.h"
53#include "qxcbintegration.h"
54#include "qxcbsystemtraytracker.h"
55#include "qxcbglintegrationfactory.h"
56#include "qxcbglintegration.h"
57#include "qxcbcursor.h"
58#include "qxcbbackingstore.h"
59#include "qxcbeventqueue.h"
60
61#include <QAbstractEventDispatcher>
62#include <QByteArray>
63#include <QScopedPointer>
64
65#include <stdio.h>
66#include <errno.h>
67
68#include <xcb/xfixes.h>
69#if QT_CONFIG(xkb)
70#define explicit dont_use_cxx_explicit
71#include <xcb/xkb.h>
72#undef explicit
73#endif
74#if QT_CONFIG(xcb_xinput)
75#include <xcb/xinput.h>
76#endif
77
78QT_BEGIN_NAMESPACE
79
80Q_LOGGING_CATEGORY(lcQpaXInput, "qt.qpa.input")
81Q_LOGGING_CATEGORY(lcQpaXInputDevices, "qt.qpa.input.devices")
82Q_LOGGING_CATEGORY(lcQpaXInputEvents, "qt.qpa.input.events")
83Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen")
84Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events")
85Q_LOGGING_CATEGORY(lcQpaEventReader, "qt.qpa.events.reader")
86Q_LOGGING_CATEGORY(lcQpaPeeker, "qt.qpa.peeker")
87Q_LOGGING_CATEGORY(lcQpaKeyboard, "qt.qpa.xkeyboard")
88Q_LOGGING_CATEGORY(lcQpaClipboard, "qt.qpa.clipboard")
89Q_LOGGING_CATEGORY(lcQpaXDnd, "qt.qpa.xdnd")
90
91// this event type was added in libxcb 1.10,
92// but we support also older version
93#ifndef XCB_GE_GENERIC
94#define XCB_GE_GENERIC 35
95#endif
96
97QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGrabServer, xcb_visualid_t defaultVisualId, const char *displayName)
98 : QXcbBasicConnection(displayName)
99 , m_canGrabServer(canGrabServer)
100 , m_defaultVisualId(defaultVisualId)
101 , m_nativeInterface(nativeInterface)
102{
103 if (!isConnected())
104 return;
105
106 m_eventQueue = new QXcbEventQueue(this);
107
108 m_xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP").toLower();
109
110 if (hasXRandr())
111 xrandrSelectEvents();
112
113 initializeScreens();
114
115#if QT_CONFIG(xcb_xinput)
116 if (hasXInput2()) {
117 xi2SetupDevices();
118 xi2SelectStateEvents();
119 }
120#endif
121
122 m_wmSupport.reset(new QXcbWMSupport(this));
123 m_keyboard = new QXcbKeyboard(this);
124#ifndef QT_NO_CLIPBOARD
125 m_clipboard = new QXcbClipboard(this);
126#endif
127#if QT_CONFIG(draganddrop)
128 m_drag = new QXcbDrag(this);
129#endif
130
131 m_startupId = qgetenv("DESKTOP_STARTUP_ID");
132 if (!m_startupId.isNull())
133 qunsetenv("DESKTOP_STARTUP_ID");
134
135 const int focusInDelay = 100;
136 m_focusInTimer.setSingleShot(true);
137 m_focusInTimer.setInterval(focusInDelay);
138 m_focusInTimer.callOnTimeout([]() {
139 // No FocusIn events for us, proceed with FocusOut normally.
140 QWindowSystemInterface::handleWindowActivated(nullptr, Qt::ActiveWindowFocusReason);
141 });
142
143 sync();
144}
145
146QXcbConnection::~QXcbConnection()
147{
148#ifndef QT_NO_CLIPBOARD
149 delete m_clipboard;
150#endif
151#if QT_CONFIG(draganddrop)
152 delete m_drag;
153#endif
154 if (m_eventQueue)
155 delete m_eventQueue;
156
157 // Delete screens in reverse order to avoid crash in case of multiple screens
158 while (!m_screens.isEmpty())
159 QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast());
160
161 while (!m_virtualDesktops.isEmpty())
162 delete m_virtualDesktops.takeLast();
163
164 delete m_glIntegration;
165
166 delete m_keyboard;
167}
168
169QXcbScreen *QXcbConnection::primaryScreen() const
170{
171 if (!m_screens.isEmpty()) {
172 Q_ASSERT(m_screens.first()->screenNumber() == primaryScreenNumber());
173 return m_screens.first();
174 }
175
176 return nullptr;
177}
178
179void QXcbConnection::addWindowEventListener(xcb_window_t id, QXcbWindowEventListener *eventListener)
180{
181 m_mapper.insert(id, eventListener);
182}
183
184void QXcbConnection::removeWindowEventListener(xcb_window_t id)
185{
186 m_mapper.remove(id);
187}
188
189QXcbWindowEventListener *QXcbConnection::windowEventListenerFromId(xcb_window_t id)
190{
191 return m_mapper.value(id, 0);
192}
193
194QXcbWindow *QXcbConnection::platformWindowFromId(xcb_window_t id)
195{
196 QXcbWindowEventListener *listener = m_mapper.value(id, 0);
197 if (listener)
198 return listener->toWindow();
199 return 0;
200}
201
202#define HANDLE_PLATFORM_WINDOW_EVENT(event_t, windowMember, handler) \
203{ \
204 auto e = reinterpret_cast<event_t *>(event); \
205 if (QXcbWindowEventListener *eventListener = windowEventListenerFromId(e->windowMember)) { \
206 if (eventListener->handleNativeEvent(event)) \
207 return; \
208 eventListener->handler(e); \
209 } \
210} \
211break;
212
213#define HANDLE_KEYBOARD_EVENT(event_t, handler) \
214{ \
215 auto e = reinterpret_cast<event_t *>(event); \
216 if (QXcbWindowEventListener *eventListener = windowEventListenerFromId(e->event)) { \
217 if (eventListener->handleNativeEvent(event)) \
218 return; \
219 m_keyboard->handler(e); \
220 } \
221} \
222break;
223
224void QXcbConnection::printXcbEvent(const QLoggingCategory &log, const char *message,
225 xcb_generic_event_t *event) const
226{
227 quint8 response_type = event->response_type & ~0x80;
228 quint16 sequence = event->sequence;
229
230#define PRINT_AND_RETURN(name) { \
231 qCDebug(log, "%s | %s(%d) | sequence: %d", message, name, response_type, sequence); \
232 return; \
233}
234#define CASE_PRINT_AND_RETURN(name) case name : PRINT_AND_RETURN(#name);
235
236 switch (response_type) {
237 CASE_PRINT_AND_RETURN( XCB_KEY_PRESS );
238 CASE_PRINT_AND_RETURN( XCB_KEY_RELEASE );
239 CASE_PRINT_AND_RETURN( XCB_BUTTON_PRESS );
240 CASE_PRINT_AND_RETURN( XCB_BUTTON_RELEASE );
241 CASE_PRINT_AND_RETURN( XCB_MOTION_NOTIFY );
242 CASE_PRINT_AND_RETURN( XCB_ENTER_NOTIFY );
243 CASE_PRINT_AND_RETURN( XCB_LEAVE_NOTIFY );
244 CASE_PRINT_AND_RETURN( XCB_FOCUS_IN );
245 CASE_PRINT_AND_RETURN( XCB_FOCUS_OUT );
246 CASE_PRINT_AND_RETURN( XCB_KEYMAP_NOTIFY );
247 CASE_PRINT_AND_RETURN( XCB_EXPOSE );
248 CASE_PRINT_AND_RETURN( XCB_GRAPHICS_EXPOSURE );
249 CASE_PRINT_AND_RETURN( XCB_NO_EXPOSURE );
250 CASE_PRINT_AND_RETURN( XCB_VISIBILITY_NOTIFY );
251 CASE_PRINT_AND_RETURN( XCB_CREATE_NOTIFY );
252 CASE_PRINT_AND_RETURN( XCB_DESTROY_NOTIFY );
253 CASE_PRINT_AND_RETURN( XCB_UNMAP_NOTIFY );
254 CASE_PRINT_AND_RETURN( XCB_MAP_NOTIFY );
255 CASE_PRINT_AND_RETURN( XCB_MAP_REQUEST );
256 CASE_PRINT_AND_RETURN( XCB_REPARENT_NOTIFY );
257 CASE_PRINT_AND_RETURN( XCB_CONFIGURE_NOTIFY );
258 CASE_PRINT_AND_RETURN( XCB_CONFIGURE_REQUEST );
259 CASE_PRINT_AND_RETURN( XCB_GRAVITY_NOTIFY );
260 CASE_PRINT_AND_RETURN( XCB_RESIZE_REQUEST );
261 CASE_PRINT_AND_RETURN( XCB_CIRCULATE_NOTIFY );
262 CASE_PRINT_AND_RETURN( XCB_CIRCULATE_REQUEST );
263 CASE_PRINT_AND_RETURN( XCB_PROPERTY_NOTIFY );
264 CASE_PRINT_AND_RETURN( XCB_SELECTION_CLEAR );
265 CASE_PRINT_AND_RETURN( XCB_SELECTION_REQUEST );
266 CASE_PRINT_AND_RETURN( XCB_SELECTION_NOTIFY );
267 CASE_PRINT_AND_RETURN( XCB_COLORMAP_NOTIFY );
268 CASE_PRINT_AND_RETURN( XCB_CLIENT_MESSAGE );
269 CASE_PRINT_AND_RETURN( XCB_MAPPING_NOTIFY );
270 CASE_PRINT_AND_RETURN( XCB_GE_GENERIC );
271 }
272 // XFixes
273 if (isXFixesType(response_type, XCB_XFIXES_SELECTION_NOTIFY))
274 PRINT_AND_RETURN("XCB_XFIXES_SELECTION_NOTIFY");
275
276 // XRandR
277 if (isXRandrType(response_type, XCB_RANDR_NOTIFY))
278 PRINT_AND_RETURN("XCB_RANDR_NOTIFY");
279 if (isXRandrType(response_type, XCB_RANDR_SCREEN_CHANGE_NOTIFY))
280 PRINT_AND_RETURN("XCB_RANDR_SCREEN_CHANGE_NOTIFY");
281
282 // XKB
283 if (isXkbType(response_type))
284 PRINT_AND_RETURN("XCB_XKB_* event");
285
286 // UNKNOWN
287 qCDebug(log, "%s | unknown(%d) | sequence: %d", message, response_type, sequence);
288
289#undef PRINT_AND_RETURN
290#undef CASE_PRINT_AND_RETURN
291}
292
293const char *xcb_errors[] =
294{
295 "Success",
296 "BadRequest",
297 "BadValue",
298 "BadWindow",
299 "BadPixmap",
300 "BadAtom",
301 "BadCursor",
302 "BadFont",
303 "BadMatch",
304 "BadDrawable",
305 "BadAccess",
306 "BadAlloc",
307 "BadColor",
308 "BadGC",
309 "BadIDChoice",
310 "BadName",
311 "BadLength",
312 "BadImplementation",
313 "Unknown"
314};
315
316const char *xcb_protocol_request_codes[] =
317{
318 "Null",
319 "CreateWindow",
320 "ChangeWindowAttributes",
321 "GetWindowAttributes",
322 "DestroyWindow",
323 "DestroySubwindows",
324 "ChangeSaveSet",
325 "ReparentWindow",
326 "MapWindow",
327 "MapSubwindows",
328 "UnmapWindow",
329 "UnmapSubwindows",
330 "ConfigureWindow",
331 "CirculateWindow",
332 "GetGeometry",
333 "QueryTree",
334 "InternAtom",
335 "GetAtomName",
336 "ChangeProperty",
337 "DeleteProperty",
338 "GetProperty",
339 "ListProperties",
340 "SetSelectionOwner",
341 "GetSelectionOwner",
342 "ConvertSelection",
343 "SendEvent",
344 "GrabPointer",
345 "UngrabPointer",
346 "GrabButton",
347 "UngrabButton",
348 "ChangeActivePointerGrab",
349 "GrabKeyboard",
350 "UngrabKeyboard",
351 "GrabKey",
352 "UngrabKey",
353 "AllowEvents",
354 "GrabServer",
355 "UngrabServer",
356 "QueryPointer",
357 "GetMotionEvents",
358 "TranslateCoords",
359 "WarpPointer",
360 "SetInputFocus",
361 "GetInputFocus",
362 "QueryKeymap",
363 "OpenFont",
364 "CloseFont",
365 "QueryFont",
366 "QueryTextExtents",
367 "ListFonts",
368 "ListFontsWithInfo",
369 "SetFontPath",
370 "GetFontPath",
371 "CreatePixmap",
372 "FreePixmap",
373 "CreateGC",
374 "ChangeGC",
375 "CopyGC",
376 "SetDashes",
377 "SetClipRectangles",
378 "FreeGC",
379 "ClearArea",
380 "CopyArea",
381 "CopyPlane",
382 "PolyPoint",
383 "PolyLine",
384 "PolySegment",
385 "PolyRectangle",
386 "PolyArc",
387 "FillPoly",
388 "PolyFillRectangle",
389 "PolyFillArc",
390 "PutImage",
391 "GetImage",
392 "PolyText8",
393 "PolyText16",
394 "ImageText8",
395 "ImageText16",
396 "CreateColormap",
397 "FreeColormap",
398 "CopyColormapAndFree",
399 "InstallColormap",
400 "UninstallColormap",
401 "ListInstalledColormaps",
402 "AllocColor",
403 "AllocNamedColor",
404 "AllocColorCells",
405 "AllocColorPlanes",
406 "FreeColors",
407 "StoreColors",
408 "StoreNamedColor",
409 "QueryColors",
410 "LookupColor",
411 "CreateCursor",
412 "CreateGlyphCursor",
413 "FreeCursor",
414 "RecolorCursor",
415 "QueryBestSize",
416 "QueryExtension",
417 "ListExtensions",
418 "ChangeKeyboardMapping",
419 "GetKeyboardMapping",
420 "ChangeKeyboardControl",
421 "GetKeyboardControl",
422 "Bell",
423 "ChangePointerControl",
424 "GetPointerControl",
425 "SetScreenSaver",
426 "GetScreenSaver",
427 "ChangeHosts",
428 "ListHosts",
429 "SetAccessControl",
430 "SetCloseDownMode",
431 "KillClient",
432 "RotateProperties",
433 "ForceScreenSaver",
434 "SetPointerMapping",
435 "GetPointerMapping",
436 "SetModifierMapping",
437 "GetModifierMapping",
438 "Unknown"
439};
440
441void QXcbConnection::handleXcbError(xcb_generic_error_t *error)
442{
443#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
444 qintptr result = 0;
445#else
446 long result = 0;
447#endif
448 QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance();
449 if (dispatcher && dispatcher->filterNativeEvent(m_nativeInterface->nativeEventType(), error, &result))
450 return;
451
452 printXcbError("QXcbConnection: XCB error", error);
453}
454
455void QXcbConnection::printXcbError(const char *message, xcb_generic_error_t *error)
456{
457 uint clamped_error_code = qMin<uint>(error->error_code, (sizeof(xcb_errors) / sizeof(xcb_errors[0])) - 1);
458 uint clamped_major_code = qMin<uint>(error->major_code, (sizeof(xcb_protocol_request_codes) / sizeof(xcb_protocol_request_codes[0])) - 1);
459
460 qCWarning(lcQpaXcb, "%s: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d",
461 message,
462 int(error->error_code), xcb_errors[clamped_error_code],
463 int(error->sequence), int(error->resource_id),
464 int(error->major_code), xcb_protocol_request_codes[clamped_major_code],
465 int(error->minor_code));
466}
467
468static Qt::MouseButtons translateMouseButtons(int s)
469{
470 Qt::MouseButtons ret = 0;
471 if (s & XCB_BUTTON_MASK_1)
472 ret |= Qt::LeftButton;
473 if (s & XCB_BUTTON_MASK_2)
474 ret |= Qt::MidButton;
475 if (s & XCB_BUTTON_MASK_3)
476 ret |= Qt::RightButton;
477 return ret;
478}
479
480void QXcbConnection::setButtonState(Qt::MouseButton button, bool down)
481{
482 m_buttonState.setFlag(button, down);
483 m_button = button;
484}
485
486Qt::MouseButton QXcbConnection::translateMouseButton(xcb_button_t s)
487{
488 switch (s) {
489 case 1: return Qt::LeftButton;
490 case 2: return Qt::MidButton;
491 case 3: return Qt::RightButton;
492 // Button values 4-7 were already handled as Wheel events, and won't occur here.
493 case 8: return Qt::BackButton; // Also known as Qt::ExtraButton1
494 case 9: return Qt::ForwardButton; // Also known as Qt::ExtraButton2
495 case 10: return Qt::ExtraButton3;
496 case 11: return Qt::ExtraButton4;
497 case 12: return Qt::ExtraButton5;
498 case 13: return Qt::ExtraButton6;
499 case 14: return Qt::ExtraButton7;
500 case 15: return Qt::ExtraButton8;
501 case 16: return Qt::ExtraButton9;
502 case 17: return Qt::ExtraButton10;
503 case 18: return Qt::ExtraButton11;
504 case 19: return Qt::ExtraButton12;
505 case 20: return Qt::ExtraButton13;
506 case 21: return Qt::ExtraButton14;
507 case 22: return Qt::ExtraButton15;
508 case 23: return Qt::ExtraButton16;
509 case 24: return Qt::ExtraButton17;
510 case 25: return Qt::ExtraButton18;
511 case 26: return Qt::ExtraButton19;
512 case 27: return Qt::ExtraButton20;
513 case 28: return Qt::ExtraButton21;
514 case 29: return Qt::ExtraButton22;
515 case 30: return Qt::ExtraButton23;
516 case 31: return Qt::ExtraButton24;
517 default: return Qt::NoButton;
518 }
519}
520
521#if QT_CONFIG(xkb)
522namespace {
523 typedef union {
524 /* All XKB events share these fields. */
525 struct {
526 uint8_t response_type;
527 uint8_t xkbType;
528 uint16_t sequence;
529 xcb_timestamp_t time;
530 uint8_t deviceID;
531 } any;
532 xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify;
533 xcb_xkb_map_notify_event_t map_notify;
534 xcb_xkb_state_notify_event_t state_notify;
535 } _xkb_event;
536}
537#endif
538
539void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
540{
541 if (Q_UNLIKELY(lcQpaEvents().isDebugEnabled()))
542 printXcbEvent(lcQpaEvents(), "Event", event);
543
544#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
545 qintptr result = 0; // Used only by MS Windows
546#else
547 long result = 0; // Used only by MS Windows
548#endif
549 if (QAbstractEventDispatcher *dispatcher = QAbstractEventDispatcher::instance()) {
550 if (dispatcher->filterNativeEvent(m_nativeInterface->nativeEventType(), event, &result))
551 return;
552 }
553
554 uint response_type = event->response_type & ~0x80;
555
556 bool handled = true;
557 switch (response_type) {
558 case XCB_EXPOSE:
559 HANDLE_PLATFORM_WINDOW_EVENT(xcb_expose_event_t, window, handleExposeEvent);
560 case XCB_BUTTON_PRESS: {
561 auto ev = reinterpret_cast<xcb_button_press_event_t *>(event);
562 m_keyboard->updateXKBStateFromCore(ev->state);
563 // the event explicitly contains the state of the three first buttons,
564 // the rest we need to manage ourselves
565 m_buttonState = (m_buttonState & ~0x7) | translateMouseButtons(ev->state);
566 setButtonState(translateMouseButton(ev->detail), true);
567 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
568 qCDebug(lcQpaXInputEvents, "legacy mouse press, button %d state %X",
569 ev->detail, static_cast<unsigned int>(m_buttonState));
570 HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_press_event_t, event, handleButtonPressEvent);
571 }
572 case XCB_BUTTON_RELEASE: {
573 auto ev = reinterpret_cast<xcb_button_release_event_t *>(event);
574 m_keyboard->updateXKBStateFromCore(ev->state);
575 m_buttonState = (m_buttonState & ~0x7) | translateMouseButtons(ev->state);
576 setButtonState(translateMouseButton(ev->detail), false);
577 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
578 qCDebug(lcQpaXInputEvents, "legacy mouse release, button %d state %X",
579 ev->detail, static_cast<unsigned int>(m_buttonState));
580 HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_release_event_t, event, handleButtonReleaseEvent);
581 }
582 case XCB_MOTION_NOTIFY: {
583 auto ev = reinterpret_cast<xcb_motion_notify_event_t *>(event);
584 m_keyboard->updateXKBStateFromCore(ev->state);
585 m_buttonState = (m_buttonState & ~0x7) | translateMouseButtons(ev->state);
586 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
587 qCDebug(lcQpaXInputEvents, "legacy mouse move %d,%d button %d state %X",
588 ev->event_x, ev->event_y, ev->detail, static_cast<unsigned int>(m_buttonState));
589 HANDLE_PLATFORM_WINDOW_EVENT(xcb_motion_notify_event_t, event, handleMotionNotifyEvent);
590 }
591 case XCB_CONFIGURE_NOTIFY:
592 HANDLE_PLATFORM_WINDOW_EVENT(xcb_configure_notify_event_t, event, handleConfigureNotifyEvent);
593 case XCB_MAP_NOTIFY:
594 HANDLE_PLATFORM_WINDOW_EVENT(xcb_map_notify_event_t, event, handleMapNotifyEvent);
595 case XCB_UNMAP_NOTIFY:
596 HANDLE_PLATFORM_WINDOW_EVENT(xcb_unmap_notify_event_t, event, handleUnmapNotifyEvent);
597 case XCB_DESTROY_NOTIFY:
598 HANDLE_PLATFORM_WINDOW_EVENT(xcb_destroy_notify_event_t, event, handleDestroyNotifyEvent);
599 case XCB_CLIENT_MESSAGE: {
600 auto clientMessage = reinterpret_cast<xcb_client_message_event_t *>(event);
601 if (clientMessage->format != 32)
602 return;
603#if QT_CONFIG(draganddrop)
604 if (clientMessage->type == atom(QXcbAtom::XdndStatus))
605 drag()->handleStatus(clientMessage);
606 else if (clientMessage->type == atom(QXcbAtom::XdndFinished))
607 drag()->handleFinished(clientMessage);
608#endif
609 if (m_systemTrayTracker && clientMessage->type == atom(QXcbAtom::MANAGER))
610 m_systemTrayTracker->notifyManagerClientMessageEvent(clientMessage);
611 HANDLE_PLATFORM_WINDOW_EVENT(xcb_client_message_event_t, window, handleClientMessageEvent);
612 }
613 case XCB_ENTER_NOTIFY:
614#if QT_CONFIG(xcb_xinput)
615 if (hasXInput2() && !xi2MouseEventsDisabled())
616 break;
617#endif
618 HANDLE_PLATFORM_WINDOW_EVENT(xcb_enter_notify_event_t, event, handleEnterNotifyEvent);
619 case XCB_LEAVE_NOTIFY:
620#if QT_CONFIG(xcb_xinput)
621 if (hasXInput2() && !xi2MouseEventsDisabled())
622 break;
623#endif
624 m_keyboard->updateXKBStateFromCore(reinterpret_cast<xcb_leave_notify_event_t *>(event)->state);
625 HANDLE_PLATFORM_WINDOW_EVENT(xcb_leave_notify_event_t, event, handleLeaveNotifyEvent);
626 case XCB_FOCUS_IN:
627 HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_in_event_t, event, handleFocusInEvent);
628 case XCB_FOCUS_OUT:
629 HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_out_event_t, event, handleFocusOutEvent);
630 case XCB_KEY_PRESS:
631 {
632 auto keyPress = reinterpret_cast<xcb_key_press_event_t *>(event);
633 m_keyboard->updateXKBStateFromCore(keyPress->state);
634 setTime(keyPress->time);
635 HANDLE_KEYBOARD_EVENT(xcb_key_press_event_t, handleKeyPressEvent);
636 }
637 case XCB_KEY_RELEASE:
638 m_keyboard->updateXKBStateFromCore(reinterpret_cast<xcb_key_release_event_t *>(event)->state);
639 HANDLE_KEYBOARD_EVENT(xcb_key_release_event_t, handleKeyReleaseEvent);
640 case XCB_MAPPING_NOTIFY:
641 m_keyboard->updateKeymap(reinterpret_cast<xcb_mapping_notify_event_t *>(event));
642 break;
643 case XCB_SELECTION_REQUEST:
644 {
645#if QT_CONFIG(draganddrop) || QT_CONFIG(clipboard)
646 auto selectionRequest = reinterpret_cast<xcb_selection_request_event_t *>(event);
647#endif
648#if QT_CONFIG(draganddrop)
649 if (selectionRequest->selection == atom(QXcbAtom::XdndSelection))
650 m_drag->handleSelectionRequest(selectionRequest);
651 else
652#endif
653 {
654#ifndef QT_NO_CLIPBOARD
655 m_clipboard->handleSelectionRequest(selectionRequest);
656#endif
657 }
658 break;
659 }
660 case XCB_SELECTION_CLEAR:
661 setTime((reinterpret_cast<xcb_selection_clear_event_t *>(event))->time);
662#ifndef QT_NO_CLIPBOARD
663 m_clipboard->handleSelectionClearRequest(reinterpret_cast<xcb_selection_clear_event_t *>(event));
664#endif
665 break;
666 case XCB_SELECTION_NOTIFY:
667 setTime((reinterpret_cast<xcb_selection_notify_event_t *>(event))->time);
668 break;
669 case XCB_PROPERTY_NOTIFY:
670 {
671#ifndef QT_NO_CLIPBOARD
672 if (m_clipboard->handlePropertyNotify(event))
673 break;
674#endif
675 auto propertyNotify = reinterpret_cast<xcb_property_notify_event_t *>(event);
676 if (propertyNotify->atom == atom(QXcbAtom::_NET_WORKAREA)) {
677 QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(propertyNotify->window);
678 if (virtualDesktop)
679 virtualDesktop->updateWorkArea();
680 } else {
681 HANDLE_PLATFORM_WINDOW_EVENT(xcb_property_notify_event_t, window, handlePropertyNotifyEvent);
682 }
683 break;
684 }
685#if QT_CONFIG(xcb_xinput)
686 case XCB_GE_GENERIC:
687 // Here the windowEventListener is invoked from xi2HandleEvent()
688 if (hasXInput2() && isXIEvent(event))
689 xi2HandleEvent(reinterpret_cast<xcb_ge_event_t *>(event));
690 break;
691#endif
692 default:
693 handled = false; // event type not recognized
694 break;
695 }
696
697 if (handled)
698 return;
699
700 handled = true;
701 if (isXFixesType(response_type, XCB_XFIXES_SELECTION_NOTIFY)) {
702 auto notify_event = reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(event);
703 setTime(notify_event->timestamp);
704#ifndef QT_NO_CLIPBOARD
705 m_clipboard->handleXFixesSelectionRequest(notify_event);
706#endif
707 for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops))
708 virtualDesktop->handleXFixesSelectionNotify(notify_event);
709 } else if (isXRandrType(response_type, XCB_RANDR_NOTIFY)) {
710 updateScreens(reinterpret_cast<xcb_randr_notify_event_t *>(event));
711 } else if (isXRandrType(response_type, XCB_RANDR_SCREEN_CHANGE_NOTIFY)) {
712 auto change_event = reinterpret_cast<xcb_randr_screen_change_notify_event_t *>(event);
713 if (auto virtualDesktop = virtualDesktopForRootWindow(change_event->root))
714 virtualDesktop->handleScreenChange(change_event);
715#if QT_CONFIG(xkb)
716 } else if (isXkbType(response_type)) {
717 auto xkb_event = reinterpret_cast<_xkb_event *>(event);
718 if (xkb_event->any.deviceID == m_keyboard->coreDeviceId()) {
719 switch (xkb_event->any.xkbType) {
720 // XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap
721 // updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent recompilations.
722 case XCB_XKB_STATE_NOTIFY:
723 m_keyboard->updateXKBState(&xkb_event->state_notify);
724 break;
725 case XCB_XKB_MAP_NOTIFY:
726 m_keyboard->updateKeymap();
727 break;
728 case XCB_XKB_NEW_KEYBOARD_NOTIFY: {
729 xcb_xkb_new_keyboard_notify_event_t *ev = &xkb_event->new_keyboard_notify;
730 if (ev->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
731 m_keyboard->updateKeymap();
732 break;
733 }
734 default:
735 break;
736 }
737 }
738#endif
739 } else {
740 handled = false; // event type still not recognized
741 }
742
743 if (handled)
744 return;
745
746 if (m_glIntegration)
747 m_glIntegration->handleXcbEvent(event, response_type);
748}
749
750void QXcbConnection::setFocusWindow(QWindow *w)
751{
752 m_focusWindow = w ? static_cast<QXcbWindow *>(w->handle()) : nullptr;
753}
754void QXcbConnection::setMouseGrabber(QXcbWindow *w)
755{
756 m_mouseGrabber = w;
757 m_mousePressWindow = nullptr;
758}
759void QXcbConnection::setMousePressWindow(QXcbWindow *w)
760{
761 m_mousePressWindow = w;
762}
763
764void QXcbConnection::grabServer()
765{
766 if (m_canGrabServer)
767 xcb_grab_server(xcb_connection());
768}
769
770void QXcbConnection::ungrabServer()
771{
772 if (m_canGrabServer)
773 xcb_ungrab_server(xcb_connection());
774}
775
776xcb_timestamp_t QXcbConnection::getTimestamp()
777{
778 // send a dummy event to myself to get the timestamp from X server.
779 xcb_window_t window = rootWindow();
780 xcb_atom_t dummyAtom = atom(QXcbAtom::CLIP_TEMPORARY);
781 xcb_change_property(xcb_connection(), XCB_PROP_MODE_APPEND, window, dummyAtom,
782 XCB_ATOM_INTEGER, 32, 0, nullptr);
783
784 connection()->flush();
785
786 xcb_generic_event_t *event = nullptr;
787
788 while (!event) {
789 connection()->sync();
790 event = eventQueue()->peek([window, dummyAtom](xcb_generic_event_t *event, int type) {
791 if (type != XCB_PROPERTY_NOTIFY)
792 return false;
793 auto propertyNotify = reinterpret_cast<xcb_property_notify_event_t *>(event);
794 return propertyNotify->window == window && propertyNotify->atom == dummyAtom;
795 });
796 }
797
798 xcb_property_notify_event_t *pn = reinterpret_cast<xcb_property_notify_event_t *>(event);
799 xcb_timestamp_t timestamp = pn->time;
800 free(event);
801
802 xcb_delete_property(xcb_connection(), window, dummyAtom);
803
804 return timestamp;
805}
806
807xcb_window_t QXcbConnection::getSelectionOwner(xcb_atom_t atom) const
808{
809 return Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom)->owner;
810}
811
812xcb_window_t QXcbConnection::getQtSelectionOwner()
813{
814 if (!m_qtSelectionOwner) {
815 xcb_screen_t *xcbScreen = primaryVirtualDesktop()->screen();
816 int16_t x = 0, y = 0;
817 uint16_t w = 3, h = 3;
818 m_qtSelectionOwner = xcb_generate_id(xcb_connection());
819 xcb_create_window(xcb_connection(),
820 XCB_COPY_FROM_PARENT, // depth -- same as root
821 m_qtSelectionOwner, // window id
822 xcbScreen->root, // parent window id
823 x, y, w, h,
824 0, // border width
825 XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class
826 xcbScreen->root_visual, // visual
827 0, // value mask
828 0); // value list
829
830 QXcbWindow::setWindowTitle(connection(), m_qtSelectionOwner,
831 QLatin1String("Qt Selection Owner for ") + QCoreApplication::applicationName());
832 }
833 return m_qtSelectionOwner;
834}
835
836xcb_window_t QXcbConnection::rootWindow()
837{
838 QXcbScreen *s = primaryScreen();
839 return s ? s->root() : 0;
840}
841
842xcb_window_t QXcbConnection::clientLeader()
843{
844 if (m_clientLeader == 0) {
845 m_clientLeader = xcb_generate_id(xcb_connection());
846 QXcbScreen *screen = primaryScreen();
847 xcb_create_window(xcb_connection(),
848 XCB_COPY_FROM_PARENT,
849 m_clientLeader,
850 screen->root(),
851 0, 0, 1, 1,
852 0,
853 XCB_WINDOW_CLASS_INPUT_OUTPUT,
854 screen->screen()->root_visual,
855 0, 0);
856
857
858 QXcbWindow::setWindowTitle(connection(), m_clientLeader,
859 QStringLiteral("Qt Client Leader Window"));
860
861 xcb_change_property(xcb_connection(),
862 XCB_PROP_MODE_REPLACE,
863 m_clientLeader,
864 atom(QXcbAtom::WM_CLIENT_LEADER),
865 XCB_ATOM_WINDOW,
866 32,
867 1,
868 &m_clientLeader);
869
870#if QT_CONFIG(xcb_sm)
871 // If we are session managed, inform the window manager about it
872 QByteArray session = qGuiApp->sessionId().toLatin1();
873 if (!session.isEmpty()) {
874 xcb_change_property(xcb_connection(),
875 XCB_PROP_MODE_REPLACE,
876 m_clientLeader,
877 atom(QXcbAtom::SM_CLIENT_ID),
878 XCB_ATOM_STRING,
879 8,
880 session.length(),
881 session.constData());
882 }
883#endif
884 }
885 return m_clientLeader;
886}
887
888/*! \internal
889
890 Compresses events of the same type to avoid swamping the event queue.
891 If event compression is not desired there are several options what developers can do:
892
893 1) Write responsive applications. We drop events that have been buffered in the event
894 queue while waiting on unresponsive GUI thread.
895 2) Use QAbstractNativeEventFilter to get all events from X connection. This is not optimal
896 because it requires working with native event types.
897 3) Or add public API to Qt for disabling event compression QTBUG-44964
898
899*/
900bool QXcbConnection::compressEvent(xcb_generic_event_t *event) const
901{
902 if (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents))
903 return false;
904
905 uint responseType = event->response_type & ~0x80;
906
907 if (responseType == XCB_MOTION_NOTIFY) {
908 // compress XCB_MOTION_NOTIFY notify events
909 return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
910 [](xcb_generic_event_t *, int type) {
911 return type == XCB_MOTION_NOTIFY;
912 });
913 }
914
915#if QT_CONFIG(xcb_xinput)
916 // compress XI_* events
917 if (responseType == XCB_GE_GENERIC) {
918 if (!hasXInput2())
919 return false;
920
921 // compress XI_Motion
922 if (isXIType(event, XCB_INPUT_MOTION)) {
923#if QT_CONFIG(tabletevent)
924 auto xdev = reinterpret_cast<xcb_input_motion_event_t *>(event);
925 if (!QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents) &&
926 const_cast<QXcbConnection *>(this)->tabletDataForDevice(xdev->sourceid))
927 return false;
928#endif // QT_CONFIG(tabletevent)
929 return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
930 [this](xcb_generic_event_t *next, int) {
931 return isXIType(next, XCB_INPUT_MOTION);
932 });
933 }
934
935 // compress XI_TouchUpdate for the same touch point id
936 if (isXIType(event, XCB_INPUT_TOUCH_UPDATE)) {
937 auto touchUpdateEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(event);
938 uint32_t id = touchUpdateEvent->detail % INT_MAX;
939
940 return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
941 [this, &id](xcb_generic_event_t *next, int) {
942 if (!isXIType(next, XCB_INPUT_TOUCH_UPDATE))
943 return false;
944 auto touchUpdateNextEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(next);
945 return id == touchUpdateNextEvent->detail % INT_MAX;
946 });
947 }
948
949 return false;
950 }
951#endif
952
953 if (responseType == XCB_CONFIGURE_NOTIFY) {
954 // compress multiple configure notify events for the same window
955 return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
956 [event](xcb_generic_event_t *next, int type) {
957 if (type != XCB_CONFIGURE_NOTIFY)
958 return false;
959 auto currentEvent = reinterpret_cast<xcb_configure_notify_event_t *>(event);
960 auto nextEvent = reinterpret_cast<xcb_configure_notify_event_t *>(next);
961 return currentEvent->event == nextEvent->event;
962 });
963 }
964
965 return false;
966}
967
968bool QXcbConnection::isUserInputEvent(xcb_generic_event_t *event) const
969{
970 auto eventType = event->response_type & ~0x80;
971 bool isInputEvent = eventType == XCB_BUTTON_PRESS ||
972 eventType == XCB_BUTTON_RELEASE ||
973 eventType == XCB_KEY_PRESS ||
974 eventType == XCB_KEY_RELEASE ||
975 eventType == XCB_MOTION_NOTIFY ||
976 eventType == XCB_ENTER_NOTIFY ||
977 eventType == XCB_LEAVE_NOTIFY;
978 if (isInputEvent)
979 return true;
980
981#if QT_CONFIG(xcb_xinput)
982 if (connection()->hasXInput2()) {
983 isInputEvent = isXIType(event, XCB_INPUT_BUTTON_PRESS) ||
984 isXIType(event, XCB_INPUT_BUTTON_RELEASE) ||
985 isXIType(event, XCB_INPUT_MOTION) ||
986 isXIType(event, XCB_INPUT_TOUCH_BEGIN) ||
987 isXIType(event, XCB_INPUT_TOUCH_UPDATE) ||
988 isXIType(event, XCB_INPUT_TOUCH_END) ||
989 isXIType(event, XCB_INPUT_ENTER) ||
990 isXIType(event, XCB_INPUT_LEAVE) ||
991 // wacom driver's way of reporting tool proximity
992 isXIType(event, XCB_INPUT_PROPERTY);
993 }
994 if (isInputEvent)
995 return true;
996#endif
997
998 if (eventType == XCB_CLIENT_MESSAGE) {
999 auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
1000 if (clientMessage->format == 32 && clientMessage->type == atom(QXcbAtom::WM_PROTOCOLS))
1001 if (clientMessage->data.data32[0] == atom(QXcbAtom::WM_DELETE_WINDOW))
1002 isInputEvent = true;
1003 }
1004
1005 return isInputEvent;
1006}
1007
1008void QXcbConnection::processXcbEvents(QEventLoop::ProcessEventsFlags flags)
1009{
1010 int connection_error = xcb_connection_has_error(xcb_connection());
1011 if (connection_error) {
1012 qWarning("The X11 connection broke (error %d). Did the X11 server die?", connection_error);
1013 exit(1);
1014 }
1015
1016 m_eventQueue->flushBufferedEvents();
1017
1018 while (xcb_generic_event_t *event = m_eventQueue->takeFirst(flags)) {
1019 QScopedPointer<xcb_generic_event_t, QScopedPointerPodDeleter> eventGuard(event);
1020
1021 if (!(event->response_type & ~0x80)) {
1022 handleXcbError(reinterpret_cast<xcb_generic_error_t *>(event));
1023 continue;
1024 }
1025
1026 if (compressEvent(event))
1027 continue;
1028
1029 handleXcbEvent(event);
1030
1031 // The lock-based solution used to free the lock inside this loop,
1032 // hence allowing for more events to arrive. ### Check if we want
1033 // this flush here after QTBUG-70095
1034 m_eventQueue->flushBufferedEvents();
1035 }
1036
1037 xcb_flush(xcb_connection());
1038}
1039
1040const xcb_format_t *QXcbConnection::formatForDepth(uint8_t depth) const
1041{
1042 xcb_format_iterator_t iterator =
1043 xcb_setup_pixmap_formats_iterator(setup());
1044
1045 while (iterator.rem) {
1046 xcb_format_t *format = iterator.data;
1047 if (format->depth == depth)
1048 return format;
1049 xcb_format_next(&iterator);
1050 }
1051
1052 qWarning() << "XCB failed to find an xcb_format_t for depth:" << depth;
1053 return nullptr;
1054}
1055
1056void QXcbConnection::sync()
1057{
1058 // from xcb_aux_sync
1059 xcb_get_input_focus_cookie_t cookie = xcb_get_input_focus(xcb_connection());
1060 free(xcb_get_input_focus_reply(xcb_connection(), cookie, 0));
1061}
1062
1063QXcbSystemTrayTracker *QXcbConnection::systemTrayTracker() const
1064{
1065 if (!m_systemTrayTracker) {
1066 QXcbConnection *self = const_cast<QXcbConnection *>(this);
1067 if ((self->m_systemTrayTracker = QXcbSystemTrayTracker::create(self))) {
1068 connect(m_systemTrayTracker, SIGNAL(systemTrayWindowChanged(QScreen*)),
1069 QGuiApplication::platformNativeInterface(), SIGNAL(systemTrayWindowChanged(QScreen*)));
1070 }
1071 }
1072 return m_systemTrayTracker;
1073}
1074
1075Qt::MouseButtons QXcbConnection::queryMouseButtons() const
1076{
1077 int stateMask = 0;
1078 QXcbCursor::queryPointer(connection(), 0, 0, &stateMask);
1079 return translateMouseButtons(stateMask);
1080}
1081
1082Qt::KeyboardModifiers QXcbConnection::queryKeyboardModifiers() const
1083{
1084 int stateMask = 0;
1085 QXcbCursor::queryPointer(connection(), 0, 0, &stateMask);
1086 return keyboard()->translateModifiers(stateMask);
1087}
1088
1089QXcbGlIntegration *QXcbConnection::glIntegration() const
1090{
1091 if (m_glIntegrationInitialized)
1092 return m_glIntegration;
1093
1094 QStringList glIntegrationNames;
1095 glIntegrationNames << QStringLiteral("xcb_glx") << QStringLiteral("xcb_egl");
1096 QString glIntegrationName = QString::fromLocal8Bit(qgetenv("QT_XCB_GL_INTEGRATION"));
1097 if (!glIntegrationName.isEmpty()) {
1098 qCDebug(lcQpaGl) << "QT_XCB_GL_INTEGRATION is set to" << glIntegrationName;
1099 if (glIntegrationName != QLatin1String("none")) {
1100 glIntegrationNames.removeAll(glIntegrationName);
1101 glIntegrationNames.prepend(glIntegrationName);
1102 } else {
1103 glIntegrationNames.clear();
1104 }
1105 }
1106
1107 if (!glIntegrationNames.isEmpty()) {
1108 qCDebug(lcQpaGl) << "Choosing xcb gl-integration based on following priority\n" << glIntegrationNames;
1109 for (int i = 0; i < glIntegrationNames.size() && !m_glIntegration; i++) {
1110 m_glIntegration = QXcbGlIntegrationFactory::create(glIntegrationNames.at(i));
1111 if (m_glIntegration && !m_glIntegration->initialize(const_cast<QXcbConnection *>(this))) {
1112 qCDebug(lcQpaGl) << "Failed to initialize xcb gl-integration" << glIntegrationNames.at(i);
1113 delete m_glIntegration;
1114 m_glIntegration = nullptr;
1115 }
1116 }
1117 if (!m_glIntegration)
1118 qCDebug(lcQpaGl) << "Failed to create xcb gl-integration";
1119 }
1120
1121 m_glIntegrationInitialized = true;
1122 return m_glIntegration;
1123}
1124
1125bool QXcbConnection::event(QEvent *e)
1126{
1127 if (e->type() == QEvent::User + 1) {
1128 QXcbSyncWindowRequest *ev = static_cast<QXcbSyncWindowRequest *>(e);
1129 QXcbWindow *w = ev->window();
1130 if (w) {
1131 w->updateSyncRequestCounter();
1132 ev->invalidate();
1133 }
1134 return true;
1135 }
1136 return QObject::event(e);
1137}
1138
1139void QXcbSyncWindowRequest::invalidate()
1140{
1141 if (m_window) {
1142 m_window->clearSyncWindowRequest();
1143 m_window = 0;
1144 }
1145}
1146
1147QXcbConnectionGrabber::QXcbConnectionGrabber(QXcbConnection *connection)
1148 :m_connection(connection)
1149{
1150 connection->grabServer();
1151}
1152
1153QXcbConnectionGrabber::~QXcbConnectionGrabber()
1154{
1155 if (m_connection)
1156 m_connection->ungrabServer();
1157}
1158
1159void QXcbConnectionGrabber::release()
1160{
1161 if (m_connection) {
1162 m_connection->ungrabServer();
1163 m_connection = 0;
1164 }
1165}
1166
1167QT_END_NAMESPACE
1168