screenshot.cpp 14.1 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 22
 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"
#include <kwinglutils.h>
23
#include <kwinxrenderutils.h>
24
#include <KDE/KDebug>
25
#include <KDE/KTemporaryFile>
26
#include <QtDBus/QDBusConnection>
27
#include <QVarLengthArray>
28
#include <QtGui/QPainter>
29
#include <QMatrix4x4>
Martin Flöser's avatar
Martin Flöser committed
30
#include <xcb/xcb_image.h>
Martin Flöser's avatar
Martin Flöser committed
31 32 33 34

namespace KWin
{

35 36
KWIN_EFFECT(screenshot, ScreenShotEffect)
KWIN_EFFECT_SUPPORTED(screenshot, ScreenShotEffect::supported())
Martin Flöser's avatar
Martin Flöser committed
37 38

bool ScreenShotEffect::supported()
39
{
40
    return  effects->compositingType() == XRenderCompositing ||
41
            (effects->isOpenGLCompositing() && GLRenderTarget::supported());
42
}
Martin Flöser's avatar
Martin Flöser committed
43 44

ScreenShotEffect::ScreenShotEffect()
45 46
    : m_scheduledScreenshot(0)
{
47
    connect ( effects, SIGNAL(windowClosed(KWin::EffectWindow*)), SLOT(windowClosed(KWin::EffectWindow*)) );
48 49 50
    QDBusConnection::sessionBus().registerObject("/Screenshot", this, QDBusConnection::ExportScriptableContents);
    QDBusConnection::sessionBus().registerService("org.kde.kwin.Screenshot");
}
Martin Flöser's avatar
Martin Flöser committed
51 52

ScreenShotEffect::~ScreenShotEffect()
53 54 55 56
{
    QDBusConnection::sessionBus().unregisterObject("/Screenshot");
    QDBusConnection::sessionBus().unregisterService("org.kde.kwin.Screenshot");
}
Martin Flöser's avatar
Martin Flöser committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

#ifdef KWIN_HAVE_XRENDER_COMPOSITING
static QImage xPictureToImage(xcb_render_picture_t srcPic, const QRect &geometry, xcb_image_t **xImage)
{
    xcb_pixmap_t xpix = xcb_generate_id(connection());
    xcb_create_pixmap(connection(), 32, xpix, rootWindow(), geometry.width(), geometry.height());
    XRenderPicture pic(xpix, 32);
    xcb_render_composite(connection(), XCB_RENDER_PICT_OP_SRC, srcPic, XCB_RENDER_PICTURE_NONE, pic,
                         geometry.x(), geometry.y(), 0, 0, 0, 0, geometry.width(), geometry.height());
    xcb_flush(connection());
    *xImage = xcb_image_get(connection(), xpix, 0, 0, geometry.width(), geometry.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP);
    QImage img((*xImage)->data, (*xImage)->width, (*xImage)->height, (*xImage)->stride, QImage::Format_ARGB32_Premultiplied);
    // TODO: byte order might need swapping
    xcb_free_pixmap(connection(), xpix);
    return img;
}
#endif

Martin Flöser's avatar
Martin Flöser committed
75
void ScreenShotEffect::postPaintScreen()
76
{
Martin Flöser's avatar
Martin Flöser committed
77
    effects->postPaintScreen();
78
    if (m_scheduledScreenshot) {
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
        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;
111
        bool validTarget = true;
112 113
        QScopedPointer<GLTexture> offscreenTexture;
        QScopedPointer<GLRenderTarget> target;
114
        if (effects->isOpenGLCompositing()) {
115 116
            int w = width;
            int h = height;
117 118 119 120
            if (!GLTexture::NPOTTextureSupported()) {
                w = nearestPowerOfTwo(w);
                h = nearestPowerOfTwo(h);
            }
121
            offscreenTexture.reset(new GLTexture(w, h));
122 123
            offscreenTexture->setFilter(GL_LINEAR);
            offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
124
            target.reset(new GLRenderTarget(*offscreenTexture));
125
            validTarget = target->valid();
126
        }
127
        if (validTarget) {
128 129
            d.setXTranslation(-m_scheduledScreenshot->x() - left);
            d.setYTranslation(-m_scheduledScreenshot->y() - top);
130

Martin Flöser's avatar
Martin Flöser committed
131 132
            // render window into offscreen texture
            int mask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_TRANSLUCENT;
133
            QImage img;
134
            if (effects->isOpenGLCompositing()) {
135
                GLRenderTarget::pushRenderTarget(target.data());
136 137 138
                glClearColor(0.0, 0.0, 0.0, 0.0);
                glClear(GL_COLOR_BUFFER_BIT);
                glClearColor(0.0, 0.0, 0.0, 1.0);
139 140 141
                setMatrix(offscreenTexture->width(), offscreenTexture->height());
                effects->drawWindow(m_scheduledScreenshot, mask, infiniteRegion(), d);
                restoreMatrix();
142 143
                // copy content from framebuffer into image
                img = QImage(QSize(width, height), QImage::Format_ARGB32);
144
                glReadnPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, img.byteCount(), (GLvoid*)img.bits());
145 146 147
                GLRenderTarget::popRenderTarget();
                ScreenShotEffect::convertFromGLImage(img, width, height);
            }
148
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
Martin Flöser's avatar
Martin Flöser committed
149
            xcb_image_t *xImage = NULL;
150 151 152
            if (effects->compositingType() == XRenderCompositing) {
                setXRenderOffscreen(true);
                effects->drawWindow(m_scheduledScreenshot, mask, QRegion(0, 0, width, height), d);
153
                if (xRenderOffscreenTarget()) {
Martin Flöser's avatar
Martin Flöser committed
154
                    img = xPictureToImage(xRenderOffscreenTarget(), QRect(0, 0, width, height), &xImage);
155
                }
156 157
                setXRenderOffscreen(false);
            }
158
#endif
159

160 161
            if (m_type & INCLUDE_CURSOR) {
                grabPointerImage(img, m_scheduledScreenshot->x() + left, m_scheduledScreenshot->y() + top);
Martin Flöser's avatar
Martin Flöser committed
162
            }
163

Martin Flöser's avatar
Martin Flöser committed
164 165 166 167 168 169 170 171 172 173 174 175 176 177
            const int depth = img.depth();
            xcb_pixmap_t xpix = xcb_generate_id(connection());
            xcb_create_pixmap(connection(), depth, xpix, rootWindow(), img.width(), img.height());

            xcb_gcontext_t cid = xcb_generate_id(connection());
            xcb_create_gc(connection(), cid, xpix, 0, NULL);
            xcb_put_image(connection(), XCB_IMAGE_FORMAT_Z_PIXMAP, xpix, cid, img.width(), img.height(),
                        0, 0, 0, depth, img.byteCount(), img.constBits());
            xcb_free_gc(connection(), cid);
            xcb_flush(connection());
            emit screenshotCreated(xpix);
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
            if (xImage) {
                xcb_image_destroy(xImage);
178
            }
Martin Flöser's avatar
Martin Flöser committed
179
#endif
180
        }
Martin Flöser's avatar
Martin Flöser committed
181 182
        m_scheduledScreenshot = NULL;
    }
183
}
Martin Flöser's avatar
Martin Flöser committed
184

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
static QMatrix4x4 s_origProjection;
static QMatrix4x4 s_origModelview;

