File indexing completed on 2024-05-05 05:39:02

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