screenshot.cpp 10.3 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 <KDE/KDebug>
24
#include <KDE/KTemporaryFile>
25 26 27 28 29 30
#include <QtDBus/QDBusConnection>
#include <QtCore/QVarLengthArray>
#include <QtGui/QPainter>

#include <X11/extensions/Xfixes.h>
#include <QX11Info>
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
{
Martin Flöser's avatar
Martin Flöser committed
40
    return effects->compositingType() == KWin::OpenGLCompositing && 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
void ScreenShotEffect::postPaintScreen()
57
{
Martin Flöser's avatar
Martin Flöser committed
58
    effects->postPaintScreen();
59
    if (m_scheduledScreenshot) {
Martin Flöser's avatar
Martin Flöser committed
60 61
        int w = displayWidth();
        int h = displayHeight();
62 63 64 65 66 67 68
        if (!GLTexture::NPOTTextureSupported()) {
            w = nearestPowerOfTwo(w);
            h = nearestPowerOfTwo(h);
        }
        GLTexture* offscreenTexture = new GLTexture(w, h);
        offscreenTexture->setFilter(GL_LINEAR);
        offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
69
        GLRenderTarget* target = new GLRenderTarget(*offscreenTexture);
70 71
        if (target->valid()) {
            WindowPaintData d(m_scheduledScreenshot);
Martin Flöser's avatar
Martin Flöser committed
72 73 74 75
            double left = 0;
            double top = 0;
            double right = m_scheduledScreenshot->width();
            double bottom = m_scheduledScreenshot->height();
76 77
            if (m_scheduledScreenshot->hasDecoration() && m_type & INCLUDE_DECORATION) {
                foreach (const WindowQuad & quad, d.quads) {
78 79 80 81 82 83
                    // 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());
                }
84
            } else if (m_scheduledScreenshot->hasDecoration()) {
85 86 87 88 89
                WindowQuadList newQuads;
                left = m_scheduledScreenshot->width();
                top = m_scheduledScreenshot->height();
                right = 0;
                bottom = 0;
90 91
                foreach (const WindowQuad & quad, d.quads) {
                    if (quad.type() == WindowQuadContents) {
92 93 94 95 96 97
                        newQuads << quad;
                        left   = qMin(left, quad.left());
                        top    = qMin(top, quad.top());
                        right  = qMax(right, quad.right());
                        bottom = qMax(bottom, quad.bottom());
                    }
Martin Flöser's avatar
Martin Flöser committed
98
                }
99 100
                d.quads = newQuads;
            }
Martin Flöser's avatar
Martin Flöser committed
101 102 103 104 105 106
            int width = right - left;
            int height = bottom - top;
            d.xTranslate = -m_scheduledScreenshot->x() - left;
            d.yTranslate = -m_scheduledScreenshot->y() - top;
            // render window into offscreen texture
            int mask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_TRANSLUCENT;
107
            GLRenderTarget::pushRenderTarget(target);
108
            glClearColor(0.0, 0.0, 0.0, 0.0);
109
            glClear(GL_COLOR_BUFFER_BIT);
110
            glClearColor(0.0, 0.0, 0.0, 1.0);
111
            effects->drawWindow(m_scheduledScreenshot, mask, QRegion(0, 0, width, height), d);
112
            // copy content from framebuffer into image
113
            QImage img(QSize(width, height), QImage::Format_ARGB32);
114
            glReadPixels(0, offscreenTexture->height() - height, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits());
115
            GLRenderTarget::popRenderTarget();
116 117 118
            ScreenShotEffect::convertFromGLImage(img, width, height);
            if (m_type & INCLUDE_CURSOR) {
                grabPointerImage(img, m_scheduledScreenshot->x() + left, m_scheduledScreenshot->y() + top);
Martin Flöser's avatar
Martin Flöser committed
119
            }
120
            m_lastScreenshot = QPixmap::fromImage(img);
121 122
            if (m_lastScreenshot.handle() == 0) {
                Pixmap xpix = XCreatePixmap(display(), rootWindow(), m_lastScreenshot.width(),
123
                                            m_lastScreenshot.height(), 32);
124 125
                m_lastScreenshot = QPixmap::fromX11Pixmap(xpix, QPixmap::ExplicitlyShared);
                QPainter p(&m_lastScreenshot);
126
                p.setCompositionMode(QPainter::CompositionMode_Source);
127 128
                p.drawImage(QPoint(0, 0), img);
            }
129 130
            emit screenshotCreated(m_lastScreenshot.handle());
        }
Martin Flöser's avatar
Martin Flöser committed
131 132 133 134
        delete offscreenTexture;
        delete target;
        m_scheduledScreenshot = NULL;
    }
135
}
Martin Flöser's avatar
Martin Flöser committed
136

137
void ScreenShotEffect::screenshotWindowUnderCursor(int mask)
138
{
139 140
    m_type = (ScreenShotType)mask;
    const QPoint cursor = effects->cursorPos();
141 142 143 144 145 146 147 148 149
    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;
150 151
    }
    if (m_scheduledScreenshot) {
152
        m_scheduledScreenshot->addRepaintFull();
Martin Flöser's avatar
Martin Flöser committed
153
    }
154
}
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192