void ScreenShotEffect::setMatrix(int width, int height)
{
    QMatrix4x4 projection;
    QMatrix4x4 identity;
    projection.ortho(QRect(0, 0, width, height));
    if (effects->compositingType() == OpenGL2Compositing) {
        ShaderBinder binder(ShaderManager::GenericShader);
        GLShader *shader = binder.shader();
        s_origProjection = shader->getUniformMatrix4x4("projection");
        s_origModelview = shader->getUniformMatrix4x4("modelview");
        shader->setUniform(GLShader::ProjectionMatrix, projection);
        shader->setUniform(GLShader::ModelViewMatrix, identity);
    } else if (effects->compositingType() == OpenGL1Compositing) {
#ifdef KWIN_HAVE_OPENGL_1
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        loadMatrix(projection);
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();
#endif
    }
}

void ScreenShotEffect::restoreMatrix()
{
    if (effects->compositingType() == OpenGL2Compositing) {
        ShaderBinder binder(ShaderManager::GenericShader);
        GLShader *shader = binder.shader();
        shader->setUniform(GLShader::ProjectionMatrix, s_origProjection);
        shader->setUniform(GLShader::ModelViewMatrix, s_origModelview);
    } else if (effects->compositingType() == OpenGL1Compositing) {
#ifdef KWIN_HAVE_OPENGL_1
        glMatrixMode(GL_PROJECTION);
        glPopMatrix();
        glMatrixMode(GL_MODELVIEW);
        glPopMatrix();
#endif
    }
}

229
void ScreenShotEffect::screenshotWindowUnderCursor(int mask)
230
{
231 232
    m_type = (ScreenShotType)mask;
    const QPoint cursor = effects->cursorPos();
233 234 235 236 237 238 239 240 241
    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;
        m_scheduledScreenshot = 0;
242 243
    }
    if (m_scheduledScreenshot) {
244
        m_scheduledScreenshot->addRepaintFull();
Martin Flöser's avatar
Martin Flöser committed
245
    }
246
}
247

248 249 250 251 252 253 254 255 256 257
void ScreenShotEffect::screenshotForWindow(qulonglong winid, int mask)
{
    m_type = (ScreenShotType) mask;
    EffectWindow* w = effects->findWindow(winid);
    if(w && !w->isMinimized() && !w->isDeleted()) {
        m_scheduledScreenshot = w;
        m_scheduledScreenshot->addRepaintFull();
    }
}

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
QString ScreenShotEffect::screenshotFullscreen()
{
    return blitScreenshot(QRect(0, 0, displayWidth(), displayHeight()));
}

