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 }