screenshot.cpp 14.1 KB
Newer Older
Martin Flöser's avatar
Martin Flöser committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/********************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

 Copyright (C) 2010 Martin Gräßlin <kde@martin-graesslin.com>
 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 27 28
#include <QtDBus/QDBusConnection>
#include <QtCore/QVarLengthArray>
#include <QtGui/QPainter>
Martin Flöser's avatar
Martin Flöser committed
29
#include <xcb/xcb_image.h>
Martin Flöser's avatar
Martin Flöser committed
30 31 32 33

namespace KWin
{

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

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

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

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

#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
74
void ScreenShotEffect::postPaintScreen()
75
{
Martin Flöser's avatar
Martin Flöser committed
76
    effects->postPaintScreen();
77
    if (m_scheduledScreenshot) {
78 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
        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;
110
        bool validTarget = true;
111 112
        QScopedPointer<GLTexture> offscreenTexture;
        QScopedPointer<GLRenderTarget> target;
113
        if (effects->isOpenGLCompositing()) {
114 115
            int w = width;
            int h = height;
116 117 118 119
            if (!GLTexture::NPOTTextureSupported()) {
                w = nearestPowerOfTwo(w);
                h = nearestPowerOfTwo(h);
            }
120
            offscreenTexture.reset(new GLTexture(w, h));
121 122
            offscreenTexture->setFilter(GL_LINEAR);
            offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
123
            target.reset(new GLRenderTarget(*offscreenTexture));
124
            validTarget = target->valid();
125
        }
126
        if (validTarget) {
127 128
            d.setXTranslation(-m_scheduledScreenshot->x() - left);
            d.setYTranslation(-m_scheduledScreenshot->y() - top);
129

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

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

Martin Flöser's avatar
Martin Flöser committed
163 164 165 166 167 168 169 170 171 172 173 174 175 176
            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);
177
            }
Martin Flöser's avatar
Martin Flöser committed
178
#endif
179
        }
Martin Flöser's avatar
Martin Flöser committed
180 181
        m_scheduledScreenshot = NULL;
    }
182
}
Martin Flöser's avatar
Martin Flöser committed
183

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
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
    }
}

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

247 248 249 250 251 252 253 254 255 256
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();
    }
}

257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
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)
276
    kDebug(1212) << "Framebuffer Blit not supported";
277 278
    return QString();
#else
279
    QImage img;
280
    if (effects->isOpenGLCompositing())
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
    {
        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
297 298 299
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    xcb_image_t *xImage = NULL;
#endif
300
    if (effects->compositingType() == XRenderCompositing) {
301
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
Martin Flöser's avatar
Martin Flöser committed
302
        img = xPictureToImage(effects->xrenderBufferPicture(), geometry, &xImage);
303
#endif
304 305
    }

306 307 308 309 310 311 312
    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
313 314 315 316 317
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    if (xImage) {
        xcb_image_destroy(xImage);
    }
#endif
318 319 320 321
    temp.close();
    return temp.fileName();
#endif
}
Martin Flöser's avatar
Martin Flöser committed
322

323
void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety)
324 325
// 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
326 327 328 329 330
    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())
331
        return;
332

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

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

Martin Flöser's avatar
Martin Flöser committed
340 341 342 343 344 345 346 347
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();
348
        uint *end = p + w * h;
Martin Flöser's avatar
Martin Flöser committed
349 350 351 352 353 354 355 356 357
        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);
358
            for (int x = 0; x < w; ++x) {
Martin Flöser's avatar
Martin Flöser committed
359 360
                const uint pixel = *q;
                *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff)
361
                     | (pixel & 0xff00ff00);
Martin Flöser's avatar
Martin Flöser committed
362 363 364 365 366 367 368 369 370

                q++;
            }
        }

    }
    img = img.mirrored();
}

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

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

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