QString ScreenShotEffect::screenshotFullscreen()
{
    if (!GLRenderTarget::blitSupported()) {
        kDebug(1212) << "Framebuffer Blit not supported";
        return QString();
    }

    return blitScreenshot(QRect(0, 0, displayWidth(), displayHeight()));
}

QString ScreenShotEffect::screenshotScreen(int screen)
{
    if (!GLRenderTarget::blitSupported()) {
        kDebug(1212) << "Framebuffer Blit not supported";
        return QString();
    }

    return blitScreenshot(effects->clientArea(FullScreenArea, screen, 0));
}

QString ScreenShotEffect::screenshotArea(int x, int y, int width, int height)
{
    if (!GLRenderTarget::blitSupported()) {
        kDebug(1212) << "Framebuffer Blit not supported";
        return QString();
    }

    return blitScreenshot(QRect(x, y, width, height));
}

QString ScreenShotEffect::blitScreenshot(const QRect &geometry)
{
#ifdef KWIN_HAVE_OPENGLES
    Q_UNUSED(geometry)
    return QString();
#else
    GLTexture tex(geometry.width(), geometry.height());
193
    GLRenderTarget target(tex);
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    target.blitFromFramebuffer(geometry);
    // copy content from framebuffer into image
    tex.bind();
    QImage img(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());
    KTemporaryFile temp;
    temp.setSuffix(".png");
    temp.setAutoRemove(false);
    if (!temp.open()) {
        return QString();
    }
    img.save(&temp);
    temp.close();
    return temp.fileName();
#endif
}
Martin Flöser's avatar
Martin Flöser committed
212

213
void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety)
214 215
// Uses the X11_EXTENSIONS_XFIXES_H extension to grab the pointer image, and overlays it onto the snapshot.
{
216 217 218
    XFixesCursorImage *xcursorimg = XFixesGetCursorImage(QX11Info::display());
    if (!xcursorimg)
        return;
219 220 221 222

    //Annoyingly, xfixes specifies the data to be 32bit, but places it in an unsigned long *
    //which can be 64 bit.  So we need to iterate over a 64bit structure to put it in a 32bit
    //structure.
223
    QVarLengthArray< quint32 > pixels(xcursorimg->width * xcursorimg->height);
224 225 226 227
    for (int i = 0; i < xcursorimg->width * xcursorimg->height; ++i)
        pixels[i] = xcursorimg->pixels[i] & 0xffffffff;

    QImage qcursorimg((uchar *) pixels.data(), xcursorimg->width, xcursorimg->height,
228
                      QImage::Format_ARGB32_Premultiplied);
229 230 231 232 233 234 235

    QPainter painter(&snapshot);
    painter.drawImage(QPointF(xcursorimg->x - xcursorimg->xhot - offsetx, xcursorimg->y - xcursorimg ->yhot - offsety), qcursorimg);

    XFree(xcursorimg);
}

Martin Flöser's avatar
Martin Flöser committed
236 237 238 239 240 241 242 243
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();
244
        uint *end = p + w * h;
Martin Flöser's avatar
Martin Flöser committed
245 246 247 248 249 250 251 252 253
        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);
254
            for (int x = 0; x < w; ++x) {
Martin Flöser's avatar
Martin Flöser committed
255 256
                const uint pixel = *q;
                *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff)
257
                     | (pixel & 0xff00ff00);
Martin Flöser's avatar
Martin Flöser committed
258 259 260 261 262 263 264 265 266

                q++;
            }
        }

    }
    img = img.mirrored();
}

267 268 269 270 271
bool ScreenShotEffect::isActive() const
{
    return m_scheduledScreenshot != NULL;
}

272 273 274 275 276 277 278 279
void ScreenShotEffect::windowClosed( EffectWindow* w )
{
    if (w == m_scheduledScreenshot) {
        m_scheduledScreenshot = NULL;
        screenshotWindowUnderCursor(m_type);
    }
}

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