File indexing completed on 2025-02-02 04:26:13

0001 /* This file is part of Spectacle, the KDE screenshot utility
0002  * SPDX-FileCopyrightText: 2019 Boudhayan Gupta <bgupta@kde.org>
0003 
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "ImagePlatformXcb.h"
0008 
0009 #include <xcb/randr.h>
0010 #include <xcb/xcb_cursor.h>
0011 #include <xcb/xcb_util.h>
0012 #include <xcb/xfixes.h>
0013 
0014 #include <X11/Xatom.h>
0015 #include <X11/Xdefs.h>
0016 
0017 #include <QAbstractNativeEventFilter>
0018 #include <QApplication>
0019 #include <QDBusConnection>
0020 #include <QDBusConnectionInterface>
0021 #include <QDBusInterface>
0022 #include <QGraphicsDropShadowEffect>
0023 #include <QGraphicsPixmapItem>
0024 #include <QGraphicsScene>
0025 #include <QPainter>
0026 #include <QScreen>
0027 #include <QSet>
0028 #include <QStack>
0029 #include <QTimer>
0030 #include <private/qtx11extras_p.h>
0031 #include <QtMath>
0032 
0033 #include <KWindowInfo>
0034 #include <KWindowSystem>
0035 #include <KX11Extras>
0036 
0037 using namespace Qt::StringLiterals;
0038 
0039 #include <memory>
0040 
0041 /* -- XCB Image Smart Pointer ------------------------------------------------------------------ */
0042 
0043 struct XcbImagePtrDeleter {
0044     void operator()(xcb_image_t *xcbImage) const
0045     {
0046         if (xcbImage) {
0047             xcb_image_destroy(xcbImage);
0048         }
0049     }
0050 };
0051 using XcbImagePtr = std::unique_ptr<xcb_image_t, XcbImagePtrDeleter>;
0052 
0053 struct CFreeDeleter {
0054     void operator()(void *ptr) const
0055     {
0056         free(ptr);
0057     }
0058 };
0059 template<typename Reply>
0060 using XcbReplyPtr = std::unique_ptr<Reply, CFreeDeleter>;
0061 
0062 /* -- On Click Native Event Filter ------------------------------------------------------------- */
0063 
0064 class ImagePlatformXcb::OnClickEventFilter : public QAbstractNativeEventFilter
0065 {
0066 public:
0067     explicit OnClickEventFilter(ImagePlatformXcb *platformPtr)
0068         : m_platformPtr(platformPtr)
0069     {
0070     }
0071 
0072     void setCaptureOptions(ImagePlatform::GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow)
0073     {
0074         m_grabMode = grabMode;
0075         m_includePointer = includePointer;
0076         m_includeDecorations = includeDecorations;
0077         m_includeShadow = includeShadow;
0078     }
0079 
0080     bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr * /*result*/) override
0081     {
0082         if (eventType == "xcb_generic_event_t") {
0083             auto firstEvent = static_cast<xcb_generic_event_t *>(message);
0084 
0085             switch (firstEvent->response_type & ~0x80) {
0086             case XCB_BUTTON_RELEASE: {
0087                 // uninstall the eventfilter and release the mouse
0088                 qApp->removeNativeEventFilter(this);
0089                 xcb_ungrab_pointer(QX11Info::connection(), XCB_TIME_CURRENT_TIME);
0090 
0091                 // decide whether to grab or abort. regrab the mouse on mouse-wheel events
0092                 {
0093                     auto secondEvent = static_cast<xcb_button_release_event_t *>(message);
0094                     if (secondEvent->detail == 1) {
0095                         QTimer::singleShot(0, nullptr, [this]() {
0096                             m_platformPtr->doGrabNow(m_grabMode, m_includePointer, m_includeDecorations, m_includeShadow);
0097                         });
0098                     } else if (secondEvent->detail == 2 || secondEvent->detail == 3) {
0099                         // 2: middle click, 3: right click; both cancel
0100                         Q_EMIT m_platformPtr->newScreenshotTaken();
0101                     } else if (secondEvent->detail < 4) {
0102                         Q_EMIT m_platformPtr->newScreenshotFailed();
0103                     } else {
0104                         QTimer::singleShot(0, nullptr, [this]() {
0105                             m_platformPtr->doGrabOnClick(m_grabMode, m_includePointer, m_includeDecorations, m_includeShadow);
0106                         });
0107                     }
0108                 }
0109                 return true;
0110             }
0111             default:
0112                 return false;
0113             }
0114         }
0115         return false;
0116     }
0117 
0118 private:
0119     ImagePlatformXcb *m_platformPtr;
0120     ImagePlatform::GrabMode m_grabMode{GrabMode::AllScreens};
0121     bool m_includePointer{true};
0122     bool m_includeDecorations{true};
0123     bool m_includeShadow{true};
0124 };
0125 
0126 /* -- General Plumbing ------------------------------------------------------------------------- */
0127 
0128 ImagePlatformXcb::ImagePlatformXcb(QObject *parent)
0129     : ImagePlatform(parent)
0130     , m_nativeEventFilter(new OnClickEventFilter(this))
0131 {
0132     updateSupportedGrabModes();
0133     connect(qGuiApp, &QGuiApplication::screenAdded, this, &ImagePlatformXcb::updateSupportedGrabModes);
0134     connect(qGuiApp, &QGuiApplication::screenRemoved, this, &ImagePlatformXcb::updateSupportedGrabModes);
0135 }
0136 
0137 ImagePlatformXcb::~ImagePlatformXcb()
0138 {
0139 }
0140 
0141 ImagePlatform::GrabModes ImagePlatformXcb::supportedGrabModes() const
0142 {
0143     return m_grabModes;
0144 }
0145 
0146 void ImagePlatformXcb::updateSupportedGrabModes()
0147 {
0148     ImagePlatform::GrabModes grabModes = {
0149         GrabMode::AllScreens, GrabMode::ActiveWindow,
0150         GrabMode::WindowUnderCursor, GrabMode::TransientWithParent,
0151         GrabMode::PerScreenImageNative
0152     };
0153 
0154     if (QApplication::screens().count() > 1) {
0155         grabModes |= ImagePlatform::GrabMode::CurrentScreen;
0156     }
0157 
0158     if (m_grabModes != grabModes) {
0159         m_grabModes = grabModes;
0160         Q_EMIT supportedGrabModesChanged();
0161     }
0162 }
0163 
0164 ImagePlatform::ShutterModes ImagePlatformXcb::supportedShutterModes() const
0165 {
0166     return {ShutterMode::Immediate | ShutterMode::OnClick};
0167 }
0168 
0169 void ImagePlatformXcb::doGrab(ShutterMode shutterMode, GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow)
0170 {
0171     switch (shutterMode) {
0172     case ShutterMode::Immediate: {
0173         doGrabNow(grabMode, includePointer, includeDecorations, includeShadow);
0174         return;
0175     }
0176     case ShutterMode::OnClick: {
0177         doGrabOnClick(grabMode, includePointer, includeDecorations, includeShadow);
0178         return;
0179     }
0180     }
0181 }
0182 
0183 /* -- Platform Utilities ----------------------------------------------------------------------- */
0184 
0185 void ImagePlatformXcb::updateWindowTitle(xcb_window_t window)
0186 {
0187     auto title = KX11Extras::readNameProperty(window, XA_WM_NAME);
0188     Q_EMIT windowTitleChanged(title);
0189 }
0190 
0191 bool ImagePlatformXcb::isKWinAvailable()
0192 {
0193     if (QDBusConnection::sessionBus().interface()->isServiceRegistered(u"org.kde.KWin"_s)) {
0194         QDBusInterface iface(u"org.kde.KWin"_s, u"/Effects"_s, u"org.kde.kwin.Effects"_s);
0195         QDBusReply<bool> reply = iface.call(u"isEffectLoaded"_s, u"screenshot"_s);
0196         return reply.value();
0197     }
0198     return false;
0199 }
0200 
0201 /* -- XCB Utilities ---------------------------------------------------------------------------- */
0202 
0203 QPoint ImagePlatformXcb::getCursorPosition()
0204 {
0205     // QCursor::pos() is not used because it requires additional calculations.
0206     // Its value is the offset to the origin of the current screen is in
0207     // device-independent pixels while the origin itself uses native pixels.
0208 
0209     auto xcbConn = QX11Info::connection();
0210     auto pointerCookie = xcb_query_pointer_unchecked(xcbConn, QX11Info::appRootWindow());
0211     XcbReplyPtr<xcb_query_pointer_reply_t> pointerReply(xcb_query_pointer_reply(xcbConn, pointerCookie, nullptr));
0212 
0213     return QPoint(pointerReply->root_x, pointerReply->root_y);
0214 }
0215 
0216 QRect ImagePlatformXcb::getDrawableGeometry(xcb_drawable_t drawable)
0217 {
0218     auto xcbConn = QX11Info::connection();
0219     auto geoCookie = xcb_get_geometry_unchecked(xcbConn, drawable);
0220     XcbReplyPtr<xcb_get_geometry_reply_t> geoReply(xcb_get_geometry_reply(xcbConn, geoCookie, nullptr));
0221     if (!geoReply) {
0222         return QRect();
0223     }
0224     return QRect(geoReply->x, geoReply->y, geoReply->width, geoReply->height);
0225 }
0226 
0227 xcb_window_t ImagePlatformXcb::getWindowUnderCursor()
0228 {
0229     auto xcbConn = QX11Info::connection();
0230     auto appWin = QX11Info::appRootWindow();
0231 
0232     const QByteArray atomName("WM_STATE");
0233     auto atomCookie = xcb_intern_atom_unchecked(xcbConn, 0, atomName.length(), atomName.constData());
0234     auto pointerCookie = xcb_query_pointer_unchecked(xcbConn, appWin);
0235     XcbReplyPtr<xcb_intern_atom_reply_t> atomReply(xcb_intern_atom_reply(xcbConn, atomCookie, nullptr));
0236     XcbReplyPtr<xcb_query_pointer_reply_t> pointerReply(xcb_query_pointer_reply(xcbConn, pointerCookie, nullptr));
0237 
0238     if (atomReply->atom == XCB_ATOM_NONE) {
0239         return QX11Info::appRootWindow();
0240     }
0241 
0242     // now start testing
0243     QStack<xcb_window_t> windowStack;
0244     windowStack.push(pointerReply->child);
0245 
0246     while (!windowStack.isEmpty()) {
0247         appWin = windowStack.pop();
0248 
0249         // next, check if our window has the WM_STATE property set on
0250         // the window. if yes, return the window - we have found it
0251         auto propCookie = xcb_get_property_unchecked(xcbConn, 0, appWin, atomReply->atom, XCB_ATOM_ANY, 0, 0);
0252         XcbReplyPtr<xcb_get_property_reply_t> propReply(xcb_get_property_reply(xcbConn, propCookie, nullptr));
0253 
0254         if (propReply->type != XCB_ATOM_NONE) {
0255             return appWin;
0256         }
0257 
0258         // if we're here, this means the window is not the real window
0259         // we should start looking at its children
0260         auto treeCookie = xcb_query_tree_unchecked(xcbConn, appWin);
0261         XcbReplyPtr<xcb_query_tree_reply_t> treeReply(xcb_query_tree_reply(xcbConn, treeCookie, nullptr));
0262         auto windowChildren = xcb_query_tree_children(treeReply.get());
0263         auto windowChildrenLength = xcb_query_tree_children_length(treeReply.get());
0264 
0265         for (int iIdx = windowChildrenLength - 1; iIdx >= 0; iIdx--) {
0266             windowStack.push(windowChildren[iIdx]);
0267         }
0268     }
0269 
0270     // return the window. it has geometry information for a crop
0271     return pointerReply->child;
0272 }
0273 
0274 xcb_window_t ImagePlatformXcb::getTransientWindowParent(xcb_window_t childWindow, QRect &windowRectOut, bool includeDecorations)
0275 {
0276     NET::Properties netProp = includeDecorations ? NET::WMFrameExtents : NET::WMGeometry;
0277     KWindowInfo windowInfo(childWindow, netProp, NET::WM2TransientFor);
0278 
0279     // add the current window to the image
0280     if (includeDecorations) {
0281         windowRectOut = windowInfo.frameGeometry();
0282     } else {
0283         windowRectOut = windowInfo.geometry();
0284     }
0285     return windowInfo.transientFor();
0286 }
0287 
0288 QList<QRect> ImagePlatformXcb::getScreenRects()
0289 {
0290     QList<QRect> screenRects;
0291     auto xcbConn = QX11Info::connection();
0292     auto appWin = QX11Info::appRootWindow();
0293     auto monitorsCookie = xcb_randr_get_monitors(xcbConn, appWin, 1);
0294     XcbReplyPtr<xcb_randr_get_monitors_reply_t> monitorsReply(xcb_randr_get_monitors_reply(xcbConn, monitorsCookie, nullptr));
0295     auto it = xcb_randr_get_monitors_monitors_iterator(monitorsReply.get());
0296     while (it.rem) {
0297         auto monitorInfo = it.data;
0298         screenRects += {monitorInfo->x, monitorInfo->y, monitorInfo->width, monitorInfo->height};
0299         xcb_randr_monitor_info_next(&it);
0300     }
0301     return screenRects;
0302 }
0303 
0304 /* -- Image Processing Utilities --------------------------------------------------------------- */
0305 
0306 QImage ImagePlatformXcb::addDropShadow(QImage &image)
0307 {
0308     // Create a new image that is 20px wider and 20px taller than the original image
0309     QImage shadowImage(image.size() + QSize(40, 40), QImage::Format_ARGB32);
0310     shadowImage.fill(Qt::transparent);
0311 
0312     // Create a painter for the shadow image
0313     QPainter shadowPainter(&shadowImage);
0314 
0315     // Create a pixmap item for the original image
0316     auto pixmapItem = new QGraphicsPixmapItem;
0317     pixmapItem->setPixmap(QPixmap::fromImage(image));
0318 
0319     // Create a drop shadow effect for the pixmap item
0320     auto shadowEffect = new QGraphicsDropShadowEffect;
0321     shadowEffect->setOffset(0);
0322     shadowEffect->setBlurRadius(20);
0323     pixmapItem->setGraphicsEffect(shadowEffect);
0324 
0325     // Create a graphics scene and add the pixmap item to it
0326     QGraphicsScene graphicsScene;
0327     graphicsScene.addItem(pixmapItem);
0328 
0329     // Render the graphics scene to the shadow image
0330     graphicsScene.render(&shadowPainter, QRectF(), QRectF(-20, -20, image.width() + 40, image.height() + 40));
0331     shadowPainter.end();
0332 
0333     // Return the shadow image
0334     return shadowImage;
0335 }
0336 
0337 QImage ImagePlatformXcb::convertFromNative(xcb_image_t *xcbImage)
0338 {
0339     auto imageFormat = QImage::Format_Invalid;
0340     switch (xcbImage->depth) {
0341     case 1:
0342         imageFormat = QImage::Format_MonoLSB;
0343         break;
0344     case 16:
0345         imageFormat = QImage::Format_RGB16;
0346         break;
0347     case 24:
0348         imageFormat = QImage::Format_RGB32;
0349         break;
0350     case 30:
0351         imageFormat = QImage::Format_RGB30;
0352         break;
0353     case 32:
0354         imageFormat = QImage::Format_ARGB32_Premultiplied;
0355         break;
0356     default:
0357         return {}; // we don't know
0358     }
0359 
0360     // the RGB32 format requires data format 0xffRRGGBB, ensure that this fourth byte really is 0xff
0361     if (imageFormat == QImage::Format_RGB32) {
0362         auto data = reinterpret_cast<quint32 *>(xcbImage->data);
0363         for (size_t iIter = 0; iIter < xcbImage->width * xcbImage->height; iIter++) {
0364             data[iIter] |= 0xff000000;
0365         }
0366     }
0367 
0368     QImage image(xcbImage->data, xcbImage->width, xcbImage->height, imageFormat);
0369     if (image.isNull()) {
0370         return {};
0371     }
0372 
0373     // work around an abort in QImage::color
0374     if (image.format() == QImage::Format_MonoLSB) {
0375         image.setColorCount(2);
0376         image.setColor(0, QColor(Qt::white).rgb());
0377         image.setColor(1, QColor(Qt::black).rgb());
0378     }
0379 
0380     // the image is ready. Since the backing data from xcbImage could be freed
0381     // before the image goes away, a deep copy is necessary.
0382     return image.copy();
0383 }
0384 
0385 QImage ImagePlatformXcb::blendCursorImage(QImage &image, const QRect rect)
0386 {
0387     // If the cursor position lies outside the area, do not bother drawing a cursor.
0388 
0389     auto cursorPos = getCursorPosition();
0390     if (!rect.contains(cursorPos)) {
0391         return image;
0392     }
0393 
0394     // now we can get the image and start processing
0395     auto xcbConn = QX11Info::connection();
0396 
0397     auto cursorCookie = xcb_xfixes_get_cursor_image_unchecked(xcbConn);
0398     XcbReplyPtr<xcb_xfixes_get_cursor_image_reply_t> cursorReply(xcb_xfixes_get_cursor_image_reply(xcbConn, cursorCookie, nullptr));
0399     if (!cursorReply) {
0400         return image;
0401     }
0402 
0403     // get the image and process it into a qimage
0404     auto pixelData = xcb_xfixes_get_cursor_image_cursor_image(cursorReply.get());
0405     if (!pixelData) {
0406         return image;
0407     }
0408     QImage cursorImage(reinterpret_cast<quint8 *>(pixelData), cursorReply->width, cursorReply->height, QImage::Format_ARGB32_Premultiplied);
0409 
0410     // a small fix for the cursor position for fancier cursors
0411     cursorPos -= QPoint(cursorReply->xhot, cursorReply->yhot);
0412 
0413     // now we translate the cursor point to our screen rectangle and do the painting
0414     cursorPos -= QPoint(rect.x(), rect.y());
0415     QPainter painter(&image);
0416     painter.drawImage(cursorPos, cursorImage);
0417     return image;
0418 }
0419 
0420 QImage ImagePlatformXcb::postProcessImage(QImage &image, QRect rect, bool blendPointer)
0421 {
0422     if (!(blendPointer)) {
0423         // note: this may be a null image if an error occurred.
0424         return image;
0425     }
0426     return blendCursorImage(image, rect);
0427 }
0428 
0429 /* -- Capture Helpers -------------------------------------------------------------------------- */
0430 
0431 QImage ImagePlatformXcb::getImageFromDrawable(xcb_drawable_t xcbDrawable, const QRect &rect)
0432 {
0433     auto xcbConn = QX11Info::connection();
0434 
0435     // proceed to get an image based on the geometry (in device pixels)
0436     XcbImagePtr xcbImage(xcb_image_get(xcbConn, xcbDrawable, rect.x(), rect.y(), rect.width(), rect.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP));
0437 
0438     // too bad, the capture failed.
0439     if (!xcbImage) {
0440         return {};
0441     }
0442 
0443     // now process the image
0444     return convertFromNative(xcbImage.get());
0445 }
0446 
0447 QImage ImagePlatformXcb::getToplevelImage(QRect rect, bool blendPointer)
0448 {
0449     auto rootWindow = QX11Info::appRootWindow();
0450 
0451     // treat a null rect as an alias for capturing fullscreen
0452     if (!rect.isValid()) {
0453         rect = getDrawableGeometry(rootWindow);
0454     } else {
0455         QRegion screenRegion;
0456         const auto screenRects = getScreenRects();
0457         for (auto &screenRect :screenRects) {
0458             screenRegion += screenRect;
0459         }
0460         rect = (screenRegion & rect).boundingRect();
0461     }
0462 
0463     auto image = getImageFromDrawable(rootWindow, rect);
0464     return postProcessImage(image, rect, blendPointer);
0465 }
0466 
0467 QImage ImagePlatformXcb::getWindowImage(xcb_window_t window, bool blendPointer)
0468 {
0469     auto xcbConn = QX11Info::connection();
0470 
0471     // first get geometry information for our window
0472     auto geoCookie = xcb_get_geometry_unchecked(xcbConn, window);
0473     XcbReplyPtr<xcb_get_geometry_reply_t> geoReply(xcb_get_geometry_reply(xcbConn, geoCookie, nullptr));
0474     QRect windowRect(geoReply->x, geoReply->y, geoReply->width, geoReply->height);
0475 
0476     // then proceed to get an image
0477     auto image = getImageFromDrawable(window, windowRect);
0478 
0479     // translate window coordinates to global ones.
0480     auto rootGeoCookie = xcb_get_geometry_unchecked(xcbConn, geoReply->root);
0481     XcbReplyPtr<xcb_get_geometry_reply_t> rootGeoReply(xcb_get_geometry_reply(xcbConn, rootGeoCookie, nullptr));
0482     auto translateCookie = xcb_translate_coordinates_unchecked(xcbConn, window, geoReply->root, rootGeoReply->x, rootGeoReply->y);
0483     XcbReplyPtr<xcb_translate_coordinates_reply_t> translateReply(xcb_translate_coordinates_reply(xcbConn, translateCookie, nullptr));
0484 
0485     // adjust local to global coordinates.
0486     windowRect.moveRight(windowRect.x() + translateReply->dst_x);
0487     windowRect.moveTop(windowRect.y() + translateReply->dst_y);
0488 
0489     // if the window capture failed, try to obtain one from the full screen.
0490     if (image.isNull()) {
0491         return getToplevelImage(windowRect, blendPointer);
0492     }
0493     return postProcessImage(image, windowRect, blendPointer);
0494 }
0495 
0496 void ImagePlatformXcb::handleKWinScreenshotReply(quint64 drawable)
0497 {
0498     QDBusConnection::sessionBus().disconnect(u"org.kde.KWin"_s,
0499                                              u"/Screenshot"_s,
0500                                              u"org.kde.kwin.Screenshot"_s,
0501                                              u"screenshotCreated"_s,
0502                                              this,
0503                                              SLOT(handleKWinScreenshotReply(quint64)));
0504 
0505     // obtain width and height and grab an image (x and y are always zero for drawables)
0506     auto xcbDrawable = static_cast<xcb_drawable_t>(drawable);
0507     auto rect = getDrawableGeometry(xcbDrawable);
0508     auto image = getImageFromDrawable(xcbDrawable, rect);
0509 
0510     if (!image.isNull()) {
0511         image.setDevicePixelRatio(qGuiApp->devicePixelRatio());
0512         Q_EMIT newScreenshotTaken(image);
0513         return;
0514     }
0515     Q_EMIT newScreenshotFailed();
0516 }
0517 
0518 /* -- Grabber Methods -------------------------------------------------------------------------- */
0519 
0520 void ImagePlatformXcb::grabAllScreens(bool includePointer, bool crop)
0521 {
0522     auto image = getToplevelImage(QRect(), includePointer);
0523     image.setDevicePixelRatio(qGuiApp->devicePixelRatio());
0524     if (crop) {
0525         Q_EMIT newCroppableScreenshotTaken(image);
0526         return;
0527     }
0528     Q_EMIT newScreenshotTaken(image);
0529 }
0530 
0531 void ImagePlatformXcb::grabCurrentScreen(bool includePointer)
0532 {
0533     auto cursorPosition = QCursor::pos();
0534     const auto screenRects = getScreenRects();
0535     for (auto &screenRect : screenRects) {
0536         // On X11, QScreen::geometry's position is not scaled, but the size is scaled and rounded.
0537         QRect qScreenRect(screenRect.topLeft(), screenRect.size() / qGuiApp->devicePixelRatio());
0538         if (!qScreenRect.contains(cursorPosition)) {
0539             continue;
0540         }
0541 
0542         // the screen origin is in native pixels, but the size is device-dependent.
0543         // convert these also to native pixels.
0544         auto image = getToplevelImage(screenRect, includePointer);
0545         image.setDevicePixelRatio(qGuiApp->devicePixelRatio());
0546         Q_EMIT newScreenshotTaken(image);
0547         return;
0548     }
0549 
0550     // no screen found with our cursor, fallback to capturing all screens
0551     grabAllScreens(includePointer);
0552 }
0553 
0554 void ImagePlatformXcb::grabApplicationWindow(xcb_window_t window, bool includePointer, bool includeDecorations)
0555 {
0556     // if the user doesn't want decorations captured, we're in luck. This is
0557     // the easiest bit
0558 
0559     auto image = getWindowImage(window, includePointer);
0560     image.setDevicePixelRatio(qGuiApp->devicePixelRatio());
0561     if (!includeDecorations || window == QX11Info::appRootWindow()) {
0562         Q_EMIT newScreenshotTaken(image);
0563         return;
0564     }
0565 
0566     // if the user wants the window decorations, things get a little tricky.
0567     // we can't simply get a handle to the window manager frame window and
0568     // just grab it, because some compositing window managers (yes, kwin
0569     // included) do not render the window onto the frame but keep it in a
0570     // separate opengl buffer, so grabbing this window is going to simply
0571     // give us a transparent image with the frame and titlebar.
0572 
0573     // all is not lost. what we need to do is grab the image of the entire
0574     // desktop, find the geometry of the window including its frame, and
0575     // crop the root image accordingly.
0576 
0577     KWindowInfo windowInfo(window, NET::WMFrameExtents);
0578     if (windowInfo.valid()) {
0579         auto frameGeom = windowInfo.frameGeometry();
0580         image = getToplevelImage(frameGeom, includePointer);
0581     }
0582 
0583     // fallback is window without the frame
0584     Q_EMIT newScreenshotTaken(image);
0585 }
0586 
0587 void ImagePlatformXcb::grabActiveWindow(bool includePointer, bool includeDecorations, bool includeShadow)
0588 {
0589     auto activeWindow = KX11Extras::activeWindow();
0590     updateWindowTitle(activeWindow);
0591 
0592     // if KWin is available, use the KWin DBus interfaces
0593     if (includeDecorations && isKWinAvailable()) {
0594         auto bus = QDBusConnection::sessionBus();
0595         bus.connect(u"org.kde.KWin"_s,
0596                      u"/Screenshot"_s,
0597                      u"org.kde.kwin.Screenshot"_s,
0598                      u"screenshotCreated"_s,
0599                      this,
0600                      SLOT(handleKWinScreenshotReply(quint64)));
0601         QDBusInterface iface(u"org.kde.KWin"_s, u"/Screenshot"_s, u"org.kde.kwin.Screenshot"_s);
0602 
0603         int opMask = 1;
0604         if (includePointer) {
0605             opMask |= 1 << 1;
0606         }
0607         if (includeShadow) {
0608             opMask |= 1 << 2;
0609         }
0610         iface.call(QStringLiteral("screenshotForWindow"), static_cast<quint64>(activeWindow), opMask);
0611 
0612         return;
0613     }
0614 
0615     // otherwise, use the native functionality
0616     grabApplicationWindow(activeWindow, includePointer, includeDecorations);
0617 }
0618 
0619 void ImagePlatformXcb::grabWindowUnderCursor(bool includePointer, bool includeDecorations, bool includeShadow)
0620 {
0621     auto window = getWindowUnderCursor();
0622     updateWindowTitle(window);
0623 
0624     // if KWin is available, use the KWin DBus interfaces
0625     if (includeDecorations && isKWinAvailable()) {
0626         auto bus = QDBusConnection::sessionBus();
0627         bus.connect(u"org.kde.KWin"_s,
0628                      u"/Screenshot"_s,
0629                      u"org.kde.kwin.Screenshot"_s,
0630                      u"screenshotCreated"_s,
0631                      this,
0632                      SLOT(handleKWinScreenshotReply(quint64)));
0633         QDBusInterface interface(u"org.kde.KWin"_s, u"/Screenshot"_s, u"org.kde.kwin.Screenshot"_s);
0634 
0635         int opMask = 1;
0636         if (includePointer) {
0637             opMask |= 1 << 1;
0638         }
0639         if (includeShadow) {
0640             opMask |= 1 << 2;
0641         }
0642         interface.call(u"screenshotWindowUnderCursor"_s, opMask);
0643 
0644         return;
0645     }
0646 
0647     // otherwise, use the native functionality
0648     grabApplicationWindow(window, includePointer, includeDecorations);
0649 }
0650 
0651 void ImagePlatformXcb::grabTransientWithParent(bool includePointer, bool includeDecorations, bool includeShadow)
0652 {
0653     auto window = getWindowUnderCursor();
0654     updateWindowTitle(window);
0655 
0656     // grab the image early
0657     auto image = getToplevelImage(QRect(), false);
0658     image.setDevicePixelRatio(qGuiApp->devicePixelRatio());
0659 
0660     // now that we know we have a transient window, let's
0661     // find other possible transient windows and the app window itself.
0662     QRegion clipRegion;
0663     QSet<xcb_window_t> transientWindows;
0664     auto parentWindow = window;
0665     const QRect desktopRect(0, 0, 1, 1);
0666     do {
0667         // find parent window and add the window to the visible region
0668         auto winId = parentWindow;
0669         QRect winRect;
0670         parentWindow = getTransientWindowParent(winId, winRect, includeDecorations);
0671         transientWindows << winId;
0672 
0673         // Don't include the 1x1 pixel sized desktop window in the top left corner that is present
0674         // if the window is a QDialog without a parent.
0675         // BUG: 376350
0676         if (winRect != desktopRect) {
0677             clipRegion += winRect;
0678         }
0679 
0680         // Continue walking only if this is a transient window (having a parent)
0681     } while (parentWindow != XCB_WINDOW_NONE && !transientWindows.contains(parentWindow));
0682 
0683     // All parents are known now, find other transient children.
0684     // Assume that the lowest window is behind everything else, then if a new
0685     // transient window is discovered, its children can then also be found.
0686     auto winList = KX11Extras::stackingOrder();
0687     for (auto winId : winList) {
0688         QRect winRect;
0689         auto parentWindow = getTransientWindowParent(winId, winRect, includeDecorations);
0690 
0691         // if the parent should be displayed, then show the child too
0692         if (transientWindows.contains(parentWindow)) {
0693             if (!transientWindows.contains(winId)) {
0694                 transientWindows << winId;
0695                 clipRegion += winRect;
0696             }
0697         }
0698     }
0699 
0700     // we can probably go ahead and generate the image now
0701     QImage clippedImage(image.size(), QImage::Format_ARGB32);
0702     clippedImage.fill(Qt::transparent);
0703 
0704     QPainter painter(&clippedImage);
0705     painter.setClipRegion(clipRegion);
0706     painter.drawImage(0, 0, image);
0707     painter.end();
0708     image = clippedImage.copy(clipRegion.boundingRect());
0709 
0710     // add a drop shadow if requested
0711     if (includeShadow) {
0712         image = addDropShadow(image);
0713     }
0714 
0715     if (includePointer) {
0716         auto topLeft = clipRegion.boundingRect().topLeft() - QPoint(20, 20);
0717         image = blendCursorImage(image, QRect(topLeft, QSize(image.width(), image.height())));
0718     }
0719 
0720     Q_EMIT newScreenshotTaken(image);
0721 }
0722 
0723 void ImagePlatformXcb::doGrabNow(GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow)
0724 {
0725     if (grabMode & ~(ActiveWindow | WindowUnderCursor | TransientWithParent)) {
0726         // Notify that window title is empty since we are not picking a window.
0727         Q_EMIT windowTitleChanged();
0728     }
0729     switch (grabMode) {
0730     case GrabMode::AllScreens:
0731     case GrabMode::AllScreensScaled:
0732         grabAllScreens(includePointer);
0733         break;
0734     case GrabMode::PerScreenImageNative: {
0735         grabAllScreens(includePointer, true);
0736         break;
0737     }
0738     case GrabMode::CurrentScreen:
0739         grabCurrentScreen(includePointer);
0740         break;
0741     case GrabMode::ActiveWindow:
0742         grabActiveWindow(includePointer, includeDecorations, includeShadow);
0743         break;
0744     case GrabMode::WindowUnderCursor:
0745         grabWindowUnderCursor(includePointer, includeDecorations, includeShadow);
0746         break;
0747     case GrabMode::TransientWithParent:
0748         grabTransientWithParent(includePointer, includeDecorations, includeShadow);
0749         break;
0750     case GrabMode::NoGrabModes:
0751         Q_EMIT newScreenshotFailed();
0752     }
0753 }
0754 
0755 void ImagePlatformXcb::doGrabOnClick(GrabMode grabMode, bool includePointer, bool includeDecorations, bool includeShadow)
0756 {
0757     // get the cursor image
0758     xcb_cursor_t xcbCursor = XCB_CURSOR_NONE;
0759     xcb_cursor_context_t *xcbCursorCtx = nullptr;
0760     xcb_screen_t *xcbAppScreen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen());
0761 
0762     if (xcb_cursor_context_new(QX11Info::connection(), xcbAppScreen, &xcbCursorCtx) >= 0) {
0763         QList<QByteArray> cursorNames = {"cross"_ba, "crosshair"_ba, "diamond-cross"_ba, "cross-reverse"_ba};
0764 
0765         for (const auto &cursorName : cursorNames) {
0766             xcb_cursor_t cursor = xcb_cursor_load_cursor(xcbCursorCtx, cursorName.constData());
0767             if (cursor != XCB_CURSOR_NONE) {
0768                 xcbCursor = cursor;
0769                 break;
0770             }
0771         }
0772     }
0773 
0774     // grab the cursor
0775     xcb_grab_pointer_cookie_t grabPointerCookie = xcb_grab_pointer_unchecked(QX11Info::connection(), // xcb connection
0776                                                                              0, // deliver events to owner? nope
0777                                                                              QX11Info::appRootWindow(), // window to grab pointer for (root)
0778                                                                              XCB_EVENT_MASK_BUTTON_RELEASE, // which events do I want
0779                                                                              XCB_GRAB_MODE_SYNC, // pointer grab mode
0780                                                                              XCB_GRAB_MODE_ASYNC, // keyboard grab mode (why is this even here)
0781                                                                              XCB_NONE, // confine pointer to which window (none)
0782                                                                              xcbCursor, // cursor to change to for the duration of grab
0783                                                                              XCB_TIME_CURRENT_TIME // do this right now
0784     );
0785     XcbReplyPtr<xcb_grab_pointer_reply_t> grabPointerReply(xcb_grab_pointer_reply(QX11Info::connection(), grabPointerCookie, nullptr));
0786 
0787     // if the grab failed, take the screenshot right away
0788     if (grabPointerReply->status != XCB_GRAB_STATUS_SUCCESS) {
0789         doGrabNow(grabMode, includePointer, includeDecorations, includeShadow);
0790         return;
0791     }
0792 
0793     // fix things if our pointer grab causes a lockup and install our event filter
0794     m_nativeEventFilter->setCaptureOptions(grabMode, includePointer, includeDecorations, includeShadow);
0795     xcb_allow_events(QX11Info::connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME);
0796     qApp->installNativeEventFilter(m_nativeEventFilter.get());
0797 
0798     // done. clean stuff up
0799     xcb_cursor_context_free(xcbCursorCtx);
0800     xcb_free_cursor(QX11Info::connection(), xcbCursor);
0801 }
0802 
0803 #include "moc_ImagePlatformXcb.cpp"