File indexing completed on 2024-05-05 17:45:24

0001 /*
0002     Holds one embedded window, registers as DBus entry
0003     SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
0004     SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "sniproxy.h"
0010 
0011 #include <algorithm>
0012 #include <xcb/xcb_atom.h>
0013 #include <xcb/xcb_event.h>
0014 
0015 #include "debug.h"
0016 #include "xcbutils.h"
0017 
0018 #include <QGuiApplication>
0019 #include <QScreen>
0020 #include <QTimer>
0021 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0022 #include <private/qtx11extras_p.h>
0023 #else
0024 #include <QX11Info>
0025 #endif
0026 
0027 #include <QBitmap>
0028 
0029 #include <KWindowSystem>
0030 #include <netwm.h>
0031 
0032 #include "statusnotifieritemadaptor.h"
0033 #include "statusnotifierwatcher_interface.h"
0034 
0035 #include "../c_ptr.h"
0036 #include "xtestsender.h"
0037 
0038 // #define VISUAL_DEBUG
0039 
0040 #define SNI_WATCHER_SERVICE_NAME "org.kde.StatusNotifierWatcher"
0041 #define SNI_WATCHER_PATH "/StatusNotifierWatcher"
0042 
0043 static uint16_t s_embedSize = 32; // max size of window to embed. We no longer resize the embedded window as Chromium acts stupidly.
0044 static unsigned int XEMBED_VERSION = 0;
0045 
0046 int SNIProxy::s_serviceCount = 0;
0047 
0048 void xembed_message_send(xcb_window_t towin, long message, long d1, long d2, long d3)
0049 {
0050     xcb_client_message_event_t ev;
0051 
0052     ev.response_type = XCB_CLIENT_MESSAGE;
0053     ev.window = towin;
0054     ev.format = 32;
0055     ev.data.data32[0] = XCB_CURRENT_TIME;
0056     ev.data.data32[1] = message;
0057     ev.data.data32[2] = d1;
0058     ev.data.data32[3] = d2;
0059     ev.data.data32[4] = d3;
0060     ev.type = Xcb::atoms->xembedAtom;
0061     xcb_send_event(QX11Info::connection(), false, towin, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
0062 }
0063 
0064 SNIProxy::SNIProxy(xcb_window_t wid, QObject *parent)
0065     : QObject(parent)
0066     ,
0067     // Work round a bug in our SNIWatcher with multiple SNIs per connection.
0068     // there is an undocumented feature that you can register an SNI by path, however it doesn't detect an object on a service being removed, only the entire
0069     // service closing instead lets use one DBus connection per SNI
0070     m_dbus(QDBusConnection::connectToBus(QDBusConnection::SessionBus, QStringLiteral("XembedSniProxy%1").arg(s_serviceCount++)))
0071     , m_windowId(wid)
0072     , sendingClickEvent(false)
0073     , m_injectMode(Direct)
0074 {
0075     // create new SNI
0076     new StatusNotifierItemAdaptor(this);
0077     m_dbus.registerObject(QStringLiteral("/StatusNotifierItem"), this);
0078 
0079     auto statusNotifierWatcher =
0080         new org::kde::StatusNotifierWatcher(QStringLiteral(SNI_WATCHER_SERVICE_NAME), QStringLiteral(SNI_WATCHER_PATH), QDBusConnection::sessionBus(), this);
0081     auto reply = statusNotifierWatcher->RegisterStatusNotifierItem(m_dbus.baseService());
0082     reply.waitForFinished();
0083     if (reply.isError()) {
0084         qCWarning(SNIPROXY) << "could not register SNI:" << reply.error().message();
0085     }
0086 
0087     auto c = QX11Info::connection();
0088 
0089     // create a container window
0090     auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
0091     m_containerWid = xcb_generate_id(c);
0092     uint32_t values[3];
0093     uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
0094     values[0] = screen->black_pixel; // draw a solid background so the embedded icon doesn't get garbage in it
0095     values[1] = true; // bypass wM
0096     values[2] = XCB_EVENT_MASK_VISIBILITY_CHANGE | // receive visibility change, to handle KWin restart #357443
0097                                                    // Redirect and handle structure (size, position) requests from the embedded window.
0098         XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
0099     xcb_create_window(c, /* connection    */
0100                       XCB_COPY_FROM_PARENT, /* depth         */
0101                       m_containerWid, /* window Id     */
0102                       screen->root, /* parent window */
0103                       0,
0104                       0, /* x, y          */
0105                       s_embedSize,
0106                       s_embedSize, /* width, height */
0107                       0, /* border_width  */
0108                       XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class         */
0109                       screen->root_visual, /* visual        */
0110                       mask,
0111                       values); /* masks         */
0112 
0113     /*
0114         We need the window to exist and be mapped otherwise the child won't render it's contents
0115 
0116         We also need it to exist in the right place to get the clicks working as GTK will check sendEvent locations to see if our window is in the right place.
0117        So even though our contents are drawn via compositing we still put this window in the right place
0118 
0119         We can't composite it away anything parented owned by the root window (apparently)
0120         Stack Under works in the non composited case, but it doesn't seem to work in kwin's composited case (probably need set relevant NETWM hint)
0121 
0122         As a last resort set opacity to 0 just to make sure this container never appears
0123     */
0124 
0125 #ifndef VISUAL_DEBUG
0126     stackContainerWindow(XCB_STACK_MODE_BELOW);
0127 
0128     NETWinInfo wm(c, m_containerWid, screen->root, NET::Properties(), NET::Properties2());
0129     wm.setOpacity(0);
0130 #endif
0131 
0132     xcb_flush(c);
0133 
0134     xcb_map_window(c, m_containerWid);
0135 
0136     xcb_reparent_window(c, wid, m_containerWid, 0, 0);
0137 
0138     /*
0139      * Render the embedded window offscreen
0140      */
0141     xcb_composite_redirect_window(c, wid, XCB_COMPOSITE_REDIRECT_MANUAL);
0142 
0143     /* we grab the window, but also make sure it's automatically reparented back
0144      * to the root window if we should die.
0145      */
0146     xcb_change_save_set(c, XCB_SET_MODE_INSERT, wid);
0147 
0148     // tell client we're embedding it
0149     xembed_message_send(wid, XEMBED_EMBEDDED_NOTIFY, 0, m_containerWid, XEMBED_VERSION);
0150 
0151     // move window we're embedding
0152     const uint32_t windowMoveConfigVals[2] = {0, 0};
0153 
0154     xcb_configure_window(c, wid, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, windowMoveConfigVals);
0155 
0156     QSize clientWindowSize = calculateClientWindowSize();
0157 
0158     // show the embedded window otherwise nothing happens
0159     xcb_map_window(c, wid);
0160 
0161     xcb_clear_area(c, 0, wid, 0, 0, clientWindowSize.width(), clientWindowSize.height());
0162 
0163     xcb_flush(c);
0164 
0165     // guess which input injection method to use
0166     // we can either send an X event to the client or XTest
0167     // some don't support direct X events (GTK3/4), and some don't support XTest because reasons
0168     // note also some clients might not have the XTest extension. We may as well assume it does and just fail to send later.
0169 
0170     // we query if the client selected button presses in the event mask
0171     // if the client does supports that we send directly, otherwise we'll use xtest
0172     auto waCookie = xcb_get_window_attributes(c, wid);
0173     UniqueCPointer<xcb_get_window_attributes_reply_t> windowAttributes(xcb_get_window_attributes_reply(c, waCookie, nullptr));
0174     if (windowAttributes && !(windowAttributes->all_event_masks & XCB_EVENT_MASK_BUTTON_PRESS)) {
0175         m_injectMode = XTest;
0176     }
0177 
0178     // there's no damage event for the first paint, and sometimes it's not drawn immediately
0179     // not ideal, but it works better than nothing
0180     // test with xchat before changing
0181     QTimer::singleShot(500, this, &SNIProxy::update);
0182 }
0183 
0184 SNIProxy::~SNIProxy()
0185 {
0186     auto c = QX11Info::connection();
0187 
0188     xcb_destroy_window(c, m_containerWid);
0189     QDBusConnection::disconnectFromBus(m_dbus.name());
0190 }
0191 
0192 void SNIProxy::update()
0193 {
0194     const QImage image = getImageNonComposite();
0195     if (image.isNull()) {
0196         qCDebug(SNIPROXY) << "No xembed icon for" << m_windowId << Title();
0197         return;
0198     }
0199 
0200     int w = image.width();
0201     int h = image.height();
0202 
0203     m_pixmap = QPixmap::fromImage(image);
0204     if (w > s_embedSize || h > s_embedSize) {
0205         qCDebug(SNIPROXY) << "Scaling pixmap of window" << m_windowId << Title() << "from w*h" << w << h;
0206         m_pixmap = m_pixmap.scaled(s_embedSize, s_embedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0207     }
0208     Q_EMIT NewIcon();
0209     Q_EMIT NewToolTip();
0210 }
0211 
0212 void SNIProxy::resizeWindow(const uint16_t width, const uint16_t height) const
0213 {
0214     auto connection = QX11Info::connection();
0215 
0216     uint16_t widthNormalized = std::min(width, s_embedSize);
0217     uint16_t heighNormalized = std::min(height, s_embedSize);
0218 
0219     const uint32_t windowSizeConfigVals[2] = {widthNormalized, heighNormalized};
0220     xcb_configure_window(connection, m_windowId, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, windowSizeConfigVals);
0221 
0222     xcb_flush(connection);
0223 }
0224 
0225 void SNIProxy::hideContainerWindow(xcb_window_t windowId) const
0226 {
0227     if (m_containerWid == windowId && !sendingClickEvent) {
0228         qDebug() << "Container window visible, stack below";
0229         stackContainerWindow(XCB_STACK_MODE_BELOW);
0230     }
0231 }
0232 
0233 QSize SNIProxy::calculateClientWindowSize() const
0234 {
0235     auto c = QX11Info::connection();
0236 
0237     auto cookie = xcb_get_geometry(c, m_windowId);
0238     UniqueCPointer<xcb_get_geometry_reply_t> clientGeom(xcb_get_geometry_reply(c, cookie, nullptr));
0239 
0240     QSize clientWindowSize;
0241     if (clientGeom) {
0242         clientWindowSize = QSize(clientGeom->width, clientGeom->height);
0243     }
0244     // if the window is a clearly stupid size resize to be something sensible
0245     // this is needed as chromium and such when resized just fill the icon with transparent space and only draw in the middle
0246     // however KeePass2 does need this as by default the window size is 273px wide and is not transparent
0247     // use an arbitrary heuristic to make sure icons are always sensible
0248     if (clientWindowSize.isEmpty() || clientWindowSize.width() > s_embedSize || clientWindowSize.height() > s_embedSize) {
0249         qCDebug(SNIPROXY) << "Resizing window" << m_windowId << Title() << "from w*h" << clientWindowSize;
0250 
0251         resizeWindow(s_embedSize, s_embedSize);
0252 
0253         clientWindowSize = QSize(s_embedSize, s_embedSize);
0254     }
0255 
0256     return clientWindowSize;
0257 }
0258 
0259 void sni_cleanup_xcb_image(void *data)
0260 {
0261     xcb_image_destroy(static_cast<xcb_image_t *>(data));
0262 }
0263 
0264 bool SNIProxy::isTransparentImage(const QImage &image) const
0265 {
0266     int w = image.width();
0267     int h = image.height();
0268 
0269     // check for the center and sub-center pixels first and avoid full image scan
0270     if (!(qAlpha(image.pixel(w >> 1, h >> 1)) + qAlpha(image.pixel(w >> 2, h >> 2)) == 0))
0271         return false;
0272 
0273     // skip scan altogether if sub-center pixel found to be opaque
0274     // and break out from the outer loop too on full scan
0275     for (int x = 0; x < w; ++x) {
0276         for (int y = 0; y < h; ++y) {
0277             if (qAlpha(image.pixel(x, y))) {
0278                 // Found an opaque pixel.
0279                 return false;
0280             }
0281         }
0282     }
0283 
0284     return true;
0285 }
0286 
0287 QImage SNIProxy::getImageNonComposite() const
0288 {
0289     auto c = QX11Info::connection();
0290 
0291     QSize clientWindowSize = calculateClientWindowSize();
0292 
0293     xcb_image_t *image = xcb_image_get(c, m_windowId, 0, 0, clientWindowSize.width(), clientWindowSize.height(), 0xFFFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP);
0294 
0295     // Don't hook up cleanup yet, we may use a different QImage after all
0296     QImage naiveConversion;
0297     if (image) {
0298         naiveConversion = QImage(image->data, image->width, image->height, QImage::Format_ARGB32);
0299     } else {
0300         qCDebug(SNIPROXY) << "Skip NULL image returned from xcb_image_get() for" << m_windowId << Title();
0301         return QImage();
0302     }
0303 
0304     if (isTransparentImage(naiveConversion)) {
0305         QImage elaborateConversion = QImage(convertFromNative(image));
0306 
0307         // Update icon only if it is at least partially opaque.
0308         // This is just a workaround for X11 bug: xembed icon may suddenly
0309         // become transparent for a one or few frames. Reproducible at least
0310         // with WINE applications.
0311         if (isTransparentImage(elaborateConversion)) {
0312             qCDebug(SNIPROXY) << "Skip transparent xembed icon for" << m_windowId << Title();
0313             return QImage();
0314         } else
0315             return elaborateConversion;
0316     } else {
0317         // Now we are sure we can eventually delete the xcb_image_t with this version
0318         return QImage(image->data, image->width, image->height, image->stride, QImage::Format_ARGB32, sni_cleanup_xcb_image, image);
0319     }
0320 }
0321 
0322 QImage SNIProxy::convertFromNative(xcb_image_t *xcbImage) const
0323 {
0324     QImage::Format format = QImage::Format_Invalid;
0325 
0326     switch (xcbImage->depth) {
0327     case 1:
0328         format = QImage::Format_MonoLSB;
0329         break;
0330     case 16:
0331         format = QImage::Format_RGB16;
0332         break;
0333     case 24:
0334         format = QImage::Format_RGB32;
0335         break;
0336     case 30: {
0337         // Qt doesn't have a matching image format. We need to convert manually
0338         quint32 *pixels = reinterpret_cast<quint32 *>(xcbImage->data);
0339         for (uint i = 0; i < (xcbImage->size / 4); i++) {
0340             int r = (pixels[i] >> 22) & 0xff;
0341             int g = (pixels[i] >> 12) & 0xff;
0342             int b = (pixels[i] >> 2) & 0xff;
0343 
0344             pixels[i] = qRgba(r, g, b, 0xff);
0345         }
0346         // fall through, Qt format is still Format_ARGB32_Premultiplied
0347         Q_FALLTHROUGH();
0348     }
0349     case 32:
0350         format = QImage::Format_ARGB32_Premultiplied;
0351         break;
0352     default:
0353         return QImage(); // we don't know
0354     }
0355 
0356     QImage image(xcbImage->data, xcbImage->width, xcbImage->height, xcbImage->stride, format, sni_cleanup_xcb_image, xcbImage);
0357 
0358     if (image.isNull()) {
0359         return QImage();
0360     }
0361 
0362     if (format == QImage::Format_RGB32 && xcbImage->bpp == 32) {
0363         QImage m = image.createHeuristicMask();
0364         QBitmap mask(QPixmap::fromImage(m));
0365         QPixmap p = QPixmap::fromImage(image);
0366         p.setMask(mask);
0367         image = p.toImage();
0368     }
0369 
0370     // work around an abort in QImage::color
0371     if (image.format() == QImage::Format_MonoLSB) {
0372         image.setColorCount(2);
0373         image.setColor(0, QColor(Qt::white).rgb());
0374         image.setColor(1, QColor(Qt::black).rgb());
0375     }
0376 
0377     return image;
0378 }
0379 
0380 /*
0381   Wine is using XWindow Shape Extension for transparent tray icons.
0382   We need to find first clickable point starting from top-left.
0383 */
0384 QPoint SNIProxy::calculateClickPoint() const
0385 {
0386     QPoint clickPoint = QPoint(0, 0);
0387 
0388     auto c = QX11Info::connection();
0389 
0390     // request extent to check if shape has been set
0391     xcb_shape_query_extents_cookie_t extentsCookie = xcb_shape_query_extents(c, m_windowId);
0392     // at the same time make the request for rectangles (even if this request isn't needed)
0393     xcb_shape_get_rectangles_cookie_t rectaglesCookie = xcb_shape_get_rectangles(c, m_windowId, XCB_SHAPE_SK_BOUNDING);
0394 
0395     UniqueCPointer<xcb_shape_query_extents_reply_t> extentsReply(xcb_shape_query_extents_reply(c, extentsCookie, nullptr));
0396     UniqueCPointer<xcb_shape_get_rectangles_reply_t> rectanglesReply(xcb_shape_get_rectangles_reply(c, rectaglesCookie, nullptr));
0397 
0398     if (!extentsReply || !rectanglesReply || !extentsReply->bounding_shaped) {
0399         return clickPoint;
0400     }
0401 
0402     xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(rectanglesReply.get());
0403     if (!rectangles) {
0404         return clickPoint;
0405     }
0406 
0407     const QImage image = getImageNonComposite();
0408 
0409     double minLength = sqrt(pow(image.height(), 2) + pow(image.width(), 2));
0410     const int nRectangles = xcb_shape_get_rectangles_rectangles_length(rectanglesReply.get());
0411     for (int i = 0; i < nRectangles; ++i) {
0412         double length = sqrt(pow(rectangles[i].x, 2) + pow(rectangles[i].y, 2));
0413         if (length < minLength) {
0414             minLength = length;
0415             clickPoint = QPoint(rectangles[i].x, rectangles[i].y);
0416         }
0417     }
0418 
0419     qCDebug(SNIPROXY) << "Click point:" << clickPoint;
0420     return clickPoint;
0421 }
0422 
0423 void SNIProxy::stackContainerWindow(const uint32_t stackMode) const
0424 {
0425     auto c = QX11Info::connection();
0426     const uint32_t stackData[] = {stackMode};
0427     xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_STACK_MODE, stackData);
0428 }
0429 
0430 //____________properties__________
0431 
0432 QString SNIProxy::Category() const
0433 {
0434     return QStringLiteral("ApplicationStatus");
0435 }
0436 
0437 QString SNIProxy::Id() const
0438 {
0439     const auto title = Title();
0440     // we always need /some/ ID so if no window title exists, just use the winId.
0441     if (title.isEmpty()) {
0442         return QString::number(m_windowId);
0443     }
0444     return title;
0445 }
0446 
0447 KDbusImageVector SNIProxy::IconPixmap() const
0448 {
0449     KDbusImageStruct dbusImage(m_pixmap.toImage());
0450     return KDbusImageVector() << dbusImage;
0451 }
0452 
0453 bool SNIProxy::ItemIsMenu() const
0454 {
0455     return false;
0456 }
0457 
0458 QString SNIProxy::Status() const
0459 {
0460     return QStringLiteral("Active");
0461 }
0462 
0463 QString SNIProxy::Title() const
0464 {
0465     KWindowInfo window(m_windowId, NET::WMName);
0466     return window.name();
0467 }
0468 
0469 int SNIProxy::WindowId() const
0470 {
0471     return m_windowId;
0472 }
0473 
0474 //____________actions_____________
0475 
0476 void SNIProxy::Activate(int x, int y)
0477 {
0478     sendClick(XCB_BUTTON_INDEX_1, x, y);
0479 }
0480 
0481 void SNIProxy::SecondaryActivate(int x, int y)
0482 {
0483     sendClick(XCB_BUTTON_INDEX_2, x, y);
0484 }
0485 
0486 void SNIProxy::ContextMenu(int x, int y)
0487 {
0488     sendClick(XCB_BUTTON_INDEX_3, x, y);
0489 }
0490 
0491 void SNIProxy::Scroll(int delta, const QString &orientation)
0492 {
0493     if (orientation == QLatin1String("vertical")) {
0494         sendClick(delta > 0 ? XCB_BUTTON_INDEX_4 : XCB_BUTTON_INDEX_5, 0, 0);
0495     } else {
0496         sendClick(delta > 0 ? 6 : 7, 0, 0);
0497     }
0498 }
0499 
0500 void SNIProxy::sendClick(uint8_t mouseButton, int x, int y)
0501 {
0502     // it's best not to look at this code
0503     // GTK doesn't like send_events and double checks the mouse position matches where the window is and is top level
0504     // in order to solve this we move the embed container over to where the mouse is then replay the event using send_event
0505     // if patching, test with xchat + xchat context menus
0506 
0507     // note x,y are not actually where the mouse is, but the plasmoid
0508     // ideally we should make this match the plasmoid hit area
0509 
0510     qCDebug(SNIPROXY) << "Received click" << mouseButton << "with passed x*y" << x << y;
0511     sendingClickEvent = true;
0512 
0513     auto c = QX11Info::connection();
0514 
0515     auto cookieSize = xcb_get_geometry(c, m_windowId);
0516     UniqueCPointer<xcb_get_geometry_reply_t> clientGeom(xcb_get_geometry_reply(c, cookieSize, nullptr));
0517 
0518     if (!clientGeom) {
0519         return;
0520     }
0521 
0522     /*qCDebug(SNIPROXY) << "samescreen" << pointer->same_screen << endl
0523     << "root x*y" << pointer->root_x << pointer->root_y << endl
0524     << "win x*y" << pointer->win_x << pointer->win_y;*/
0525 
0526     // move our window so the mouse is within its geometry
0527     uint32_t configVals[2] = {0, 0};
0528     const QPoint clickPoint = calculateClickPoint();
0529     if (mouseButton >= XCB_BUTTON_INDEX_4) {
0530         // scroll event, take pointer position
0531         auto cookie = xcb_query_pointer(c, m_windowId);
0532         UniqueCPointer<xcb_query_pointer_reply_t> pointer(xcb_query_pointer_reply(c, cookie, nullptr));
0533         configVals[0] = pointer->root_x;
0534         configVals[1] = pointer->root_y;
0535     } else {
0536         configVals[0] = static_cast<uint32_t>(x - clickPoint.x());
0537         configVals[1] = static_cast<uint32_t>(y - clickPoint.y());
0538     }
0539     xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, configVals);
0540 
0541     // pull window up
0542     stackContainerWindow(XCB_STACK_MODE_ABOVE);
0543 
0544     // mouse down
0545     if (m_injectMode == Direct) {
0546         xcb_button_press_event_t *event = new xcb_button_press_event_t;
0547         memset(event, 0x00, sizeof(xcb_button_press_event_t));
0548         event->response_type = XCB_BUTTON_PRESS;
0549         event->event = m_windowId;
0550         event->time = QX11Info::getTimestamp();
0551         event->same_screen = 1;
0552         event->root = QX11Info::appRootWindow();
0553         event->root_x = x;
0554         event->root_y = y;
0555         event->event_x = static_cast<int16_t>(clickPoint.x());
0556         event->event_y = static_cast<int16_t>(clickPoint.y());
0557         event->child = 0;
0558         event->state = 0;
0559         event->detail = mouseButton;
0560 
0561         xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_PRESS, (char *)event);
0562         delete event;
0563     } else {
0564         sendXTestPressed(QX11Info::display(), mouseButton);
0565     }
0566 
0567     // mouse up
0568     if (m_injectMode == Direct) {
0569         xcb_button_release_event_t *event = new xcb_button_release_event_t;
0570         memset(event, 0x00, sizeof(xcb_button_release_event_t));
0571         event->response_type = XCB_BUTTON_RELEASE;
0572         event->event = m_windowId;
0573         event->time = QX11Info::getTimestamp();
0574         event->same_screen = 1;
0575         event->root = QX11Info::appRootWindow();
0576         event->root_x = x;
0577         event->root_y = y;
0578         event->event_x = static_cast<int16_t>(clickPoint.x());
0579         event->event_y = static_cast<int16_t>(clickPoint.y());
0580         event->child = 0;
0581         event->state = 0;
0582         event->detail = mouseButton;
0583 
0584         xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_RELEASE, (char *)event);
0585         delete event;
0586     } else {
0587         sendXTestReleased(QX11Info::display(), mouseButton);
0588     }
0589 
0590 #ifndef VISUAL_DEBUG
0591     stackContainerWindow(XCB_STACK_MODE_BELOW);
0592 #endif
0593 
0594     sendingClickEvent = false;
0595 }