screenshot.cpp 24.7 KB
Newer Older
Martin Flöser's avatar
Martin Flöser committed
1 2 3 4
/********************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

5
 Copyright (C) 2010 Martin Gräßlin <mgraesslin@kde.org>
Martin Flöser's avatar
Martin Flöser committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "screenshot.h"
22
#include <kwinglplatform.h>
Martin Flöser's avatar
Martin Flöser committed
23
#include <kwinglutils.h>
24
#include <kwinxrenderutils.h>
25 26
#include <QtConcurrentRun>
#include <QDataStream>
27 28 29
#include <QTemporaryFile>
#include <QDir>
#include <QDBusConnection>
30
#include <QVarLengthArray>
31
#include <QPainter>
32
#include <QMatrix4x4>
Martin Flöser's avatar
Martin Flöser committed
33
#include <xcb/xcb_image.h>
Martin Flöser's avatar
Martin Flöser committed
34

35 36 37
#include <KLocalizedString>
#include <KNotification>

38 39
#include <unistd.h>

Martin Flöser's avatar
Martin Flöser committed
40 41 42
namespace KWin
{

43 44 45 46 47 48 49 50 51 52 53
const static QString s_errorAlreadyTaking = QStringLiteral("org.kde.kwin.Screenshot.Error.AlreadyTaking");
const static QString s_errorAlreadyTakingMsg = QStringLiteral("A screenshot is already been taken");
const static QString s_errorFd = QStringLiteral("org.kde.kwin.Screenshot.Error.FileDescriptor");
const static QString s_errorFdMsg = QStringLiteral("No valid file descriptor");
const static QString s_errorCancelled = QStringLiteral("org.kde.kwin.Screenshot.Error.Cancelled");
const static QString s_errorCancelledMsg = QStringLiteral("Screenshot got cancelled");
const static QString s_errorInvalidArea = QStringLiteral("org.kde.kwin.Screenshot.Error.InvalidArea");
const static QString s_errorInvalidAreaMsg = QStringLiteral("Invalid area requested");
const static QString s_errorInvalidScreen = QStringLiteral("org.kde.kwin.Screenshot.Error.InvalidScreen");
const static QString s_errorInvalidScreenMsg = QStringLiteral("Invalid screen requested");

Martin Flöser's avatar
Martin Flöser committed
54
bool ScreenShotEffect::supported()
55
{
56
    return  effects->compositingType() == XRenderCompositing ||
57
            (effects->isOpenGLCompositing() && GLRenderTarget::supported());
58
}
Martin Flöser's avatar
Martin Flöser committed
59 60

ScreenShotEffect::ScreenShotEffect()
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
61
    : m_scheduledScreenshot(nullptr)
62
{
63
    connect(effects, &EffectsHandler::windowClosed, this, &ScreenShotEffect::windowClosed);
64
    QDBusConnection::sessionBus().registerObject(QStringLiteral("/Screenshot"), this, QDBusConnection::ExportScriptableContents);
65
}
Martin Flöser's avatar
Martin Flöser committed
66 67

ScreenShotEffect::~ScreenShotEffect()
68
{
69
    QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Screenshot"));
70
}
Martin Flöser's avatar
Martin Flöser committed
71 72 73 74

#ifdef KWIN_HAVE_XRENDER_COMPOSITING
static QImage xPictureToImage(xcb_render_picture_t srcPic, const QRect &geometry, xcb_image_t **xImage)
{
75 76 77
    xcb_connection_t *c = effects->xcbConnection();
    xcb_pixmap_t xpix = xcb_generate_id(c);
    xcb_create_pixmap(c, 32, xpix, effects->x11RootWindow(), geometry.width(), geometry.height());
Martin Flöser's avatar
Martin Flöser committed
78
    XRenderPicture pic(xpix, 32);
79
    xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, srcPic, XCB_RENDER_PICTURE_NONE, pic,
Martin Flöser's avatar
Martin Flöser committed
80
                         geometry.x(), geometry.y(), 0, 0, 0, 0, geometry.width(), geometry.height());
81 82
    xcb_flush(c);
    *xImage = xcb_image_get(c, xpix, 0, 0, geometry.width(), geometry.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP);
Martin Flöser's avatar
Martin Flöser committed
83 84
    QImage img((*xImage)->data, (*xImage)->width, (*xImage)->height, (*xImage)->stride, QImage::Format_ARGB32_Premultiplied);
    // TODO: byte order might need swapping
85
    xcb_free_pixmap(c, xpix);
86
    return img.copy();
Martin Flöser's avatar
Martin Flöser committed
87 88 89
}
#endif

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
static QSize pickWindowSize(const QImage &image)
{
    xcb_connection_t *c = effects->xcbConnection();

    // This will implicitly enable BIG-REQUESTS extension.
    const uint32_t maximumRequestSize = xcb_get_maximum_request_length(c);
    const xcb_setup_t *setup = xcb_get_setup(c);

    uint32_t requestSize = sizeof(xcb_put_image_request_t);

    // With BIG-REQUESTS extension an additional 32-bit field is inserted into
    // the request so we better take it into account.
    if (setup->maximum_request_length < maximumRequestSize) {
        requestSize += 4;
    }

    const uint32_t maximumDataSize = 4 * maximumRequestSize - requestSize;
    const uint32_t bytesPerPixel = image.depth() >> 3;
    const uint32_t bytesPerLine = image.bytesPerLine();

    if (image.sizeInBytes() <= maximumDataSize) {
        return image.size();
    }

    if (maximumDataSize < bytesPerLine) {
        return QSize(maximumDataSize / bytesPerPixel, 1);
    }

    return QSize(image.width(), maximumDataSize / bytesPerLine);
}

static xcb_pixmap_t xpixmapFromImage(const QImage &image)
{
    xcb_connection_t *c = effects->xcbConnection();

    xcb_pixmap_t pixmap = xcb_generate_id(c);
    xcb_gcontext_t gc = xcb_generate_id(c);

    xcb_create_pixmap(c, image.depth(), pixmap, effects->x11RootWindow(),
        image.width(), image.height());
    xcb_create_gc(c, gc, pixmap, 0, nullptr);

    const int bytesPerPixel = image.depth() >> 3;

    // Figure out how much data we can send with one invocation of xcb_put_image.
    // In contrast to XPutImage, xcb_put_image doesn't implicitly split the data.
    const QSize window = pickWindowSize(image);

    for (int i = 0; i < image.height(); i += window.height()) {
        const int targetHeight = qMin(image.height() - i, window.height());
        const uint8_t *line = image.scanLine(i);

        for (int j = 0; j < image.width(); j += window.width()) {
            const int targetWidth = qMin(image.width() - j, window.width());
            const uint8_t *bytes = line + j * bytesPerPixel;
            const uint32_t byteCount = targetWidth * targetHeight * bytesPerPixel;

            xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap,
                gc, targetWidth, targetHeight, j, i, 0, image.depth(),
                byteCount, bytes);
        }
    }

    xcb_flush(c);
    xcb_free_gc(c, gc);

    return pixmap;
}

Albert Astals Cid's avatar
Albert Astals Cid committed
159
void ScreenShotEffect::paintScreen(int mask, const QRegion &region, ScreenPaintData &data)
160 161 162 163 164
{
    m_cachedOutputGeometry = data.outputGeometry();
    effects->paintScreen(mask, region, data);
}

Martin Flöser's avatar
Martin Flöser committed
165
void ScreenShotEffect::postPaintScreen()
166
{
Martin Flöser's avatar
Martin Flöser committed
167
    effects->postPaintScreen();
168
    if (m_scheduledScreenshot) {
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
        WindowPaintData d(m_scheduledScreenshot);
        double left = 0;
        double top = 0;
        double right = m_scheduledScreenshot->width();
        double bottom = m_scheduledScreenshot->height();
        if (m_scheduledScreenshot->hasDecoration() && m_type & INCLUDE_DECORATION) {
            foreach (const WindowQuad & quad, d.quads) {
                // we need this loop to include the decoration padding
                left   = qMin(left, quad.left());
                top    = qMin(top, quad.top());
                right  = qMax(right, quad.right());
                bottom = qMax(bottom, quad.bottom());
            }
        } else if (m_scheduledScreenshot->hasDecoration()) {
            WindowQuadList newQuads;
            left = m_scheduledScreenshot->width();
            top = m_scheduledScreenshot->height();
            right = 0;
            bottom = 0;
            foreach (const WindowQuad & quad, d.quads) {
                if (quad.type() == WindowQuadContents) {
                    newQuads << quad;
                    left   = qMin(left, quad.left());
                    top    = qMin(top, quad.top());
                    right  = qMax(right, quad.right());
                    bottom = qMax(bottom, quad.bottom());
                }
            }
            d.quads = newQuads;
        }
        const int width = right - left;
        const int height = bottom - top;
201
        bool validTarget = true;
202 203
        QScopedPointer<GLTexture> offscreenTexture;
        QScopedPointer<GLRenderTarget> target;
204
        if (effects->isOpenGLCompositing()) {
205
            offscreenTexture.reset(new GLTexture(GL_RGBA8, width, height));
206 207
            offscreenTexture->setFilter(GL_LINEAR);
            offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
208
            target.reset(new GLRenderTarget(*offscreenTexture));
209
            validTarget = target->valid();
210
        }
211
        if (validTarget) {
212 213
            d.setXTranslation(-m_scheduledScreenshot->x() - left);
            d.setYTranslation(-m_scheduledScreenshot->y() - top);
214

Martin Flöser's avatar
Martin Flöser committed
215 216
            // render window into offscreen texture
            int mask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_TRANSLUCENT;
217
            QImage img;
218
            if (effects->isOpenGLCompositing()) {
219
                GLRenderTarget::pushRenderTarget(target.data());
220 221 222
                glClearColor(0.0, 0.0, 0.0, 0.0);
                glClear(GL_COLOR_BUFFER_BIT);
                glClearColor(0.0, 0.0, 0.0, 1.0);
223 224 225 226 227

                QMatrix4x4 projection;
                projection.ortho(QRect(0, 0, offscreenTexture->width(), offscreenTexture->height()));
                d.setProjectionMatrix(projection);

228
                effects->drawWindow(m_scheduledScreenshot, mask, infiniteRegion(), d);
229

230 231
                // copy content from framebuffer into image
                img = QImage(QSize(width, height), QImage::Format_ARGB32);
232
                glReadnPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, img.sizeInBytes(), (GLvoid*)img.bits());
233 234 235
                GLRenderTarget::popRenderTarget();
                ScreenShotEffect::convertFromGLImage(img, width, height);
            }
236
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
237
            xcb_image_t *xImage = nullptr;
238 239 240
            if (effects->compositingType() == XRenderCompositing) {
                setXRenderOffscreen(true);
                effects->drawWindow(m_scheduledScreenshot, mask, QRegion(0, 0, width, height), d);
241
                if (xRenderOffscreenTarget()) {
Martin Flöser's avatar
Martin Flöser committed
242
                    img = xPictureToImage(xRenderOffscreenTarget(), QRect(0, 0, width, height), &xImage);
243
                }
244 245
                setXRenderOffscreen(false);
            }
246
#endif
247

248 249
            if (m_type & INCLUDE_CURSOR) {
                grabPointerImage(img, m_scheduledScreenshot->x() + left, m_scheduledScreenshot->y() + top);
Martin Flöser's avatar
Martin Flöser committed
250
            }
251

252
            if (m_windowMode == WindowMode::Xpixmap) {
253
                const xcb_pixmap_t xpix = xpixmapFromImage(img);
254 255 256 257
                emit screenshotCreated(xpix);
                m_windowMode = WindowMode::NoCapture;
            } else if (m_windowMode == WindowMode::File) {
                sendReplyImage(img);
258 259 260 261 262 263 264 265 266 267 268 269 270 271
            } else if (m_windowMode == WindowMode::FileDescriptor) {
                QtConcurrent::run(
                    [] (int fd, const QImage &img) {
                        QFile file;
                        if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) {
                            QDataStream ds(&file);
                            ds << img;
                            file.close();
                        } else {
                            close(fd);
                        }
                    }, m_fd, img);
                m_windowMode = WindowMode::NoCapture;
                m_fd = -1;
272
            }
Martin Flöser's avatar
Martin Flöser committed
273 274 275
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
            if (xImage) {
                xcb_image_destroy(xImage);
276
            }
Martin Flöser's avatar
Martin Flöser committed
277
#endif
278
        }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
279
        m_scheduledScreenshot = nullptr;
Martin Flöser's avatar
Martin Flöser committed
280
    }
281 282

    if (!m_scheduledGeometry.isNull()) {
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
        if (!m_cachedOutputGeometry.isNull()) {
            // special handling for per-output geometry rendering
            const QRect intersection = m_scheduledGeometry.intersected(m_cachedOutputGeometry);
            if (intersection.isEmpty()) {
                // doesn't intersect, not going onto this screenshot
                return;
            }
            const QImage img = blitScreenshot(intersection);
            if (img.size() == m_scheduledGeometry.size()) {
                // we are done
                sendReplyImage(img);
                return;
            }
            if (m_multipleOutputsImage.isNull()) {
                m_multipleOutputsImage = QImage(m_scheduledGeometry.size(), QImage::Format_ARGB32);
                m_multipleOutputsImage.fill(Qt::transparent);
            }
            QPainter p;
            p.begin(&m_multipleOutputsImage);
            p.drawImage(intersection.topLeft() - m_scheduledGeometry.topLeft(), img);
            p.end();
            m_multipleOutputsRendered = m_multipleOutputsRendered.united(intersection);
            if (m_multipleOutputsRendered.boundingRect() == m_scheduledGeometry) {
                sendReplyImage(m_multipleOutputsImage);
            }

        } else {
            const QImage img = blitScreenshot(m_scheduledGeometry);
            sendReplyImage(img);
        }
313
    }
314
}
Martin Flöser's avatar
Martin Flöser committed
315

316 317
void ScreenShotEffect::sendReplyImage(const QImage &img)
{
318 319 320 321 322 323 324 325 326 327 328 329 330 331
    if (m_fd != -1) {
        QtConcurrent::run(
            [] (int fd, const QImage &img) {
                QFile file;
                if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) {
                    QDataStream ds(&file);
                    ds << img;
                    file.close();
                } else {
                    close(fd);
                }
            }, m_fd, img);
        m_fd = -1;
    } else {
332
        QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(img)));
333
    }
334 335 336
    m_scheduledGeometry = QRect();
    m_multipleOutputsImage = QImage();
    m_multipleOutputsRendered = QRegion();
337
    m_captureCursor = false;
338
    m_windowMode = WindowMode::NoCapture;
339 340 341 342 343 344 345 346 347 348 349 350 351 352
}

QString ScreenShotEffect::saveTempImage(const QImage &img)
{
    if (img.isNull()) {
        return QString();
    }
    QTemporaryFile temp(QDir::tempPath() + QDir::separator() + QLatin1String("kwin_screenshot_XXXXXX.png"));
    temp.setAutoRemove(false);
    if (!temp.open()) {
        return QString();
    }
    img.save(&temp);
    temp.close();
353 354 355 356
    KNotification::event(KNotification::Notification,
                        i18nc("Notification caption that a screenshot got saved to file", "Screenshot"),
                        i18nc("Notification with path to screenshot file", "Screenshot saved to %1", temp.fileName()),
                        QStringLiteral("spectacle"));
357 358 359
    return temp.fileName();
}

360
void ScreenShotEffect::screenshotWindowUnderCursor(int mask)
361
{
362 363 364 365
    if (isTakingScreenshot()) {
        sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
        return;
    }
366 367
    m_type = (ScreenShotType)mask;
    const QPoint cursor = effects->cursorPos();
368 369 370 371 372 373 374 375
    EffectWindowList order = effects->stackingOrder();
    EffectWindowList::const_iterator it = order.constEnd(), first = order.constBegin();
    while( it != first ) {
        m_scheduledScreenshot = *(--it);
        if (m_scheduledScreenshot->isOnCurrentDesktop() &&
            !m_scheduledScreenshot->isMinimized() && !m_scheduledScreenshot->isDeleted() &&
            m_scheduledScreenshot->geometry().contains(cursor))
            break;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
376
        m_scheduledScreenshot = nullptr;
377 378
    }
    if (m_scheduledScreenshot) {
379
        m_windowMode = WindowMode::Xpixmap;
380
        m_scheduledScreenshot->addRepaintFull();
Martin Flöser's avatar
Martin Flöser committed
381
    }
382
}
383

384 385 386 387 388
void ScreenShotEffect::screenshotForWindow(qulonglong winid, int mask)
{
    m_type = (ScreenShotType) mask;
    EffectWindow* w = effects->findWindow(winid);
    if(w && !w->isMinimized() && !w->isDeleted()) {
389
        m_windowMode = WindowMode::Xpixmap;
390 391 392 393 394
        m_scheduledScreenshot = w;
        m_scheduledScreenshot->addRepaintFull();
    }
}

395 396 397 398 399
QString ScreenShotEffect::interactive(int mask)
{
    if (!calledFromDBus()) {
        return QString();
    }
400 401
    if (isTakingScreenshot()) {
        sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
402 403 404 405 406 407 408 409
        return QString();
    }
    m_type = (ScreenShotType) mask;
    m_windowMode = WindowMode::File;
    m_replyMessage = message();
    setDelayedReply(true);
    effects->startInteractiveWindowSelection(
        [this] (EffectWindow *w) {
410
            hideInfoMessage();
411
            if (!w) {
412
                QDBusConnection::sessionBus().send(m_replyMessage.createErrorReply(s_errorCancelled, s_errorCancelledMsg));
413 414 415 416 417 418 419
                m_windowMode = WindowMode::NoCapture;
                return;
            } else {
                m_scheduledScreenshot = w;
                m_scheduledScreenshot->addRepaintFull();
            }
    });
420

421
    showInfoMessage(InfoMessageMode::Window);
422 423
    return QString();
}
424

425 426 427 428
void ScreenShotEffect::interactive(QDBusUnixFileDescriptor fd, int mask)
{
    if (!calledFromDBus()) {
        return;
429
    }
430 431
    if (isTakingScreenshot()) {
        sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
432 433 434 435
        return;
    }
    m_fd = dup(fd.fileDescriptor());
    if (m_fd == -1) {
436
        sendErrorReply(s_errorFd, s_errorFdMsg);
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
        return;
    }
    m_type = (ScreenShotType) mask;
    m_windowMode = WindowMode::FileDescriptor;

    effects->startInteractiveWindowSelection(
        [this] (EffectWindow *w) {
            hideInfoMessage();
            if (!w) {
                close(m_fd);
                m_fd = -1;
                m_windowMode = WindowMode::NoCapture;
                return;
            } else {
                m_scheduledScreenshot = w;
                m_scheduledScreenshot->addRepaintFull();
            }
    });

456
    showInfoMessage(InfoMessageMode::Window);
457 458
}

459
void ScreenShotEffect::showInfoMessage(InfoMessageMode mode)
460
{
461
    QString text;
462 463
    switch (mode) {
    case InfoMessageMode::Window:
464
        text = i18n("Select window to screen shot with left click or enter.\nEscape or right click to cancel.");
465 466
        break;
    case InfoMessageMode::Screen:
467
        text = i18n("Create screen shot with left click or enter.\nEscape or right click to cancel.");
468 469
        break;
    }
470
    effects->showOnScreenMessage(text, QStringLiteral("spectacle"));
471 472 473 474
}

void ScreenShotEffect::hideInfoMessage()
{
475
    effects->hideOnScreenMessage(EffectsHandler::OnScreenMessageHideFlag::SkipsCloseAnimation);
476 477
}

478
QString ScreenShotEffect::screenshotFullscreen(bool captureCursor)
479
{
480 481 482
    if (!calledFromDBus()) {
        return QString();
    }
483 484
    if (isTakingScreenshot()) {
        sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
485 486 487 488 489
        return QString();
    }
    m_replyMessage = message();
    setDelayedReply(true);
    m_scheduledGeometry = effects->virtualScreenGeometry();
490
    m_captureCursor = captureCursor;
491 492
    effects->addRepaintFull();
    return QString();
493 494
}

495 496 497 498 499
void ScreenShotEffect::screenshotFullscreen(QDBusUnixFileDescriptor fd, bool captureCursor)
{
    if (!calledFromDBus()) {
        return;
    }
500 501
    if (isTakingScreenshot()) {
        sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
502 503 504 505
        return;
    }
    m_fd = dup(fd.fileDescriptor());
    if (m_fd == -1) {
506
        sendErrorReply(s_errorFd, s_errorFdMsg);
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
        return;
    }
    m_captureCursor = captureCursor;

    showInfoMessage(InfoMessageMode::Screen);
    effects->startInteractivePositionSelection(
        [this] (const QPoint &p) {
            hideInfoMessage();
            if (p == QPoint(-1, -1)) {
                // error condition
                close(m_fd);
                m_fd = -1;
            } else {
                m_scheduledGeometry = effects->virtualScreenGeometry();
                effects->addRepaint(m_scheduledGeometry);
            }
        }
    );
}

527
QString ScreenShotEffect::screenshotScreen(int screen, bool captureCursor)
528
{
529 530 531
    if (!calledFromDBus()) {
        return QString();
    }
532 533
    if (isTakingScreenshot()) {
        sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
534 535 536 537
        return QString();
    }
    m_scheduledGeometry = effects->clientArea(FullScreenArea, screen, 0);
    if (m_scheduledGeometry.isNull()) {
538
        sendErrorReply(s_errorInvalidScreen, s_errorInvalidScreenMsg);
539 540
        return QString();
    }
541
    m_captureCursor = captureCursor;
542 543 544 545
    m_replyMessage = message();
    setDelayedReply(true);
    effects->addRepaint(m_scheduledGeometry);
    return QString();
546 547
}

548 549 550 551 552
void ScreenShotEffect::screenshotScreen(QDBusUnixFileDescriptor fd, bool captureCursor)
{
    if (!calledFromDBus()) {
        return;
    }
553 554
    if (isTakingScreenshot()) {
        sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
555 556 557 558
        return;
    }
    m_fd = dup(fd.fileDescriptor());
    if (m_fd == -1) {
559
        sendErrorReply(s_errorFd, s_errorFdMsg);
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
        return;
    }
    m_captureCursor = captureCursor;

    showInfoMessage(InfoMessageMode::Screen);
    effects->startInteractivePositionSelection(
        [this] (const QPoint &p) {
            hideInfoMessage();
            if (p == QPoint(-1, -1)) {
                // error condition
                close(m_fd);
                m_fd = -1;
            } else {
                m_scheduledGeometry = effects->clientArea(FullScreenArea, effects->screenNumber(p), 0);
                if (m_scheduledGeometry.isNull()) {
                    close(m_fd);
                    m_fd = -1;
                    return;
                }
                effects->addRepaint(m_scheduledGeometry);
            }
        }
    );
}

585
QString ScreenShotEffect::screenshotArea(int x, int y, int width, int height, bool captureCursor)
586
{
587 588 589
    if (!calledFromDBus()) {
        return QString();
    }
590 591
    if (isTakingScreenshot()) {
        sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
592 593 594 595 596
        return QString();
    }
    m_scheduledGeometry = QRect(x, y, width, height);
    if (m_scheduledGeometry.isNull() || m_scheduledGeometry.isEmpty()) {
        m_scheduledGeometry = QRect();
597
        sendErrorReply(s_errorInvalidArea, s_errorInvalidAreaMsg);
598 599
        return QString();
    }
600
    m_captureCursor = captureCursor;
601 602 603 604
    m_replyMessage = message();
    setDelayedReply(true);
    effects->addRepaint(m_scheduledGeometry);
    return QString();
605 606
}

607
QImage ScreenShotEffect::blitScreenshot(const QRect &geometry)
608
{
609
    QImage img;
610
    if (effects->isOpenGLCompositing())
611 612
    {
        img = QImage(geometry.size(), QImage::Format_ARGB32);
613 614 615 616 617 618
        if (GLRenderTarget::blitSupported() && !GLPlatform::instance()->isGLES()) {
            GLTexture tex(GL_RGBA8, geometry.width(), geometry.height());
            GLRenderTarget target(tex);
            target.blitFromFramebuffer(geometry);
            // copy content from framebuffer into image
            tex.bind();
619
            glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits());
620 621 622
            tex.unbind();
        } else {
            glReadPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits());
623
        }
624 625 626
        ScreenShotEffect::convertFromGLImage(img, geometry.width(), geometry.height());
    }

Martin Flöser's avatar
Martin Flöser committed
627
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
628
    if (effects->compositingType() == XRenderCompositing) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
629
    xcb_image_t *xImage = nullptr;
Martin Flöser's avatar
Martin Flöser committed
630
        img = xPictureToImage(effects->xrenderBufferPicture(), geometry, &xImage);
631 632 633
        if (xImage) {
            xcb_image_destroy(xImage);
        }
Martin Flöser's avatar
Martin Flöser committed
634 635
    }
#endif
636

637 638 639 640
    if (m_captureCursor) {
        grabPointerImage(img, geometry.x(), geometry.y());
    }

641
    return img;
642
}
Martin Flöser's avatar
Martin Flöser committed
643

644
void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety)
645
{
646 647
    const auto cursor = effects->cursorImage();
    if (cursor.image().isNull())
648
        return;
649 650

    QPainter painter(&snapshot);
651
    painter.drawImage(effects->cursorPos() - cursor.hotSpot() - QPoint(offsetx, offsety), cursor.image());
652 653
}

Martin Flöser's avatar
Martin Flöser committed
654 655 656 657
void ScreenShotEffect::convertFromGLImage(QImage &img, int w, int h)
{
    // from QtOpenGL/qgl.cpp
    // Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
658
    // see https://github.com/qt/qtbase/blob/dev/src/opengl/qgl.cpp
Martin Flöser's avatar
Martin Flöser committed
659 660
    if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
        // OpenGL gives RGBA; Qt wants ARGB
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
661
        uint *p = reinterpret_cast<uint *>(img.bits());
662
        uint *end = p + w * h;
Martin Flöser's avatar
Martin Flöser committed
663 664 665 666 667 668 669 670
        while (p < end) {
            uint a = *p << 24;
            *p = (*p >> 8) | a;
            p++;
        }
    } else {
        // OpenGL gives ABGR (i.e. RGBA backwards); Qt wants ARGB
        for (int y = 0; y < h; y++) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
671
            uint *q = reinterpret_cast<uint *>(img.scanLine(y));
672
            for (int x = 0; x < w; ++x) {
Martin Flöser's avatar
Martin Flöser committed
673 674
                const uint pixel = *q;
                *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff)
675
                     | (pixel & 0xff00ff00);
Martin Flöser's avatar
Martin Flöser committed
676 677 678 679 680 681 682 683 684

                q++;
            }
        }

    }
    img = img.mirrored();
}

685 686
bool ScreenShotEffect::isActive() const
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
687
    return (m_scheduledScreenshot != nullptr || !m_scheduledGeometry.isNull()) && !effects->isScreenLocked();
688 689
}

690 691 692
void ScreenShotEffect::windowClosed( EffectWindow* w )
{
    if (w == m_scheduledScreenshot) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
693
        m_scheduledScreenshot = nullptr;
694 695 696 697
        screenshotWindowUnderCursor(m_type);
    }
}

698 699 700 701 702 703 704 705 706 707 708 709 710 711
bool ScreenShotEffect::isTakingScreenshot() const
{
    if (!m_scheduledGeometry.isNull()) {
        return true;
    }
    if (m_windowMode != WindowMode::NoCapture) {
        return true;
    }
    if (m_fd != -1) {
        return true;
    }
    return false;
}

Martin Flöser's avatar
Martin Flöser committed
712
} // namespace