diff --git a/effects/screenshot/screenshot.cpp b/effects/screenshot/screenshot.cpp index 5a0c52eec..9de6201fe 100644 --- a/effects/screenshot/screenshot.cpp +++ b/effects/screenshot/screenshot.cpp @@ -1,335 +1,386 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Martin Gräßlin 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 . *********************************************************************/ #include "screenshot.h" #include #include #include #include #include #include #include #include #include #include namespace KWin { bool ScreenShotEffect::supported() { return effects->compositingType() == XRenderCompositing || (effects->isOpenGLCompositing() && GLRenderTarget::supported()); } ScreenShotEffect::ScreenShotEffect() : m_scheduledScreenshot(0) + , m_replyConnection(QDBusConnection::sessionBus()) { connect ( effects, SIGNAL(windowClosed(KWin::EffectWindow*)), SLOT(windowClosed(KWin::EffectWindow*)) ); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Screenshot"), this, QDBusConnection::ExportScriptableContents); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kwin.Screenshot")); } ScreenShotEffect::~ScreenShotEffect() { QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Screenshot")); QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.kwin.Screenshot")); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING static QImage xPictureToImage(xcb_render_picture_t srcPic, const QRect &geometry, xcb_image_t **xImage) { 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()); XRenderPicture pic(xpix, 32); xcb_render_composite(c, 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(c); *xImage = xcb_image_get(c, 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(c, xpix); return img; } #endif void ScreenShotEffect::postPaintScreen() { effects->postPaintScreen(); if (m_scheduledScreenshot) { 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; bool validTarget = true; QScopedPointer offscreenTexture; QScopedPointer target; if (effects->isOpenGLCompositing()) { offscreenTexture.reset(new GLTexture(GL_RGBA8, width, height)); offscreenTexture->setFilter(GL_LINEAR); offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); target.reset(new GLRenderTarget(*offscreenTexture)); validTarget = target->valid(); } if (validTarget) { d.setXTranslation(-m_scheduledScreenshot->x() - left); d.setYTranslation(-m_scheduledScreenshot->y() - top); // render window into offscreen texture int mask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_TRANSLUCENT; QImage img; if (effects->isOpenGLCompositing()) { GLRenderTarget::pushRenderTarget(target.data()); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0); QMatrix4x4 projection; projection.ortho(QRect(0, 0, offscreenTexture->width(), offscreenTexture->height())); d.setProjectionMatrix(projection); effects->drawWindow(m_scheduledScreenshot, mask, infiniteRegion(), d); // copy content from framebuffer into image img = QImage(QSize(width, height), QImage::Format_ARGB32); glReadnPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, img.byteCount(), (GLvoid*)img.bits()); GLRenderTarget::popRenderTarget(); ScreenShotEffect::convertFromGLImage(img, width, height); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING xcb_image_t *xImage = NULL; if (effects->compositingType() == XRenderCompositing) { setXRenderOffscreen(true); effects->drawWindow(m_scheduledScreenshot, mask, QRegion(0, 0, width, height), d); if (xRenderOffscreenTarget()) { img = xPictureToImage(xRenderOffscreenTarget(), QRect(0, 0, width, height), &xImage); } setXRenderOffscreen(false); } #endif if (m_type & INCLUDE_CURSOR) { grabPointerImage(img, m_scheduledScreenshot->x() + left, m_scheduledScreenshot->y() + top); } const int depth = img.depth(); xcb_pixmap_t xpix = xcb_generate_id(xcbConnection()); xcb_create_pixmap(xcbConnection(), depth, xpix, x11RootWindow(), img.width(), img.height()); xcb_gcontext_t cid = xcb_generate_id(xcbConnection()); xcb_create_gc(xcbConnection(), cid, xpix, 0, NULL); xcb_put_image(xcbConnection(), XCB_IMAGE_FORMAT_Z_PIXMAP, xpix, cid, img.width(), img.height(), 0, 0, 0, depth, img.byteCount(), img.constBits()); xcb_free_gc(xcbConnection(), cid); xcb_flush(xcbConnection()); emit screenshotCreated(xpix); #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (xImage) { xcb_image_destroy(xImage); } #endif } m_scheduledScreenshot = NULL; } + + if (!m_scheduledGeometry.isNull()) { + m_replyConnection.send(m_replyMessage.createReply(blitScreenshot(m_scheduledGeometry))); + m_scheduledGeometry = QRect(); + } } void ScreenShotEffect::screenshotWindowUnderCursor(int mask) { m_type = (ScreenShotType)mask; const QPoint cursor = effects->cursorPos(); 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; } if (m_scheduledScreenshot) { m_scheduledScreenshot->addRepaintFull(); } } 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(); } } QString ScreenShotEffect::screenshotFullscreen() { - return blitScreenshot(effects->virtualScreenGeometry()); + if (!calledFromDBus()) { + return QString(); + } + if (!m_scheduledGeometry.isNull()) { + sendErrorReply(QDBusError::Failed, "A screenshot is already been taken"); + return QString(); + } + m_replyConnection = connection(); + m_replyMessage = message(); + setDelayedReply(true); + m_scheduledGeometry = effects->virtualScreenGeometry(); + effects->addRepaintFull(); + return QString(); } QString ScreenShotEffect::screenshotScreen(int screen) { - return blitScreenshot(effects->clientArea(FullScreenArea, screen, 0)); + if (!calledFromDBus()) { + return QString(); + } + if (!m_scheduledGeometry.isNull()) { + sendErrorReply(QDBusError::Failed, "A screenshot is already been taken"); + return QString(); + } + m_scheduledGeometry = effects->clientArea(FullScreenArea, screen, 0); + if (m_scheduledGeometry.isNull()) { + sendErrorReply(QDBusError::Failed, "Invalid screen requested"); + return QString(); + } + m_replyConnection = connection(); + m_replyMessage = message(); + setDelayedReply(true); + effects->addRepaint(m_scheduledGeometry); + return QString(); } QString ScreenShotEffect::screenshotArea(int x, int y, int width, int height) { - return blitScreenshot(QRect(x, y, width, height)); + if (!calledFromDBus()) { + return QString(); + } + if (!m_scheduledGeometry.isNull()) { + sendErrorReply(QDBusError::Failed, "A screenshot is already been taken"); + return QString(); + } + m_scheduledGeometry = QRect(x, y, width, height); + if (m_scheduledGeometry.isNull() || m_scheduledGeometry.isEmpty()) { + m_scheduledGeometry = QRect(); + sendErrorReply(QDBusError::Failed, "Invalid area requested"); + return QString(); + } + m_replyConnection = connection(); + m_replyMessage = message(); + setDelayedReply(true); + effects->addRepaint(m_scheduledGeometry); + return QString(); } QString ScreenShotEffect::blitScreenshot(const QRect &geometry) { QImage img; if (effects->isOpenGLCompositing()) { if (!GLRenderTarget::blitSupported()) { qCDebug(KWINEFFECTS) << "Framebuffer Blit not supported"; return QString(); } GLTexture tex(GL_RGBA8, 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); if (GLPlatform::instance()->isGLES()) { glReadPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); } else { glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); } tex.unbind(); ScreenShotEffect::convertFromGLImage(img, geometry.width(), geometry.height()); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING xcb_image_t *xImage = NULL; #endif if (effects->compositingType() == XRenderCompositing) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING img = xPictureToImage(effects->xrenderBufferPicture(), geometry, &xImage); #endif } QTemporaryFile temp(QDir::tempPath() + QDir::separator() + QLatin1String("kwin_screenshot_XXXXXX.png")); temp.setAutoRemove(false); if (!temp.open()) { return QString(); } img.save(&temp); #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (xImage) { xcb_image_destroy(xImage); } #endif temp.close(); return temp.fileName(); } void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety) // Uses the X11_EXTENSIONS_XFIXES_H extension to grab the pointer image, and overlays it onto the snapshot. { QScopedPointer cursor( xcb_xfixes_get_cursor_image_reply(xcbConnection(), xcb_xfixes_get_cursor_image_unchecked(xcbConnection()), NULL)); if (cursor.isNull()) return; QImage qcursorimg((uchar *) xcb_xfixes_get_cursor_image_cursor_image(cursor.data()), cursor->width, cursor->height, QImage::Format_ARGB32_Premultiplied); QPainter painter(&snapshot); painter.drawImage(QPointF(cursor->x - cursor->xhot - offsetx, cursor->y - cursor ->yhot - offsety), qcursorimg); } 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(); uint *end = p + w * h; 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); for (int x = 0; x < w; ++x) { const uint pixel = *q; *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff) | (pixel & 0xff00ff00); q++; } } } img = img.mirrored(); } bool ScreenShotEffect::isActive() const { - return m_scheduledScreenshot != NULL && !effects->isScreenLocked(); + return (m_scheduledScreenshot != NULL || !m_scheduledGeometry.isNull()) && !effects->isScreenLocked(); } void ScreenShotEffect::windowClosed( EffectWindow* w ) { if (w == m_scheduledScreenshot) { m_scheduledScreenshot = NULL; screenshotWindowUnderCursor(m_type); } } } // namespace diff --git a/effects/screenshot/screenshot.h b/effects/screenshot/screenshot.h index dcb6149e2..8caae54c0 100644 --- a/effects/screenshot/screenshot.h +++ b/effects/screenshot/screenshot.h @@ -1,93 +1,99 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWIN_SCREENSHOT_H #define KWIN_SCREENSHOT_H #include +#include +#include +#include #include #include namespace KWin { -class ScreenShotEffect : public Effect +class ScreenShotEffect : public Effect, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Screenshot") public: enum ScreenShotType { INCLUDE_DECORATION = 1 << 0, INCLUDE_CURSOR = 1 << 1 }; ScreenShotEffect(); virtual ~ScreenShotEffect(); virtual void postPaintScreen(); virtual bool isActive() const; int requestedEffectChainPosition() const override { return 50; } static bool supported(); static void convertFromGLImage(QImage &img, int w, int h); public Q_SLOTS: Q_SCRIPTABLE void screenshotForWindow(qulonglong winid, int mask = 0); Q_SCRIPTABLE void screenshotWindowUnderCursor(int mask = 0); /** * Saves a screenshot of all screen into a file and returns the path to the file. * Functionality requires hardware support, if not available a null string is returned. * @returns Path to stored screenshot, or null string in failure case. **/ Q_SCRIPTABLE QString screenshotFullscreen(); /** * Saves a screenshot of the screen identified by @p screen into a file and returns the path to the file. * Functionality requires hardware support, if not available a null string is returned. * @param screen Number of screen as numbered by QDesktopWidget * @returns Path to stored screenshot, or null string in failure case. **/ Q_SCRIPTABLE QString screenshotScreen(int screen); /** * Saves a screenshot of the selected geometry into a file and returns the path to the file. * Functionality requires hardware support, if not available a null string is returned. * @param x Left upper x coord of region * @param y Left upper y coord of region * @param width Width of the region to screenshot * @param height Height of the region to screenshot * @returns Path to stored screenshot, or null string in failure case. **/ Q_SCRIPTABLE QString screenshotArea(int x, int y, int width, int height); Q_SIGNALS: Q_SCRIPTABLE void screenshotCreated(qulonglong handle); private Q_SLOTS: void windowClosed( KWin::EffectWindow* w ); private: void grabPointerImage(QImage& snapshot, int offsetx, int offsety); QString blitScreenshot(const QRect &geometry); EffectWindow *m_scheduledScreenshot; ScreenShotType m_type; + QRect m_scheduledGeometry; + QDBusConnection m_replyConnection; + QDBusMessage m_replyMessage; }; } // namespace #endif // KWIN_SCREENSHOT_H