QString ScreenShotEffect::screenshotScreen(int screen)
{
    return blitScreenshot(effects->clientArea(FullScreenArea, screen, 0));
}

QString ScreenShotEffect::screenshotArea(int x, int y, int width, int height)
{
    return blitScreenshot(QRect(x, y, width, height));
}

QString ScreenShotEffect::blitScreenshot(const QRect &geometry)
{
#ifdef KWIN_HAVE_OPENGLES
    Q_UNUSED(geometry)
277
    kDebug(1212) << "Framebuffer Blit not supported";
278 279
    return QString();
#else
280
    QImage img;
281
    if (effects->isOpenGLCompositing())
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
    {
        if (!GLRenderTarget::blitSupported()) {
            kDebug(1212) << "Framebuffer Blit not supported";
            return QString();
        }
        GLTexture tex(geometry.width(), geometry.height());
        GLRenderTarget target(tex);
        target.blitFromFramebuffer(geometry);
        // copy content from framebuffer into image
        tex.bind();
        img = QImage(geometry.size(), QImage::Format_ARGB32);
        glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits());
        tex.unbind();
        ScreenShotEffect::convertFromGLImage(img, geometry.width(), geometry.height());
    }

Martin Flöser's avatar
Martin Flöser committed
298 299 300
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    xcb_image_t *xImage = NULL;
#endif
301
    if (effects->compositingType() == XRenderCompositing) {
302
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
Martin Flöser's avatar
Martin Flöser committed
303
        img = xPictureToImage(effects->xrenderBufferPicture(), geometry, &xImage);
304
#endif
305 306
    }

307 308 309 310 311 312 313
    KTemporaryFile temp;
    temp.setSuffix(".png");
    temp.setAutoRemove(false);
    if (!temp.open()) {
        return QString();
    }
    img.save(&temp);
Martin Flöser's avatar
Martin Flöser committed
314 315 316 317 318
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    if (xImage) {
        xcb_image_destroy(xImage);
    }
#endif
319 320 321 322
    temp.close();
    return temp.fileName();
#endif
}
Martin Flöser's avatar
Martin Flöser committed
323

324
void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety)
325 326
// Uses the X11_EXTENSIONS_XFIXES_H extension to grab the pointer image, and overlays it onto the snapshot.
{
Martin Flöser's avatar
Martin Flöser committed
327 328 329 330 331
    QScopedPointer<xcb_xfixes_get_cursor_image_reply_t, QScopedPointerPodDeleter> cursor(
        xcb_xfixes_get_cursor_image_reply(connection(),
                                          xcb_xfixes_get_cursor_image_unchecked(connection()),
                                          NULL));
    if (cursor.isNull())
332
        return;
333

Martin Flöser's avatar
Martin Flöser committed
334
    QImage qcursorimg((uchar *) xcb_xfixes_get_cursor_image_cursor_image(cursor.data()), cursor->width, cursor->height,
335
                      QImage::Format_ARGB32_Premultiplied);
336 337

    QPainter painter(&snapshot);
Martin Flöser's avatar
Martin Flöser committed
338
    painter.drawImage(QPointF(cursor->x - cursor->xhot - offsetx, cursor->y - cursor ->yhot - offsety), qcursorimg);
339 340
}

Martin Flöser's avatar
Martin Flöser committed
341 342 343 344 345 346 347 348
void ScreenShotEffect::convertFromGLImage(QImage &img, int w, int h)
{
    // from QtOpenGL/qgl.cpp
    // Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
    // see http://qt.gitorious.org/qt/qt/blobs/master/src/opengl/qgl.cpp
    if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
        // OpenGL gives RGBA; Qt wants ARGB
        uint *p = (uint*)img.bits();
349
        uint *end = p + w * h;
Martin Flöser's avatar
Martin Flöser committed
350 351 352 353 354 355 356 357 358
        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++) {
            uint *q = (uint*)img.scanLine(y);
359
            for (int x = 0; x < w; ++x) {
Martin Flöser's avatar
Martin Flöser committed
360 361
                const uint pixel = *q;
                *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff)
362
                     | (pixel & 0xff00ff00);
Martin Flöser's avatar
Martin Flöser committed
363 364 365 366 367 368 369 370 371

                q++;
            }
        }

    }
    img = img.mirrored();
}

372 373
bool ScreenShotEffect::isActive() const
{
374
    return m_scheduledScreenshot != NULL && !effects->isScreenLocked();
375 376
}

377 378 379 380 381 382 383 384
void ScreenShotEffect::windowClosed( EffectWindow* w )
{
    if (w == m_scheduledScreenshot) {
        m_scheduledScreenshot = NULL;
        screenshotWindowUnderCursor(m_type);
    }
}

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