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 }