| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the plugins of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtGui/private/qguiapplication_p.h> |
| #include <QtCore/QDebug> |
| #include <QtCore/QCoreApplication> |
| |
| #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 <QAbstractEventDispatcher> |
| #include <QByteArray> |
| #include <QScopedPointer> |
| |
| #include <stdio.h> |
| #include <errno.h> |
| |
| #include <xcb/xfixes.h> |
| #if QT_CONFIG(xkb) |
| #define explicit dont_use_cxx_explicit |
| #include <xcb/xkb.h> |
| #undef explicit |
| #endif |
| #if QT_CONFIG(xcb_xinput) |
| #include <xcb/xinput.h> |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| 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") |
| |
| // this event type was added in libxcb 1.10, |
| // but we support also older version |
| #ifndef XCB_GE_GENERIC |
| #define XCB_GE_GENERIC 35 |
| #endif |
| |
| QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGrabServer, xcb_visualid_t defaultVisualId, const char *displayName) |
| : QXcbBasicConnection(displayName) |
| , m_canGrabServer(canGrabServer) |
| , m_defaultVisualId(defaultVisualId) |
| , m_nativeInterface(nativeInterface) |
| { |
| if (!isConnected()) |
| return; |
| |
| m_eventQueue = new QXcbEventQueue(this); |
| |
| m_xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP").toLower(); |
| |
| if (hasXRandr()) |
| xrandrSelectEvents(); |
| |
| initializeScreens(); |
| |
| #if QT_CONFIG(xcb_xinput) |
| if (hasXInput2()) { |
| xi2SetupDevices(); |
| xi2SelectStateEvents(); |
| } |
| #endif |
| |
| 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 |
| |
| m_startupId = qgetenv("DESKTOP_STARTUP_ID"); |
| if (!m_startupId.isNull()) |
| qunsetenv("DESKTOP_STARTUP_ID"); |
| |
| const int focusInDelay = 100; |
| m_focusInTimer.setSingleShot(true); |
| m_focusInTimer.setInterval(focusInDelay); |
| m_focusInTimer.callOnTimeout([]() { |
| // No FocusIn events for us, proceed with FocusOut normally. |
| QWindowSystemInterface::handleWindowActivated(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, 0); |
| } |
| |
| QXcbWindow *QXcbConnection::platformWindowFromId(xcb_window_t id) |
| { |
| QXcbWindowEventListener *listener = m_mapper.value(id, 0); |
| if (listener) |
| return listener->toWindow(); |
| return 0; |
| } |
| |
| #define HANDLE_PLATFORM_WINDOW_EVENT(event_t, windowMember, handler) \ |
| { \ |
| auto e = reinterpret_cast<event_t *>(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_t *>(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); |
| |
| 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_PRINT_AND_RETURN( XCB_GE_GENERIC ); |
| } |
| // 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) |
| { |
| #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| qintptr result = 0; |
| #else |
| long result = 0; |
| #endif |
| 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<uint>(error->error_code, (sizeof(xcb_errors) / sizeof(xcb_errors[0])) - 1); |
| uint clamped_major_code = qMin<uint>(error->major_code, (sizeof(xcb_protocol_request_codes) / sizeof(xcb_protocol_request_codes[0])) - 1); |
| |
| qCWarning(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 = 0; |
| if (s & XCB_BUTTON_MASK_1) |
| ret |= Qt::LeftButton; |
| if (s & XCB_BUTTON_MASK_2) |
| ret |= Qt::MidButton; |
| 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::MidButton; |
| 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; |
| } |
| } |
| |
| #if QT_CONFIG(xkb) |
| 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; |
| } |
| #endif |
| |
| void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) |
| { |
| if (Q_UNLIKELY(lcQpaEvents().isDebugEnabled())) |
| printXcbEvent(lcQpaEvents(), "Event", event); |
| |
| #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| qintptr result = 0; // Used only by MS Windows |
| #else |
| long result = 0; // Used only by MS Windows |
| #endif |
| 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<xcb_button_press_event_t *>(event); |
| 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<unsigned int>(m_buttonState)); |
| HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_press_event_t, event, handleButtonPressEvent); |
| } |
| case XCB_BUTTON_RELEASE: { |
| auto ev = reinterpret_cast<xcb_button_release_event_t *>(event); |
| 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<unsigned int>(m_buttonState)); |
| HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_release_event_t, event, handleButtonReleaseEvent); |
| } |
| case XCB_MOTION_NOTIFY: { |
| auto ev = reinterpret_cast<xcb_motion_notify_event_t *>(event); |
| 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<unsigned int>(m_buttonState)); |
| HANDLE_PLATFORM_WINDOW_EVENT(xcb_motion_notify_event_t, event, handleMotionNotifyEvent); |
| } |
| case XCB_CONFIGURE_NOTIFY: |
| 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<xcb_client_message_event_t *>(event); |
| if (clientMessage->format != 32) |
| return; |
| #if QT_CONFIG(draganddrop) |
| if (clientMessage->type == atom(QXcbAtom::XdndStatus)) |
| drag()->handleStatus(clientMessage); |
| else if (clientMessage->type == atom(QXcbAtom::XdndFinished)) |
| drag()->handleFinished(clientMessage); |
| #endif |
| if (m_systemTrayTracker && clientMessage->type == atom(QXcbAtom::MANAGER)) |
| m_systemTrayTracker->notifyManagerClientMessageEvent(clientMessage); |
| HANDLE_PLATFORM_WINDOW_EVENT(xcb_client_message_event_t, window, handleClientMessageEvent); |
| } |
| case XCB_ENTER_NOTIFY: |
| #if QT_CONFIG(xcb_xinput) |
| if (hasXInput2() && !xi2MouseEventsDisabled()) |
| break; |
| #endif |
| HANDLE_PLATFORM_WINDOW_EVENT(xcb_enter_notify_event_t, event, handleEnterNotifyEvent); |
| case XCB_LEAVE_NOTIFY: |
| #if QT_CONFIG(xcb_xinput) |
| if (hasXInput2() && !xi2MouseEventsDisabled()) |
| break; |
| #endif |
| m_keyboard->updateXKBStateFromCore(reinterpret_cast<xcb_leave_notify_event_t *>(event)->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<xcb_key_press_event_t *>(event); |
| m_keyboard->updateXKBStateFromCore(keyPress->state); |
| setTime(keyPress->time); |
| HANDLE_KEYBOARD_EVENT(xcb_key_press_event_t, handleKeyPressEvent); |
| } |
| case XCB_KEY_RELEASE: |
| m_keyboard->updateXKBStateFromCore(reinterpret_cast<xcb_key_release_event_t *>(event)->state); |
| HANDLE_KEYBOARD_EVENT(xcb_key_release_event_t, handleKeyReleaseEvent); |
| case XCB_MAPPING_NOTIFY: |
| m_keyboard->updateKeymap(reinterpret_cast<xcb_mapping_notify_event_t *>(event)); |
| break; |
| case XCB_SELECTION_REQUEST: |
| { |
| #if QT_CONFIG(draganddrop) || QT_CONFIG(clipboard) |
| auto selectionRequest = reinterpret_cast<xcb_selection_request_event_t *>(event); |
| #endif |
| #if QT_CONFIG(draganddrop) |
| if (selectionRequest->selection == atom(QXcbAtom::XdndSelection)) |
| m_drag->handleSelectionRequest(selectionRequest); |
| else |
| #endif |
| { |
| #ifndef QT_NO_CLIPBOARD |
| m_clipboard->handleSelectionRequest(selectionRequest); |
| #endif |
| } |
| break; |
| } |
| case XCB_SELECTION_CLEAR: |
| setTime((reinterpret_cast<xcb_selection_clear_event_t *>(event))->time); |
| #ifndef QT_NO_CLIPBOARD |
| m_clipboard->handleSelectionClearRequest(reinterpret_cast<xcb_selection_clear_event_t *>(event)); |
| #endif |
| break; |
| case XCB_SELECTION_NOTIFY: |
| setTime((reinterpret_cast<xcb_selection_notify_event_t *>(event))->time); |
| break; |
| case XCB_PROPERTY_NOTIFY: |
| { |
| #ifndef QT_NO_CLIPBOARD |
| if (m_clipboard->handlePropertyNotify(event)) |
| break; |
| #endif |
| auto propertyNotify = reinterpret_cast<xcb_property_notify_event_t *>(event); |
| if (propertyNotify->atom == atom(QXcbAtom::_NET_WORKAREA)) { |
| QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(propertyNotify->window); |
| if (virtualDesktop) |
| virtualDesktop->updateWorkArea(); |
| } else { |
| HANDLE_PLATFORM_WINDOW_EVENT(xcb_property_notify_event_t, window, handlePropertyNotifyEvent); |
| } |
| break; |
| } |
| #if QT_CONFIG(xcb_xinput) |
| case XCB_GE_GENERIC: |
| // Here the windowEventListener is invoked from xi2HandleEvent() |
| if (hasXInput2() && isXIEvent(event)) |
| xi2HandleEvent(reinterpret_cast<xcb_ge_event_t *>(event)); |
| break; |
| #endif |
| 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<xcb_xfixes_selection_notify_event_t *>(event); |
| setTime(notify_event->timestamp); |
| #ifndef QT_NO_CLIPBOARD |
| m_clipboard->handleXFixesSelectionRequest(notify_event); |
| #endif |
| for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops)) |
| virtualDesktop->handleXFixesSelectionNotify(notify_event); |
| } else if (isXRandrType(response_type, XCB_RANDR_NOTIFY)) { |
| updateScreens(reinterpret_cast<xcb_randr_notify_event_t *>(event)); |
| } else if (isXRandrType(response_type, XCB_RANDR_SCREEN_CHANGE_NOTIFY)) { |
| auto change_event = reinterpret_cast<xcb_randr_screen_change_notify_event_t *>(event); |
| if (auto virtualDesktop = virtualDesktopForRootWindow(change_event->root)) |
| virtualDesktop->handleScreenChange(change_event); |
| #if QT_CONFIG(xkb) |
| } 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 redundent 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; |
| } |
| } |
| #endif |
| } 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<QXcbWindow *>(w->handle()) : nullptr; |
| } |
| void QXcbConnection::setMouseGrabber(QXcbWindow *w) |
| { |
| m_mouseGrabber = w; |
| m_mousePressWindow = nullptr; |
| } |
| void QXcbConnection::setMousePressWindow(QXcbWindow *w) |
| { |
| m_mousePressWindow = w; |
| } |
| |
| void QXcbConnection::grabServer() |
| { |
| if (m_canGrabServer) |
| xcb_grab_server(xcb_connection()); |
| } |
| |
| void QXcbConnection::ungrabServer() |
| { |
| if (m_canGrabServer) |
| xcb_ungrab_server(xcb_connection()); |
| } |
| |
| 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::CLIP_TEMPORARY); |
| xcb_change_property(xcb_connection(), XCB_PROP_MODE_APPEND, window, dummyAtom, |
| XCB_ATOM_INTEGER, 32, 0, nullptr); |
| |
| connection()->flush(); |
| |
| xcb_generic_event_t *event = nullptr; |
| |
| while (!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<xcb_property_notify_event_t *>(event); |
| return propertyNotify->window == window && propertyNotify->atom == dummyAtom; |
| }); |
| } |
| |
| xcb_property_notify_event_t *pn = reinterpret_cast<xcb_property_notify_event_t *>(event); |
| xcb_timestamp_t timestamp = pn->time; |
| free(event); |
| |
| xcb_delete_property(xcb_connection(), window, dummyAtom); |
| |
| return timestamp; |
| } |
| |
| xcb_window_t QXcbConnection::getSelectionOwner(xcb_atom_t atom) const |
| { |
| return Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom)->owner; |
| } |
| |
| xcb_window_t QXcbConnection::getQtSelectionOwner() |
| { |
| 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 |
| 0); // value list |
| |
| QXcbWindow::setWindowTitle(connection(), m_qtSelectionOwner, |
| QLatin1String("Qt Selection Owner for ") + 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, 0); |
| |
| |
| QXcbWindow::setWindowTitle(connection(), m_clientLeader, |
| QStringLiteral("Qt Client Leader Window")); |
| |
| xcb_change_property(xcb_connection(), |
| XCB_PROP_MODE_REPLACE, |
| m_clientLeader, |
| atom(QXcbAtom::WM_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::SM_CLIENT_ID), |
| XCB_ATOM_STRING, |
| 8, |
| session.length(), |
| session.constData()); |
| } |
| #endif |
| } |
| 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; |
| }); |
| } |
| |
| #if QT_CONFIG(xcb_xinput) |
| // 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<xcb_input_motion_event_t *>(event); |
| if (!QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents) && |
| const_cast<QXcbConnection *>(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<xcb_input_touch_update_event_t *>(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<xcb_input_touch_update_event_t *>(next); |
| return id == touchUpdateNextEvent->detail % INT_MAX; |
| }); |
| } |
| |
| return false; |
| } |
| #endif |
| |
| 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<xcb_configure_notify_event_t *>(event); |
| auto nextEvent = reinterpret_cast<xcb_configure_notify_event_t *>(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 QT_CONFIG(xcb_xinput) |
| 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; |
| #endif |
| |
| if (eventType == XCB_CLIENT_MESSAGE) { |
| auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event); |
| if (clientMessage->format == 32 && clientMessage->type == atom(QXcbAtom::WM_PROTOCOLS)) |
| if (clientMessage->data.data32[0] == atom(QXcbAtom::WM_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<xcb_generic_event_t, QScopedPointerPodDeleter> eventGuard(event); |
| |
| if (!(event->response_type & ~0x80)) { |
| handleXcbError(reinterpret_cast<xcb_generic_error_t *>(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(); |
| } |
| |
| 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, 0)); |
| } |
| |
| QXcbSystemTrayTracker *QXcbConnection::systemTrayTracker() const |
| { |
| if (!m_systemTrayTracker) { |
| QXcbConnection *self = const_cast<QXcbConnection *>(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(), 0, 0, &stateMask); |
| return translateMouseButtons(stateMask); |
| } |
| |
| Qt::KeyboardModifiers QXcbConnection::queryKeyboardModifiers() const |
| { |
| int stateMask = 0; |
| QXcbCursor::queryPointer(connection(), 0, 0, &stateMask); |
| return keyboard()->translateModifiers(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 != QLatin1String("none")) { |
| 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<QXcbConnection *>(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<QXcbSyncWindowRequest *>(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 = 0; |
| } |
| } |
| |
| 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 = 0; |
| } |
| } |
| |
| QT_END_NAMESPACE |