// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include #include #include #include "qxcbconnection.h" #include "qxcbkeyboard.h" #include "qxcbwindow.h" #include "qxcbclipboard.h" #if QT_CONFIG(draganddrop) #include "qxcbdrag.h" #endif #include "qxcbwmsupport.h" #include "qxcbnativeinterface.h" #include "qxcbintegration.h" #include "qxcbsystemtraytracker.h" #include "qxcbglintegrationfactory.h" #include "qxcbglintegration.h" #include "qxcbcursor.h" #include "qxcbbackingstore.h" #include "qxcbeventqueue.h" #include #include #include #include #include #include #define explicit dont_use_cxx_explicit #include #undef explicit #include #if QT_CONFIG(xcb_xlib) #include "qt_xlib_wrapper.h" #endif QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; Q_LOGGING_CATEGORY(lcQpaXInput, "qt.qpa.input") Q_LOGGING_CATEGORY(lcQpaXInputDevices, "qt.qpa.input.devices") Q_LOGGING_CATEGORY(lcQpaXInputEvents, "qt.qpa.input.events") Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen") Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events") Q_LOGGING_CATEGORY(lcQpaEventReader, "qt.qpa.events.reader") Q_LOGGING_CATEGORY(lcQpaPeeker, "qt.qpa.peeker") Q_LOGGING_CATEGORY(lcQpaKeyboard, "qt.qpa.xkeyboard") Q_LOGGING_CATEGORY(lcQpaClipboard, "qt.qpa.clipboard") Q_LOGGING_CATEGORY(lcQpaXDnd, "qt.qpa.xdnd") QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGrabServer, xcb_visualid_t defaultVisualId, const char *displayName) : QXcbBasicConnection(displayName) , m_duringSystemMoveResize(false) , m_canGrabServer(canGrabServer) , m_defaultVisualId(defaultVisualId) , m_nativeInterface(nativeInterface) { if (!isConnected()) return; m_eventQueue = new QXcbEventQueue(this); if (hasXRandr()) xrandrSelectEvents(); initializeScreens(false); if (hasXInput2()) { xi2SetupDevices(); xi2SelectStateEvents(); } m_wmSupport.reset(new QXcbWMSupport(this)); m_keyboard = new QXcbKeyboard(this); #ifndef QT_NO_CLIPBOARD m_clipboard = new QXcbClipboard(this); #endif #if QT_CONFIG(draganddrop) m_drag = new QXcbDrag(this); #endif setStartupId(qgetenv("DESKTOP_STARTUP_ID")); if (!startupId().isNull()) qunsetenv("DESKTOP_STARTUP_ID"); const int focusInDelay = 100; m_focusInTimer.setSingleShot(true); m_focusInTimer.setInterval(focusInDelay); m_focusInTimer.callOnTimeout(this, []() { // No FocusIn events for us, proceed with FocusOut normally. QWindowSystemInterface::handleFocusWindowChanged(nullptr, Qt::ActiveWindowFocusReason); }); sync(); } QXcbConnection::~QXcbConnection() { #ifndef QT_NO_CLIPBOARD delete m_clipboard; #endif #if QT_CONFIG(draganddrop) delete m_drag; #endif if (m_eventQueue) delete m_eventQueue; // Delete screens in reverse order to avoid crash in case of multiple screens while (!m_screens.isEmpty()) QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast()); while (!m_virtualDesktops.isEmpty()) delete m_virtualDesktops.takeLast(); delete m_glIntegration; delete m_keyboard; } QXcbScreen *QXcbConnection::primaryScreen() const { if (!m_screens.isEmpty()) { Q_ASSERT(m_screens.first()->screenNumber() == primaryScreenNumber()); return m_screens.first(); } return nullptr; } void QXcbConnection::addWindowEventListener(xcb_window_t id, QXcbWindowEventListener *eventListener) { m_mapper.insert(id, eventListener); } void QXcbConnection::removeWindowEventListener(xcb_window_t id) { m_mapper.remove(id); } QXcbWindowEventListener *QXcbConnection::windowEventListenerFromId(xcb_window_t id) { return m_mapper.value(id, nullptr); } QXcbWindow *QXcbConnection::platformWindowFromId(xcb_window_t id) { QXcbWindowEventListener *listener = m_mapper.value(id, nullptr); if (listener) return listener->toWindow(); return nullptr; } #define HANDLE_PLATFORM_WINDOW_EVENT(event_t, windowMember, handler) \ { \ auto e = reinterpret_cast(event); \ if (QXcbWindowEventListener *eventListener = windowEventListenerFromId(e->windowMember)) { \ if (eventListener->handleNativeEvent(event)) \ return; \ eventListener->handler(e); \ } \ } \ break; #define HANDLE_KEYBOARD_EVENT(event_t, handler) \ { \ auto e = reinterpret_cast(event); \ if (QXcbWindowEventListener *eventListener = windowEventListenerFromId(e->event)) { \ if (eventListener->handleNativeEvent(event)) \ return; \ m_keyboard->handler(e); \ } \ } \ break; void QXcbConnection::printXcbEvent(const QLoggingCategory &log, const char *message, xcb_generic_event_t *event) const { quint8 response_type = event->response_type & ~0x80; quint16 sequence = event->sequence; #define PRINT_AND_RETURN(name) { \ qCDebug(log, "%s | %s(%d) | sequence: %d", message, name, response_type, sequence); \ return; \ } #define CASE_PRINT_AND_RETURN(name) case name : PRINT_AND_RETURN(#name); #define XI_PRINT_AND_RETURN(name) { \ qCDebug(log, "%s | XInput Event(%s) | sequence: %d", message, name, sequence); \ return; \ } #define XI_CASE_PRINT_AND_RETURN(name) case name : XI_PRINT_AND_RETURN(#name); switch (response_type) { CASE_PRINT_AND_RETURN( XCB_KEY_PRESS ); CASE_PRINT_AND_RETURN( XCB_KEY_RELEASE ); CASE_PRINT_AND_RETURN( XCB_BUTTON_PRESS ); CASE_PRINT_AND_RETURN( XCB_BUTTON_RELEASE ); CASE_PRINT_AND_RETURN( XCB_MOTION_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_ENTER_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_LEAVE_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_FOCUS_IN ); CASE_PRINT_AND_RETURN( XCB_FOCUS_OUT ); CASE_PRINT_AND_RETURN( XCB_KEYMAP_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_EXPOSE ); CASE_PRINT_AND_RETURN( XCB_GRAPHICS_EXPOSURE ); CASE_PRINT_AND_RETURN( XCB_NO_EXPOSURE ); CASE_PRINT_AND_RETURN( XCB_VISIBILITY_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_CREATE_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_DESTROY_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_UNMAP_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_MAP_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_MAP_REQUEST ); CASE_PRINT_AND_RETURN( XCB_REPARENT_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_CONFIGURE_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_CONFIGURE_REQUEST ); CASE_PRINT_AND_RETURN( XCB_GRAVITY_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_RESIZE_REQUEST ); CASE_PRINT_AND_RETURN( XCB_CIRCULATE_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_CIRCULATE_REQUEST ); CASE_PRINT_AND_RETURN( XCB_PROPERTY_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_SELECTION_CLEAR ); CASE_PRINT_AND_RETURN( XCB_SELECTION_REQUEST ); CASE_PRINT_AND_RETURN( XCB_SELECTION_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_COLORMAP_NOTIFY ); CASE_PRINT_AND_RETURN( XCB_CLIENT_MESSAGE ); CASE_PRINT_AND_RETURN( XCB_MAPPING_NOTIFY ); case XCB_GE_GENERIC: { if (hasXInput2() && isXIEvent(event)) { auto *xiDeviceEvent = reinterpret_cast(event); // qt_xcb_input_device_event_t switch (xiDeviceEvent->event_type) { XI_CASE_PRINT_AND_RETURN( XCB_INPUT_KEY_PRESS ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_KEY_RELEASE ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_BUTTON_PRESS ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_BUTTON_RELEASE ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_MOTION ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_ENTER ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_LEAVE ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_FOCUS_IN ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_FOCUS_OUT ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_HIERARCHY ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_PROPERTY ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_KEY_PRESS ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_KEY_RELEASE ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_BUTTON_PRESS ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_BUTTON_RELEASE ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_MOTION ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_TOUCH_BEGIN ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_TOUCH_UPDATE ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_TOUCH_END ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_TOUCH_OWNERSHIP ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_TOUCH_BEGIN ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_TOUCH_UPDATE ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_TOUCH_END ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_BARRIER_HIT ); XI_CASE_PRINT_AND_RETURN( XCB_INPUT_BARRIER_LEAVE ); default: qCDebug(log, "%s | XInput Event(other type) | sequence: %d", message, sequence); return; } } else { qCDebug(log, "%s | %s(%d) | sequence: %d", message, "XCB_GE_GENERIC", response_type, sequence); return; } } } // XFixes if (isXFixesType(response_type, XCB_XFIXES_SELECTION_NOTIFY)) PRINT_AND_RETURN("XCB_XFIXES_SELECTION_NOTIFY"); // XRandR if (isXRandrType(response_type, XCB_RANDR_NOTIFY)) PRINT_AND_RETURN("XCB_RANDR_NOTIFY"); if (isXRandrType(response_type, XCB_RANDR_SCREEN_CHANGE_NOTIFY)) PRINT_AND_RETURN("XCB_RANDR_SCREEN_CHANGE_NOTIFY"); // XKB if (isXkbType(response_type)) PRINT_AND_RETURN("XCB_XKB_* event"); // UNKNOWN qCDebug(log, "%s | unknown(%d) | sequence: %d", message, response_type, sequence); #undef PRINT_AND_RETURN #undef CASE_PRINT_AND_RETURN } const char *xcb_errors[] = { "Success", "BadRequest", "BadValue", "BadWindow", "BadPixmap", "BadAtom", "BadCursor", "BadFont", "BadMatch", "BadDrawable", "BadAccess", "BadAlloc", "BadColor", "BadGC", "BadIDChoice", "BadName", "BadLength", "BadImplementation", "Unknown" }; const char *xcb_protocol_request_codes[] = { "Null", "CreateWindow", "ChangeWindowAttributes", "GetWindowAttributes", "DestroyWindow", "DestroySubwindows", "ChangeSaveSet", "ReparentWindow", "MapWindow", "MapSubwindows", "UnmapWindow", "UnmapSubwindows", "ConfigureWindow", "CirculateWindow", "GetGeometry", "QueryTree", "InternAtom", "GetAtomName", "ChangeProperty", "DeleteProperty", "GetProperty", "ListProperties", "SetSelectionOwner", "GetSelectionOwner", "ConvertSelection", "SendEvent", "GrabPointer", "UngrabPointer", "GrabButton", "UngrabButton", "ChangeActivePointerGrab", "GrabKeyboard", "UngrabKeyboard", "GrabKey", "UngrabKey", "AllowEvents", "GrabServer", "UngrabServer", "QueryPointer", "GetMotionEvents", "TranslateCoords", "WarpPointer", "SetInputFocus", "GetInputFocus", "QueryKeymap", "OpenFont", "CloseFont", "QueryFont", "QueryTextExtents", "ListFonts", "ListFontsWithInfo", "SetFontPath", "GetFontPath", "CreatePixmap", "FreePixmap", "CreateGC", "ChangeGC", "CopyGC", "SetDashes", "SetClipRectangles", "FreeGC", "ClearArea", "CopyArea", "CopyPlane", "PolyPoint", "PolyLine", "PolySegment", "PolyRectangle", "PolyArc", "FillPoly", "PolyFillRectangle", "PolyFillArc", "PutImage", "GetImage", "PolyText8", "PolyText16", "ImageText8", "ImageText16", "CreateColormap", "FreeColormap", "CopyColormapAndFree", "InstallColormap", "UninstallColormap", "ListInstalledColormaps", "AllocColor", "AllocNamedColor", "AllocColorCells", "AllocColorPlanes", "FreeColors", "StoreColors", "StoreNamedColor", "QueryColors", "LookupColor", "CreateCursor", "CreateGlyphCursor", "FreeCursor", "RecolorCursor", "QueryBestSize", "QueryExtension", "ListExtensions", "ChangeKeyboardMapping", "GetKeyboardMapping", "ChangeKeyboardControl", "GetKeyboardControl", "Bell", "ChangePointerControl", "GetPointerControl", "SetScreenSaver", "GetScreenSaver", "ChangeHosts", "ListHosts", "SetAccessControl", "SetCloseDownMode", "KillClient", "RotateProperties", "ForceScreenSaver", "SetPointerMapping", "GetPointerMapping", "SetModifierMapping", "GetModifierMapping", "Unknown" }; void QXcbConnection::handleXcbError(xcb_generic_error_t *error) { qintptr result = 0; QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(); if (dispatcher && dispatcher->filterNativeEvent(m_nativeInterface->nativeEventType(), error, &result)) return; printXcbError("QXcbConnection: XCB error", error); } void QXcbConnection::printXcbError(const char *message, xcb_generic_error_t *error) { uint clamped_error_code = qMin(error->error_code, (sizeof(xcb_errors) / sizeof(xcb_errors[0])) - 1); uint clamped_major_code = qMin(error->major_code, (sizeof(xcb_protocol_request_codes) / sizeof(xcb_protocol_request_codes[0])) - 1); qCDebug(lcQpaXcb, "%s: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d", message, int(error->error_code), xcb_errors[clamped_error_code], int(error->sequence), int(error->resource_id), int(error->major_code), xcb_protocol_request_codes[clamped_major_code], int(error->minor_code)); } static Qt::MouseButtons translateMouseButtons(int s) { Qt::MouseButtons ret; if (s & XCB_BUTTON_MASK_1) ret |= Qt::LeftButton; if (s & XCB_BUTTON_MASK_2) ret |= Qt::MiddleButton; if (s & XCB_BUTTON_MASK_3) ret |= Qt::RightButton; return ret; } void QXcbConnection::setButtonState(Qt::MouseButton button, bool down) { m_buttonState.setFlag(button, down); m_button = button; } Qt::MouseButton QXcbConnection::translateMouseButton(xcb_button_t s) { switch (s) { case 1: return Qt::LeftButton; case 2: return Qt::MiddleButton; case 3: return Qt::RightButton; // Button values 4-7 were already handled as Wheel events, and won't occur here. case 8: return Qt::BackButton; // Also known as Qt::ExtraButton1 case 9: return Qt::ForwardButton; // Also known as Qt::ExtraButton2 case 10: return Qt::ExtraButton3; case 11: return Qt::ExtraButton4; case 12: return Qt::ExtraButton5; case 13: return Qt::ExtraButton6; case 14: return Qt::ExtraButton7; case 15: return Qt::ExtraButton8; case 16: return Qt::ExtraButton9; case 17: return Qt::ExtraButton10; case 18: return Qt::ExtraButton11; case 19: return Qt::ExtraButton12; case 20: return Qt::ExtraButton13; case 21: return Qt::ExtraButton14; case 22: return Qt::ExtraButton15; case 23: return Qt::ExtraButton16; case 24: return Qt::ExtraButton17; case 25: return Qt::ExtraButton18; case 26: return Qt::ExtraButton19; case 27: return Qt::ExtraButton20; case 28: return Qt::ExtraButton21; case 29: return Qt::ExtraButton22; case 30: return Qt::ExtraButton23; case 31: return Qt::ExtraButton24; default: return Qt::NoButton; } } namespace { typedef union { /* All XKB events share these fields. */ struct { uint8_t response_type; uint8_t xkbType; uint16_t sequence; xcb_timestamp_t time; uint8_t deviceID; } any; xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify; xcb_xkb_map_notify_event_t map_notify; xcb_xkb_state_notify_event_t state_notify; } _xkb_event; } void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) { if (Q_UNLIKELY(lcQpaEvents().isDebugEnabled())) printXcbEvent(lcQpaEvents(), "Event", event); qintptr result = 0; // Used only by MS Windows if (QAbstractEventDispatcher *dispatcher = QAbstractEventDispatcher::instance()) { if (dispatcher->filterNativeEvent(m_nativeInterface->nativeEventType(), event, &result)) return; } uint response_type = event->response_type & ~0x80; bool handled = true; switch (response_type) { case XCB_EXPOSE: HANDLE_PLATFORM_WINDOW_EVENT(xcb_expose_event_t, window, handleExposeEvent); case XCB_BUTTON_PRESS: { auto ev = reinterpret_cast(event); setTime(ev->time); m_keyboard->updateXKBStateFromCore(ev->state); // the event explicitly contains the state of the three first buttons, // the rest we need to manage ourselves m_buttonState = (m_buttonState & ~0x7) | translateMouseButtons(ev->state); setButtonState(translateMouseButton(ev->detail), true); if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) qCDebug(lcQpaXInputEvents, "legacy mouse press, button %d state %X", ev->detail, static_cast(m_buttonState)); HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_press_event_t, event, handleButtonPressEvent); } case XCB_BUTTON_RELEASE: { auto ev = reinterpret_cast(event); setTime(ev->time); if (m_duringSystemMoveResize && ev->root != XCB_NONE) abortSystemMoveResize(ev->root); m_keyboard->updateXKBStateFromCore(ev->state); m_buttonState = (m_buttonState & ~0x7) | translateMouseButtons(ev->state); setButtonState(translateMouseButton(ev->detail), false); if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) qCDebug(lcQpaXInputEvents, "legacy mouse release, button %d state %X", ev->detail, static_cast(m_buttonState)); HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_release_event_t, event, handleButtonReleaseEvent); } case XCB_MOTION_NOTIFY: { auto ev = reinterpret_cast(event); setTime(ev->time); m_keyboard->updateXKBStateFromCore(ev->state); m_buttonState = (m_buttonState & ~0x7) | translateMouseButtons(ev->state); if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) qCDebug(lcQpaXInputEvents, "legacy mouse move %d,%d button %d state %X", ev->event_x, ev->event_y, ev->detail, static_cast(m_buttonState)); HANDLE_PLATFORM_WINDOW_EVENT(xcb_motion_notify_event_t, event, handleMotionNotifyEvent); } case XCB_CONFIGURE_NOTIFY: { if (isAtLeastXRandR15()) { auto ev = reinterpret_cast(event); if (ev->event == rootWindow()) initializeScreens(true); } HANDLE_PLATFORM_WINDOW_EVENT(xcb_configure_notify_event_t, event, handleConfigureNotifyEvent); } case XCB_MAP_NOTIFY: HANDLE_PLATFORM_WINDOW_EVENT(xcb_map_notify_event_t, event, handleMapNotifyEvent); case XCB_UNMAP_NOTIFY: HANDLE_PLATFORM_WINDOW_EVENT(xcb_unmap_notify_event_t, event, handleUnmapNotifyEvent); case XCB_DESTROY_NOTIFY: HANDLE_PLATFORM_WINDOW_EVENT(xcb_destroy_notify_event_t, event, handleDestroyNotifyEvent); case XCB_CLIENT_MESSAGE: { auto clientMessage = reinterpret_cast(event); if (clientMessage->format != 32) return; #if QT_CONFIG(draganddrop) if (clientMessage->type == atom(QXcbAtom::AtomXdndStatus)) drag()->handleStatus(clientMessage); else if (clientMessage->type == atom(QXcbAtom::AtomXdndFinished)) drag()->handleFinished(clientMessage); #endif if (m_systemTrayTracker && clientMessage->type == atom(QXcbAtom::AtomMANAGER)) m_systemTrayTracker->notifyManagerClientMessageEvent(clientMessage); HANDLE_PLATFORM_WINDOW_EVENT(xcb_client_message_event_t, window, handleClientMessageEvent); } case XCB_ENTER_NOTIFY: if (hasXInput2()) { // Prefer XI2 enter (XCB_INPUT_ENTER) events over core events. break; } setTime(reinterpret_cast(event)->time); HANDLE_PLATFORM_WINDOW_EVENT(xcb_enter_notify_event_t, event, handleEnterNotifyEvent); case XCB_LEAVE_NOTIFY: { if (hasXInput2()) { // Prefer XI2 leave (XCB_INPUT_LEAVE) events over core events. break; } auto ev = reinterpret_cast(event); setTime(ev->time); m_keyboard->updateXKBStateFromCore(ev->state); HANDLE_PLATFORM_WINDOW_EVENT(xcb_leave_notify_event_t, event, handleLeaveNotifyEvent); } case XCB_FOCUS_IN: HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_in_event_t, event, handleFocusInEvent); case XCB_FOCUS_OUT: HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_out_event_t, event, handleFocusOutEvent); case XCB_KEY_PRESS: { auto keyPress = reinterpret_cast(event); setTime(keyPress->time); m_keyboard->updateXKBStateFromCore(keyPress->state); setTime(keyPress->time); HANDLE_KEYBOARD_EVENT(xcb_key_press_event_t, handleKeyPressEvent); } case XCB_KEY_RELEASE: { auto keyRelease = reinterpret_cast(event); setTime(keyRelease->time); m_keyboard->updateXKBStateFromCore(keyRelease->state); HANDLE_KEYBOARD_EVENT(xcb_key_release_event_t, handleKeyReleaseEvent); } case XCB_MAPPING_NOTIFY: m_keyboard->updateKeymap(reinterpret_cast(event)); break; case XCB_SELECTION_REQUEST: { #if QT_CONFIG(draganddrop) || QT_CONFIG(clipboard) auto selectionRequest = reinterpret_cast(event); setTime(selectionRequest->time); #endif #if QT_CONFIG(draganddrop) if (selectionRequest->selection == atom(QXcbAtom::AtomXdndSelection)) m_drag->handleSelectionRequest(selectionRequest); else #endif { #ifndef QT_NO_CLIPBOARD m_clipboard->handleSelectionRequest(selectionRequest); #endif } break; } case XCB_SELECTION_CLEAR: setTime((reinterpret_cast(event))->time); #ifndef QT_NO_CLIPBOARD m_clipboard->handleSelectionClearRequest(reinterpret_cast(event)); #endif break; case XCB_SELECTION_NOTIFY: setTime((reinterpret_cast(event))->time); break; case XCB_PROPERTY_NOTIFY: { auto propertyNotify = reinterpret_cast(event); setTime(propertyNotify->time); #ifndef QT_NO_CLIPBOARD if (m_clipboard->handlePropertyNotify(event)) break; #endif if (propertyNotify->atom == atom(QXcbAtom::Atom_NET_WORKAREA)) { QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(propertyNotify->window); if (virtualDesktop) virtualDesktop->updateWorkArea(); } else if (propertyNotify->atom == atom(QXcbAtom::Atom_NET_SUPPORTED)) { m_wmSupport->updateNetWMAtoms(); } else { HANDLE_PLATFORM_WINDOW_EVENT(xcb_property_notify_event_t, window, handlePropertyNotifyEvent); } break; } case XCB_GE_GENERIC: // Here the windowEventListener is invoked from xi2HandleEvent() if (hasXInput2() && isXIEvent(event)) xi2HandleEvent(reinterpret_cast(event)); break; default: handled = false; // event type not recognized break; } if (handled) return; handled = true; if (isXFixesType(response_type, XCB_XFIXES_SELECTION_NOTIFY)) { auto notify_event = reinterpret_cast(event); setTime(notify_event->timestamp); #ifndef QT_NO_CLIPBOARD m_clipboard->handleXFixesSelectionRequest(notify_event); #endif for (QXcbVirtualDesktop *virtualDesktop : std::as_const(m_virtualDesktops)) virtualDesktop->handleXFixesSelectionNotify(notify_event); } else if (isXRandrType(response_type, XCB_RANDR_NOTIFY)) { if (!isAtLeastXRandR15()) updateScreens(reinterpret_cast(event)); } else if (isXRandrType(response_type, XCB_RANDR_SCREEN_CHANGE_NOTIFY)) { if (!isAtLeastXRandR15()) { auto change_event = reinterpret_cast(event); if (auto virtualDesktop = virtualDesktopForRootWindow(change_event->root)) virtualDesktop->handleScreenChange(change_event); } } else if (isXkbType(response_type)) { auto xkb_event = reinterpret_cast<_xkb_event *>(event); if (xkb_event->any.deviceID == m_keyboard->coreDeviceId()) { switch (xkb_event->any.xkbType) { // XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap // updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundant recompilations. case XCB_XKB_STATE_NOTIFY: m_keyboard->updateXKBState(&xkb_event->state_notify); break; case XCB_XKB_MAP_NOTIFY: m_keyboard->updateKeymap(); break; case XCB_XKB_NEW_KEYBOARD_NOTIFY: { xcb_xkb_new_keyboard_notify_event_t *ev = &xkb_event->new_keyboard_notify; if (ev->changed & XCB_XKB_NKN_DETAIL_KEYCODES) m_keyboard->updateKeymap(); break; } default: break; } } } else { handled = false; // event type still not recognized } if (handled) return; if (m_glIntegration) m_glIntegration->handleXcbEvent(event, response_type); } void QXcbConnection::setFocusWindow(QWindow *w) { m_focusWindow = w ? static_cast(w->handle()) : nullptr; } void QXcbConnection::setMouseGrabber(QXcbWindow *w) { m_mouseGrabber = w; m_mousePressWindow = nullptr; } void QXcbConnection::setMousePressWindow(QXcbWindow *w) { m_mousePressWindow = w; } QByteArray QXcbConnection::startupId() const { return m_startupId; } void QXcbConnection::setStartupId(const QByteArray &nextId) { m_startupId = nextId; if (m_clientLeader) { if (!nextId.isEmpty()) xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, clientLeader(), atom(QXcbAtom::Atom_NET_STARTUP_ID), atom(QXcbAtom::AtomUTF8_STRING), 8, nextId.size(), nextId.constData()); else xcb_delete_property(xcb_connection(), clientLeader(), atom(QXcbAtom::Atom_NET_STARTUP_ID)); } } void QXcbConnection::grabServer() { if (m_canGrabServer) xcb_grab_server(xcb_connection()); } void QXcbConnection::ungrabServer() { if (m_canGrabServer) xcb_ungrab_server(xcb_connection()); } QString QXcbConnection::windowManagerName() const { QXcbVirtualDesktop *pvd = primaryVirtualDesktop(); if (pvd) return pvd->windowManagerName().toLower(); return QString(); } xcb_timestamp_t QXcbConnection::getTimestamp() { // send a dummy event to myself to get the timestamp from X server. xcb_window_t window = rootWindow(); xcb_atom_t dummyAtom = atom(QXcbAtom::Atom_QT_GET_TIMESTAMP); xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, dummyAtom, XCB_ATOM_INTEGER, 32, 0, nullptr); connection()->flush(); xcb_generic_event_t *event = nullptr; // When disconnection is caused by X server, event will never be able to hold // a valid pointer. isConnected(), which calls xcb_connection_has_error(), // can handle this type of disconnection and properly quits the loop. while (isConnected() && !event) { connection()->sync(); event = eventQueue()->peek([window, dummyAtom](xcb_generic_event_t *event, int type) { if (type != XCB_PROPERTY_NOTIFY) return false; auto propertyNotify = reinterpret_cast(event); return propertyNotify->window == window && propertyNotify->atom == dummyAtom; }); } if (!event) { // https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#glossary // > One timestamp value (named CurrentTime) is never generated by the // > server. This value is reserved for use in requests to represent the // > current server time. return XCB_CURRENT_TIME; } xcb_property_notify_event_t *pn = reinterpret_cast(event); xcb_timestamp_t timestamp = pn->time; free(event); return timestamp; } xcb_window_t QXcbConnection::selectionOwner(xcb_atom_t atom) const { auto reply = Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom); if (!reply) { qCDebug(lcQpaXcb) << "failed to query selection owner"; return XCB_NONE; } return reply->owner; } xcb_window_t QXcbConnection::qtSelectionOwner() { if (!m_qtSelectionOwner) { xcb_screen_t *xcbScreen = primaryVirtualDesktop()->screen(); int16_t x = 0, y = 0; uint16_t w = 3, h = 3; m_qtSelectionOwner = xcb_generate_id(xcb_connection()); xcb_create_window(xcb_connection(), XCB_COPY_FROM_PARENT, // depth -- same as root m_qtSelectionOwner, // window id xcbScreen->root, // parent window id x, y, w, h, 0, // border width XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class xcbScreen->root_visual, // visual 0, // value mask nullptr); // value list QXcbWindow::setWindowTitle(connection(), m_qtSelectionOwner, "Qt Selection Owner for "_L1 + QCoreApplication::applicationName()); } return m_qtSelectionOwner; } xcb_window_t QXcbConnection::rootWindow() { QXcbScreen *s = primaryScreen(); return s ? s->root() : 0; } xcb_window_t QXcbConnection::clientLeader() { if (m_clientLeader == 0) { m_clientLeader = xcb_generate_id(xcb_connection()); QXcbScreen *screen = primaryScreen(); xcb_create_window(xcb_connection(), XCB_COPY_FROM_PARENT, m_clientLeader, screen->root(), 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->screen()->root_visual, 0, nullptr); QXcbWindow::setWindowTitle(connection(), m_clientLeader, QGuiApplication::applicationDisplayName()); xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_clientLeader, atom(QXcbAtom::AtomWM_CLIENT_LEADER), XCB_ATOM_WINDOW, 32, 1, &m_clientLeader); #if QT_CONFIG(xcb_sm) // If we are session managed, inform the window manager about it QByteArray session = qGuiApp->sessionId().toLatin1(); if (!session.isEmpty()) { xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_clientLeader, atom(QXcbAtom::AtomSM_CLIENT_ID), XCB_ATOM_STRING, 8, session.size(), session.constData()); } #endif setStartupId(startupId()); } return m_clientLeader; } /*! \internal Compresses events of the same type to avoid swamping the event queue. If event compression is not desired there are several options what developers can do: 1) Write responsive applications. We drop events that have been buffered in the event queue while waiting on unresponsive GUI thread. 2) Use QAbstractNativeEventFilter to get all events from X connection. This is not optimal because it requires working with native event types. 3) Or add public API to Qt for disabling event compression QTBUG-44964 */ bool QXcbConnection::compressEvent(xcb_generic_event_t *event) const { if (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) return false; uint responseType = event->response_type & ~0x80; if (responseType == XCB_MOTION_NOTIFY) { // compress XCB_MOTION_NOTIFY notify events return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch, [](xcb_generic_event_t *, int type) { return type == XCB_MOTION_NOTIFY; }); } // compress XI_* events if (responseType == XCB_GE_GENERIC) { if (!hasXInput2()) return false; // compress XI_Motion if (isXIType(event, XCB_INPUT_MOTION)) { #if QT_CONFIG(tabletevent) auto xdev = reinterpret_cast(event); if (!QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents) && const_cast(this)->tabletDataForDevice(xdev->sourceid)) return false; #endif // QT_CONFIG(tabletevent) return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch, [this](xcb_generic_event_t *next, int) { return isXIType(next, XCB_INPUT_MOTION); }); } // compress XI_TouchUpdate for the same touch point id if (isXIType(event, XCB_INPUT_TOUCH_UPDATE)) { auto touchUpdateEvent = reinterpret_cast(event); uint32_t id = touchUpdateEvent->detail % INT_MAX; return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch, [this, &id](xcb_generic_event_t *next, int) { if (!isXIType(next, XCB_INPUT_TOUCH_UPDATE)) return false; auto touchUpdateNextEvent = reinterpret_cast(next); return id == touchUpdateNextEvent->detail % INT_MAX; }); } return false; } if (responseType == XCB_CONFIGURE_NOTIFY) { // compress multiple configure notify events for the same window return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch, [event](xcb_generic_event_t *next, int type) { if (type != XCB_CONFIGURE_NOTIFY) return false; auto currentEvent = reinterpret_cast(event); auto nextEvent = reinterpret_cast(next); return currentEvent->event == nextEvent->event; }); } return false; } bool QXcbConnection::isUserInputEvent(xcb_generic_event_t *event) const { auto eventType = event->response_type & ~0x80; bool isInputEvent = eventType == XCB_BUTTON_PRESS || eventType == XCB_BUTTON_RELEASE || eventType == XCB_KEY_PRESS || eventType == XCB_KEY_RELEASE || eventType == XCB_MOTION_NOTIFY || eventType == XCB_ENTER_NOTIFY || eventType == XCB_LEAVE_NOTIFY; if (isInputEvent) return true; if (connection()->hasXInput2()) { isInputEvent = isXIType(event, XCB_INPUT_BUTTON_PRESS) || isXIType(event, XCB_INPUT_BUTTON_RELEASE) || isXIType(event, XCB_INPUT_MOTION) || isXIType(event, XCB_INPUT_TOUCH_BEGIN) || isXIType(event, XCB_INPUT_TOUCH_UPDATE) || isXIType(event, XCB_INPUT_TOUCH_END) || isXIType(event, XCB_INPUT_ENTER) || isXIType(event, XCB_INPUT_LEAVE) || // wacom driver's way of reporting tool proximity isXIType(event, XCB_INPUT_PROPERTY); } if (isInputEvent) return true; if (eventType == XCB_CLIENT_MESSAGE) { auto clientMessage = reinterpret_cast(event); if (clientMessage->format == 32 && clientMessage->type == atom(QXcbAtom::AtomWM_PROTOCOLS)) if (clientMessage->data.data32[0] == atom(QXcbAtom::AtomWM_DELETE_WINDOW)) isInputEvent = true; } return isInputEvent; } void QXcbConnection::processXcbEvents(QEventLoop::ProcessEventsFlags flags) { int connection_error = xcb_connection_has_error(xcb_connection()); if (connection_error) { qWarning("The X11 connection broke (error %d). Did the X11 server die?", connection_error); exit(1); } m_eventQueue->flushBufferedEvents(); while (xcb_generic_event_t *event = m_eventQueue->takeFirst(flags)) { QScopedPointer eventGuard(event); if (!(event->response_type & ~0x80)) { handleXcbError(reinterpret_cast(event)); continue; } if (compressEvent(event)) continue; handleXcbEvent(event); // The lock-based solution used to free the lock inside this loop, // hence allowing for more events to arrive. ### Check if we want // this flush here after QTBUG-70095 m_eventQueue->flushBufferedEvents(); } #if QT_CONFIG(xcb_xlib) qt_XFlush(static_cast(xlib_display())); #endif xcb_flush(xcb_connection()); } const xcb_format_t *QXcbConnection::formatForDepth(uint8_t depth) const { xcb_format_iterator_t iterator = xcb_setup_pixmap_formats_iterator(setup()); while (iterator.rem) { xcb_format_t *format = iterator.data; if (format->depth == depth) return format; xcb_format_next(&iterator); } qWarning() << "XCB failed to find an xcb_format_t for depth:" << depth; return nullptr; } void QXcbConnection::sync() { // from xcb_aux_sync xcb_get_input_focus_cookie_t cookie = xcb_get_input_focus(xcb_connection()); free(xcb_get_input_focus_reply(xcb_connection(), cookie, nullptr)); } QXcbSystemTrayTracker *QXcbConnection::systemTrayTracker() const { if (!m_systemTrayTracker) { QXcbConnection *self = const_cast(this); if ((self->m_systemTrayTracker = QXcbSystemTrayTracker::create(self))) { connect(m_systemTrayTracker, SIGNAL(systemTrayWindowChanged(QScreen*)), QGuiApplication::platformNativeInterface(), SIGNAL(systemTrayWindowChanged(QScreen*))); } } return m_systemTrayTracker; } Qt::MouseButtons QXcbConnection::queryMouseButtons() const { int stateMask = 0; QXcbCursor::queryPointer(connection(), nullptr, nullptr, &stateMask); return translateMouseButtons(stateMask); } QXcbGlIntegration *QXcbConnection::glIntegration() const { if (m_glIntegrationInitialized) return m_glIntegration; QStringList glIntegrationNames; glIntegrationNames << QStringLiteral("xcb_glx") << QStringLiteral("xcb_egl"); QString glIntegrationName = QString::fromLocal8Bit(qgetenv("QT_XCB_GL_INTEGRATION")); if (!glIntegrationName.isEmpty()) { qCDebug(lcQpaGl) << "QT_XCB_GL_INTEGRATION is set to" << glIntegrationName; if (glIntegrationName != "none"_L1) { glIntegrationNames.removeAll(glIntegrationName); glIntegrationNames.prepend(glIntegrationName); } else { glIntegrationNames.clear(); } } if (!glIntegrationNames.isEmpty()) { qCDebug(lcQpaGl) << "Choosing xcb gl-integration based on following priority\n" << glIntegrationNames; for (int i = 0; i < glIntegrationNames.size() && !m_glIntegration; i++) { m_glIntegration = QXcbGlIntegrationFactory::create(glIntegrationNames.at(i)); if (m_glIntegration && !m_glIntegration->initialize(const_cast(this))) { qCDebug(lcQpaGl) << "Failed to initialize xcb gl-integration" << glIntegrationNames.at(i); delete m_glIntegration; m_glIntegration = nullptr; } } if (!m_glIntegration) qCDebug(lcQpaGl) << "Failed to create xcb gl-integration"; } m_glIntegrationInitialized = true; return m_glIntegration; } bool QXcbConnection::event(QEvent *e) { if (e->type() == QEvent::User + 1) { QXcbSyncWindowRequest *ev = static_cast(e); QXcbWindow *w = ev->window(); if (w) { w->updateSyncRequestCounter(); ev->invalidate(); } return true; } return QObject::event(e); } void QXcbSyncWindowRequest::invalidate() { if (m_window) { m_window->clearSyncWindowRequest(); m_window = nullptr; } } QXcbConnectionGrabber::QXcbConnectionGrabber(QXcbConnection *connection) :m_connection(connection) { connection->grabServer(); } QXcbConnectionGrabber::~QXcbConnectionGrabber() { if (m_connection) m_connection->ungrabServer(); } void QXcbConnectionGrabber::release() { if (m_connection) { m_connection->ungrabServer(); m_connection = nullptr; } } QT_END_NAMESPACE #include "moc_qxcbconnection.cpp"