effects/screenshot: Prepare for versioned dbus interface

On Wayland, when the compositor sends a screenshot to the requesting
app, it encodes the screenshot as a PNG image and sends the encoded data
over the pipe. The requesting app (Spectacle) then needs to decode the
data.

The issue is that encoding PNG images is not cheap. This is the main
reason why Spectacle is shown with a huge delay after you press the
PrtScr key.

In order to fix the latency issue, we need to transfer raw image data.
Unfortunately, the current dbus api of the screenshot is too cluttered
and the best option at the moment is to start with a clean slate.

This change prepares the screenshot effect for versioned dbus interface.
Most of existing dbus logic was moved out in a separate class. In order
to schedule screen shots, the screenshot effect got some new API.

    QFuture<QImage> scheduleScreenShot(window, flags)
    QFuture<QImage> scheduleScreenShot(area, flags)
    QFuture<QImage> scheduleScreenShot(screen, flags)

If a dbus interface needs to take a screenshot, it needs to call one of
the overloaded scheduleScreenShot() functions. Every overload returns a
QFuture object that can be used for querying the result.

This change also introduces "sink" and "source" objects in the dbus api
implementation to simplify handling of QFuture objects.

Note that the QFutureInterface is undocumented, so if you use it, you do
it on your own risk. However, since Qt 5.15 is frozen for non-commercial
use and some other Plasma projects already use QFutureInterface, this
is not a big concern. For what it's worth, in Qt 6, there's the QPromise
class, which is equivalent to the QFutureInterface class.

CCBUG: 433776
CCBUG: 430869
This commit is contained in:
Vlad Zahorodnii 2021-03-01 13:58:57 +02:00
parent 38996d9725
commit 1fb44b5bd5
5 changed files with 1522 additions and 960 deletions

View file

@ -5,4 +5,5 @@
set(kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} set(kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources}
../service_utils.cpp ../service_utils.cpp
screenshot/screenshot.cpp screenshot/screenshot.cpp
screenshot/screenshotdbusinterface1.cpp
) )

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@
This file is part of the KDE project. This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Martin Gräßlin <mgraesslin@kde.org> SPDX-FileCopyrightText: 2010 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later SPDX-License-Identifier: GPL-2.0-or-later
*/ */
@ -11,175 +12,95 @@
#define KWIN_SCREENSHOT_H #define KWIN_SCREENSHOT_H
#include <kwineffects.h> #include <kwineffects.h>
#include <QDBusContext>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusUnixFileDescriptor>
#include <QObject>
#include <QImage>
class ComparableQPoint; #include <QFuture>
#include <QFutureInterface>
#include <QImage>
#include <QObject>
namespace KWin namespace KWin
{ {
/** /**
* The screenshot effet allows to takes screenshot, by window, area, screen, etc... * This enum type is used to specify how a screenshot needs to be taken.
*
* A using application must have "org.kde.kwin.Screenshot" in its X-KDE-DBUS-Restricted-Interfaces application service file field.
*/ */
class ScreenShotEffect : public Effect, protected QDBusContext enum ScreenShotFlag {
ScreenShotIncludeDecoration = 0x1, ///< Include window titlebar and borders
ScreenShotIncludeCursor = 0x2, ///< Include the cursor
ScreenShotNativeResolution = 0x4, ///< Take the screenshot at the native resolution
};
Q_DECLARE_FLAGS(ScreenShotFlags, ScreenShotFlag)
class ScreenShotDBusInterface1;
struct ScreenShotWindowData;
struct ScreenShotAreaData;
struct ScreenShotScreenData;
/**
* The screenshot effet allows to takes screenshot, by window, area, screen, etc...
*/
class ScreenShotEffect : public Effect
{ {
Q_OBJECT Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Screenshot")
public: public:
enum ScreenShotType {
INCLUDE_DECORATION = 1 << 0,
INCLUDE_CURSOR = 1 << 1
};
ScreenShotEffect(); ScreenShotEffect();
~ScreenShotEffect() override; ~ScreenShotEffect() override;
/**
* Schedules a screenshot of the given @a screen. The returned QFuture can be used to query
* the image data. If the screen is removed before the screenshot is taken, the future will
* be cancelled.
*/
QFuture<QImage> scheduleScreenShot(EffectScreen *screen, ScreenShotFlags flags = {});
/**
* Schedules a screenshot of the given @a area. The returned QFuture can be used to query the
* image data.
*/
QFuture<QImage> scheduleScreenShot(const QRect &area, ScreenShotFlags flags = {});
/**
* Schedules a screenshot of the given @a window. The returned QFuture can be used to query
* the image data. If the window is removed before the screenshot is taken, the future will
* be cancelled.
*/
QFuture<QImage> scheduleScreenShot(EffectWindow *window, ScreenShotFlags flags = {});
void paintScreen(int mask, const QRegion &region, ScreenPaintData &data) override; void paintScreen(int mask, const QRegion &region, ScreenPaintData &data) override;
void postPaintScreen() override; void postPaintScreen() override;
bool isActive() const override; bool isActive() const override;
int requestedEffectChainPosition() const override;
int requestedEffectChainPosition() const override {
return 50;
}
static bool supported(); static bool supported();
static void convertFromGLImage(QImage &img, int w, int h);
void scheduleScreenshotWindowUnderCursor();
public Q_SLOTS:
Q_SCRIPTABLE void screenshotForWindow(qulonglong winid, int mask = 0);
/**
* Starts an interactive window screenshot session. The user can select a window to
* screenshot.
*
* Once the window is selected the screenshot is saved into a file and the path gets
* returned to the DBus peer.
*
* @param mask The mask for what to include in the screenshot
*/
Q_SCRIPTABLE QString interactive(int mask = 0);
/**
* Starts an interactive window screenshot session. The user can select a window to
* screenshot.
*
* Once the window is selected the screenshot is saved into the @p fd passed to the
* method. It is intended to be used with a pipe, so that the invoking side can just
* read from the pipe. The image gets written into the fd using a QDataStream.
*
* @param fd File descriptor into which the screenshot should be saved
* @param mask The mask for what to include in the screenshot
*/
Q_SCRIPTABLE void interactive(QDBusUnixFileDescriptor fd, 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.
* @param captureCursor Whether to include the cursor in the image
* @returns Path to stored screenshot, or null string in failure case.
*/
Q_SCRIPTABLE QString screenshotFullscreen(bool captureCursor = false);
/**
* Takes a full screen screenshot in a one file format.
*
* Once the screenshot is taken it gets saved into the @p fd passed to the
* method. It is intended to be used with a pipe, so that the invoking side can just
* read from the pipe. The image gets written into the fd using a QDataStream.
*
* @param fd File descriptor into which the screenshot should be saved
* @param captureCursor Whether to include the mouse cursor
* @param shouldReturnNativeSize Whether to return an image according to the virtualGeometry, or according to pixel on screen size
*/
Q_SCRIPTABLE void screenshotFullscreen(QDBusUnixFileDescriptor fd, bool captureCursor = false, bool shouldReturnNativeSize = false);
/**
* Take a screenshot of the passed screens and return a QList<QImage> in the fd response,
* an image for each screen in pixel-on-screen size when shouldReturnNativeSize is passed, or converted to using logicale size if not
*
* @param fd
* @param screensNames the names of the screens whose screenshot to return
* @param captureCursor
* @param shouldReturnNativeSize
*/
Q_SCRIPTABLE void screenshotScreens(QDBusUnixFileDescriptor fd, const QStringList &screensNames, bool captureCursor = false, bool shouldReturnNativeSize = false);
/**
* 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
* @param captureCursor Whether to include the cursor in the image
* @returns Path to stored screenshot, or null string in failure case.
*/
Q_SCRIPTABLE QString screenshotScreen(int screen, bool captureCursor = false);
/**
* Starts an interactive screenshot of a screen session.
*
* The user is asked to select the screen to screenshot.
*
* Once the screenshot is taken it gets saved into the @p fd passed to the
* method. It is intended to be used with a pipe, so that the invoking side can just
* read from the pipe. The image gets written into the fd using a QDataStream.
*
* @param fd File descriptor into which the screenshot should be saved
* @param captureCursor Whether to include the mouse cursor
*/
Q_SCRIPTABLE void screenshotScreen(QDBusUnixFileDescriptor fd, bool captureCursor = false);
/**
* 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
* @param captureCursor Whether to include the cursor in the image
* @returns Path to stored screenshot, or null string in failure case.
*/
Q_SCRIPTABLE QString screenshotArea(int x, int y, int width, int height, bool captureCursor = false);
Q_SIGNALS:
Q_SCRIPTABLE void screenshotCreated(qulonglong handle);
private Q_SLOTS: private Q_SLOTS:
void windowClosed( KWin::EffectWindow* w ); void handleWindowClosed(EffectWindow *window);
void handleScreenAdded();
void handleScreenRemoved(EffectScreen *screen);
private: private:
void takeScreenShot(ScreenShotWindowData *screenshot);
bool takeScreenShot(ScreenShotAreaData *screenshot);
bool takeScreenShot(ScreenShotScreenData *screenshot);
void cancelWindowScreenShots();
void cancelAreaScreenShots();
void cancelScreenScreenShots();
void grabPointerImage(QImage& snapshot, int offsetx, int offsety); void grabPointerImage(QImage& snapshot, int offsetx, int offsety);
QImage blitScreenshot(const QRect &geometry, const qreal scale = 1.0); QImage blitScreenshot(const QRect &geometry, const qreal scale = 1.0);
QString saveTempImage(const QImage &img);
void sendReplyImage(const QImage &img);
void sendReplyImages();
void clearState();
enum class InfoMessageMode {
Window,
Screen
};
void showInfoMessage(InfoMessageMode mode);
void hideInfoMessage();
bool isTakingScreenshot() const;
void computeCoordinatesAfterScaling();
bool checkCall() const;
EffectWindow *m_scheduledScreenshot; QVector<ScreenShotWindowData> m_windowScreenShots;
ScreenShotType m_type; QVector<ScreenShotAreaData> m_areaScreenShots;
QRect m_scheduledGeometry; QVector<ScreenShotScreenData> m_screenScreenShots;
QDBusMessage m_replyMessage;
QRegion m_multipleOutputsRendered; QScopedPointer<ScreenShotDBusInterface1> m_dbusInterface1;
QMap<ComparableQPoint, QImage> m_cacheOutputsImages;
QList<QPoint> m_orderImg;
bool m_captureCursor = false;
bool m_nativeSize = false;
enum class WindowMode {
NoCapture,
Xpixmap,
File,
FileDescriptor
};
WindowMode m_windowMode = WindowMode::NoCapture;
int m_fd = -1;
EffectScreen *m_paintedScreen = nullptr; EffectScreen *m_paintedScreen = nullptr;
}; };
} // namespace } // namespace
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::ScreenShotFlags)
#endif // KWIN_SCREENSHOT_H #endif // KWIN_SCREENSHOT_H

View file

@ -0,0 +1,860 @@
/*
SPDX-FileCopyrightText: 2010 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "screenshotdbusinterface1.h"
#include "../service_utils.h"
#include <KLocalizedString>
#include <KNotification>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusReply>
#include <QDir>
#include <QTemporaryFile>
#include <QtConcurrent>
#include <unistd.h>
namespace KWin
{
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_errorNotAuthorized = QStringLiteral("org.kde.kwin.Screenshot.Error.NoAuthorized");
const static QString s_errorNotAuthorizedMsg = QStringLiteral("The process is not authorized to take a screenshot");
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");
const static QString s_dbusInterfaceName = QStringLiteral("org.kde.kwin.Screenshot");
const static QString s_errorScreenMissing = QStringLiteral("org.kde.kwin.Screenshot.Error.ScreenMissing");
const static QString s_errorScreenMissingMsg = QStringLiteral("Screen not found");
class ScreenShotSource1 : public QObject
{
Q_OBJECT
public:
explicit ScreenShotSource1(QObject *parent = nullptr);
virtual QImage data() const = 0;
virtual void marshal(ScreenShotSink1 *sink) = 0;
virtual bool isCancelled() const = 0;
virtual bool isCompleted() const = 0;
Q_SIGNALS:
void cancelled();
void completed();
};
class ScreenShotSourceBasic1 : public ScreenShotSource1
{
Q_OBJECT
public:
explicit ScreenShotSourceBasic1(const QFuture<QImage> &future);
bool isCancelled() const override;
bool isCompleted() const override;
QImage data() const override;
void marshal(ScreenShotSink1 *sink) override;
private:
QFuture<QImage> m_future;
QFutureWatcher<QImage> *m_watcher;
};
class ScreenShotSourceScreen1 : public ScreenShotSourceBasic1
{
Q_OBJECT
public:
ScreenShotSourceScreen1(ScreenShotEffect *effect, EffectScreen *screen, ScreenShotFlags flags);
};
class ScreenShotSourceArea1 : public ScreenShotSourceBasic1
{
Q_OBJECT
public:
ScreenShotSourceArea1(ScreenShotEffect *effect, const QRect &area, ScreenShotFlags flags);
};
class ScreenShotSourceWindow1 : public ScreenShotSourceBasic1
{
Q_OBJECT
public:
ScreenShotSourceWindow1(ScreenShotEffect *effect, EffectWindow *window, ScreenShotFlags flags);
};
class ScreenShotSourceMulti1 : public ScreenShotSource1
{
Q_OBJECT
public:
explicit ScreenShotSourceMulti1(const QList<ScreenShotSource1 *> &sources);
bool isCancelled() const override;
bool isCompleted() const override;
QImage data() const override;
void marshal(ScreenShotSink1 *sink) override;
private:
void handleSourceCancelled();
void handleSourceCompleted();
QList<ScreenShotSource1 *> m_sources;
};
class ScreenShotSink1 : public QObject
{
Q_OBJECT
public:
explicit ScreenShotSink1(ScreenShotDBusInterface1 *interface,
QDBusMessage replyMessage = QDBusMessage());
virtual void flush(const QImage &image);
virtual void flushMulti(const QList<QImage> &images);
virtual void cancel();
protected:
ScreenShotDBusInterface1 *m_interface;
QDBusMessage m_replyMessage;
};
class ScreenShotSinkPipe1 : public ScreenShotSink1
{
Q_OBJECT
public:
ScreenShotSinkPipe1(ScreenShotDBusInterface1 *interface, int fileDescriptor,
QDBusMessage replyMessage = QDBusMessage());
~ScreenShotSinkPipe1() override;
void flush(const QImage &image) override;
void flushMulti(const QList<QImage> &images) override;
private:
int m_fileDescriptor;
};
class ScreenShotSinkXpixmap1 : public ScreenShotSink1
{
Q_OBJECT
public:
explicit ScreenShotSinkXpixmap1(ScreenShotDBusInterface1 *interface,
QDBusMessage replyMessage = QDBusMessage());
void flush(const QImage &image) override;
};
class ScreenShotSinkFile1 : public ScreenShotSink1
{
Q_OBJECT
public:
explicit ScreenShotSinkFile1(ScreenShotDBusInterface1 *interface,
QDBusMessage replyMessage = QDBusMessage());
void flush(const QImage &image) override;
};
ScreenShotSource1::ScreenShotSource1(QObject *parent)
: QObject(parent)
{
}
ScreenShotSourceBasic1::ScreenShotSourceBasic1(const QFuture<QImage> &future)
: m_future(future)
{
m_watcher = new QFutureWatcher<QImage>(this);
connect(m_watcher, &QFutureWatcher<QImage>::finished, this, &ScreenShotSource1::completed);
connect(m_watcher, &QFutureWatcher<QImage>::canceled, this, &ScreenShotSource1::cancelled);
m_watcher->setFuture(m_future);
}
bool ScreenShotSourceBasic1::isCancelled() const
{
return m_future.isCanceled();
}
bool ScreenShotSourceBasic1::isCompleted() const
{
return m_future.isFinished();
}
QImage ScreenShotSourceBasic1::data() const
{
return m_future.result();
}
void ScreenShotSourceBasic1::marshal(ScreenShotSink1 *sink)
{
sink->flush(data());
}
ScreenShotSourceScreen1::ScreenShotSourceScreen1(ScreenShotEffect *effect,
EffectScreen *screen,
ScreenShotFlags flags)
: ScreenShotSourceBasic1(effect->scheduleScreenShot(screen, flags))
{
}
ScreenShotSourceArea1::ScreenShotSourceArea1(ScreenShotEffect *effect,
const QRect &area,
ScreenShotFlags flags)
: ScreenShotSourceBasic1(effect->scheduleScreenShot(area, flags))
{
}
ScreenShotSourceWindow1::ScreenShotSourceWindow1(ScreenShotEffect *effect,
EffectWindow *window,
ScreenShotFlags flags)
: ScreenShotSourceBasic1(effect->scheduleScreenShot(window, flags))
{
}
ScreenShotSourceMulti1::ScreenShotSourceMulti1(const QList<ScreenShotSource1 *> &sources)
: m_sources(sources)
{
for (ScreenShotSource1 *source : sources) {
source->setParent(this);
connect(source, &ScreenShotSource1::cancelled,
this, &ScreenShotSourceMulti1::handleSourceCancelled);
connect(source, &ScreenShotSource1::completed,
this, &ScreenShotSourceMulti1::handleSourceCompleted);
}
}
bool ScreenShotSourceMulti1::isCancelled() const
{
return std::any_of(m_sources.begin(), m_sources.end(), [](const ScreenShotSource1 *source) {
return source->isCancelled();
});
}
bool ScreenShotSourceMulti1::isCompleted() const
{
return std::all_of(m_sources.begin(), m_sources.end(), [](const ScreenShotSource1 *source) {
return source->isCompleted();
});
}
QImage ScreenShotSourceMulti1::data() const
{
return QImage(); // We don't allow nesting multi screenshot sources.
}
void ScreenShotSourceMulti1::marshal(ScreenShotSink1 *sink)
{
QList<QImage> images;
images.reserve(m_sources.count());
for (ScreenShotSource1 *source : qAsConst(m_sources)) {
images.append(source->data());
}
sink->flushMulti(images);
}
void ScreenShotSourceMulti1::handleSourceCancelled()
{
emit cancelled();
}
void ScreenShotSourceMulti1::handleSourceCompleted()
{
// If all sources are complete now, emit the completed signal.
if (isCompleted()) {
emit completed();
}
}
ScreenShotSink1::ScreenShotSink1(ScreenShotDBusInterface1 *interface, QDBusMessage message)
: m_interface(interface)
, m_replyMessage(message)
{
}
void ScreenShotSink1::flush(const QImage &image)
{
Q_UNUSED(image)
qCWarning(KWINEFFECTS) << metaObject()->className() << "does not implement" << Q_FUNC_INFO;
}
void ScreenShotSink1::flushMulti(const QList<QImage> &images)
{
Q_UNUSED(images)
qCWarning(KWINEFFECTS) << metaObject()->className() << "does not implement" << Q_FUNC_INFO;
}
void ScreenShotSink1::cancel()
{
if (m_replyMessage.isDelayedReply()) {
QDBusConnection::sessionBus().send(m_replyMessage.createErrorReply(s_errorCancelled,
s_errorCancelledMsg));
}
}
ScreenShotSinkPipe1::ScreenShotSinkPipe1(ScreenShotDBusInterface1 *interface, int fileDescriptor,
QDBusMessage replyMessage)
: ScreenShotSink1(interface, replyMessage)
, m_fileDescriptor(fileDescriptor)
{
}
ScreenShotSinkPipe1::~ScreenShotSinkPipe1()
{
if (m_fileDescriptor != -1) {
close(m_fileDescriptor);
}
}
void ScreenShotSinkPipe1::flush(const QImage &image)
{
if (m_fileDescriptor == -1) {
return;
}
QtConcurrent::run([](int fd, const QImage &image) {
QFile file;
if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) {
QDataStream ds(&file);
ds << image;
file.close();
} else {
close(fd);
}
}, m_fileDescriptor, image);
// The ownership of the pipe file descriptor has been moved to the worker thread.
m_fileDescriptor = -1;
}
void ScreenShotSinkPipe1::flushMulti(const QList<QImage> &images)
{
if (m_fileDescriptor == -1) {
return;
}
QtConcurrent::run([](int fd, const QList<QImage> &images) {
QFile file;
if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) {
QDataStream ds(&file);
ds.setVersion(QDataStream::Qt_DefaultCompiledVersion);
ds << images;
file.close();
} else {
close(fd);
}
}, m_fileDescriptor, images);
// The ownership of the pipe file descriptor has been moved to the worker thread.
m_fileDescriptor = -1;
}
ScreenShotSinkXpixmap1::ScreenShotSinkXpixmap1(ScreenShotDBusInterface1 *interface,
QDBusMessage replyMessage)
: ScreenShotSink1(interface, replyMessage)
{
}
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;
}
void ScreenShotSinkXpixmap1::flush(const QImage &image)
{
const xcb_pixmap_t pixmap = xpixmapFromImage(image);
emit m_interface->screenshotCreated(pixmap);
}
ScreenShotSinkFile1::ScreenShotSinkFile1(ScreenShotDBusInterface1 *interface,
QDBusMessage replyMessage)
: ScreenShotSink1(interface, replyMessage)
{
}
static QString saveTempImage(const QImage &image)
{
if (image.isNull()) {
return QString();
}
QTemporaryFile temp(QDir::tempPath() + QDir::separator() + QLatin1String("kwin_screenshot_XXXXXX.png"));
temp.setAutoRemove(false);
if (!temp.open()) {
return QString();
}
image.save(&temp);
temp.close();
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"));
return temp.fileName();
}
void ScreenShotSinkFile1::flush(const QImage &image)
{
QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(image)));
}
ScreenShotDBusInterface1::ScreenShotDBusInterface1(ScreenShotEffect *effect, QObject *parent)
: QObject(parent)
, m_effect(effect)
{
QDBusConnection::sessionBus().registerObject(QStringLiteral("/Screenshot"),
this,
QDBusConnection::ExportScriptableContents);
}
ScreenShotDBusInterface1::~ScreenShotDBusInterface1()
{
QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Screenshot"));
}
bool ScreenShotDBusInterface1::checkCall() const
{
if (!calledFromDBus()) {
return false;
}
const QDBusReply<uint> reply = connection().interface()->servicePid(message().service());
if (reply.isValid()) {
const uint pid = reply.value();
const auto interfaces = KWin::fetchRestrictedDBusInterfacesFromPid(pid);
if (!interfaces.contains(s_dbusInterfaceName)) {
sendErrorReply(s_errorNotAuthorized, s_errorNotAuthorizedMsg);
qCWarning(KWINEFFECTS) << "Process" << pid << "tried to take a screenshot without being granted to DBus interface" << s_dbusInterfaceName;
return false;
}
} else {
return false;
}
if (isTakingScreenshot()) {
sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
return false;
}
return true;
}
void ScreenShotDBusInterface1::screenshotWindowUnderCursor(int mask)
{
if (isTakingScreenshot()) {
sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
return;
}
EffectWindow *hoveredWindow = nullptr;
const QPoint cursor = effects->cursorPos();
EffectWindowList order = effects->stackingOrder();
EffectWindowList::const_iterator it = order.constEnd(), first = order.constBegin();
while (it != first) {
hoveredWindow = *(--it);
if (hoveredWindow->isOnCurrentDesktop()
&& !hoveredWindow->isMinimized()
&& !hoveredWindow->isDeleted()
&& hoveredWindow->geometry().contains(cursor)) {
break;
}
hoveredWindow = nullptr;
}
if (hoveredWindow) {
takeScreenShot(hoveredWindow, ScreenShotFlags(mask),
new ScreenShotSinkXpixmap1(this, message()));
}
}
void ScreenShotDBusInterface1::screenshotForWindow(qulonglong winId, int mask)
{
EffectWindow *window = effects->findWindow(winId);
if (!window || window->isMinimized() || window->isDeleted()) {
return;
}
takeScreenShot(window, ScreenShotFlags(mask), new ScreenShotSinkXpixmap1(this, message()));
}
QString ScreenShotDBusInterface1::interactive(int mask)
{
if (!calledFromDBus()) {
return QString();
}
if (isTakingScreenshot()) {
sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
return QString();
}
const QDBusMessage replyMessage = message();
setDelayedReply(true);
effects->startInteractiveWindowSelection([this, mask, replyMessage](EffectWindow *window) {
hideInfoMessage();
if (!window) {
QDBusConnection::sessionBus().send(replyMessage.createErrorReply(s_errorCancelled,
s_errorCancelledMsg));
return;
}
takeScreenShot(window, ScreenShotFlags(mask), new ScreenShotSinkFile1(this, replyMessage));
});
showInfoMessage(InfoMessageMode::Window);
return QString();
}
void ScreenShotDBusInterface1::interactive(QDBusUnixFileDescriptor fd, int mask)
{
if (!calledFromDBus()) {
return;
}
if (isTakingScreenshot()) {
sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg);
return;
}
const int fileDescriptor = dup(fd.fileDescriptor());
if (fileDescriptor == -1) {
sendErrorReply(s_errorFd, s_errorFdMsg);
return;
}
effects->startInteractiveWindowSelection([this, fileDescriptor, mask](EffectWindow *window) {
hideInfoMessage();
if (!window) {
close(fileDescriptor);
return;
}
takeScreenShot(window, ScreenShotFlags(mask),
new ScreenShotSinkPipe1(this, fileDescriptor));
});
showInfoMessage(InfoMessageMode::Window);
}
QString ScreenShotDBusInterface1::screenshotFullscreen(bool captureCursor)
{
if (!checkCall()) {
return QString();
}
ScreenShotFlags flags = ScreenShotFlags();
if (captureCursor) {
flags |= ScreenShotIncludeCursor;
}
takeScreenShot(effects->virtualScreenGeometry(), flags, new ScreenShotSinkFile1(this, message()));
setDelayedReply(true);
return QString();
}
void ScreenShotDBusInterface1::screenshotFullscreen(QDBusUnixFileDescriptor fd,
bool captureCursor, bool shouldReturnNativeSize)
{
if (!checkCall()) {
return;
}
const int fileDescriptor = dup(fd.fileDescriptor());
if (fileDescriptor == -1) {
sendErrorReply(s_errorFd, s_errorFdMsg);
return;
}
ScreenShotFlags flags = ScreenShotFlags();
if (captureCursor) {
flags |= ScreenShotIncludeCursor;
}
if (shouldReturnNativeSize) {
flags |= ScreenShotNativeResolution;
}
takeScreenShot(effects->virtualScreenGeometry(), flags,
new ScreenShotSinkPipe1(this, fileDescriptor));
}
QString ScreenShotDBusInterface1::screenshotScreen(int screenId, bool captureCursor)
{
if (!checkCall()) {
return QString();
}
EffectScreen *screen = effects->findScreen(screenId);
if (!screen) {
sendErrorReply(s_errorInvalidScreen, s_errorInvalidScreenMsg);
return QString();
}
ScreenShotFlags flags = ScreenShotNativeResolution;
if (captureCursor) {
flags |= ScreenShotIncludeCursor;
}
takeScreenShot(screen, flags, new ScreenShotSinkFile1(this, message()));
setDelayedReply(true);
return QString();
}
void ScreenShotDBusInterface1::screenshotScreen(QDBusUnixFileDescriptor fd, bool captureCursor)
{
if (!checkCall()) {
return;
}
const int fileDescriptor = dup(fd.fileDescriptor());
if (fileDescriptor == -1) {
sendErrorReply(s_errorFd, s_errorFdMsg);
return;
}
ScreenShotFlags flags = ScreenShotNativeResolution;
if (captureCursor) {
flags |= ScreenShotIncludeCursor;
}
effects->startInteractivePositionSelection([this, fileDescriptor, flags](const QPoint &p) {
hideInfoMessage();
if (p == QPoint(-1, -1)) {
close(fileDescriptor);
} else {
EffectScreen *screen = effects->screenAt(p);
if (!screen) {
close(fileDescriptor);
return;
}
takeScreenShot(screen, flags, new ScreenShotSinkPipe1(this, fileDescriptor));
}
});
showInfoMessage(InfoMessageMode::Screen);
}
void ScreenShotDBusInterface1::screenshotScreens(QDBusUnixFileDescriptor fd,
const QStringList &screensNames,
bool captureCursor, bool shouldReturnNativeSize)
{
if (!checkCall()) {
return;
}
const int fileDescriptor = dup(fd.fileDescriptor());
if (fileDescriptor == -1) {
sendErrorReply(s_errorFd, s_errorFdMsg);
return;
}
ScreenShotFlags flags = ScreenShotFlags();
if (captureCursor) {
flags |= ScreenShotIncludeCursor;
}
if (shouldReturnNativeSize) {
flags |= ScreenShotNativeResolution;
}
QList<EffectScreen *> screens;
screens.reserve(screensNames.count());
for (const QString &screenName : screensNames) {
EffectScreen *screen = effects->findScreen(screenName);
if (!screen) {
close(fileDescriptor);
sendErrorReply(s_errorScreenMissing, s_errorScreenMissingMsg + ": " + screenName);
return;
}
screens.append(screen);
}
ScreenShotSinkPipe1 *sink = new ScreenShotSinkPipe1(this, fileDescriptor);
takeScreenShot(screens, flags, sink);
}
QString ScreenShotDBusInterface1::screenshotArea(int x, int y, int width, int height, bool captureCursor)
{
if (!checkCall()) {
return QString();
}
const QRect area(x, y, width, height);
if (area.isEmpty()) {
sendErrorReply(s_errorInvalidArea, s_errorInvalidAreaMsg);
return QString();
}
ScreenShotFlags flags = ScreenShotFlags();
if (captureCursor) {
flags |= ScreenShotIncludeCursor;
}
takeScreenShot(area, flags, new ScreenShotSinkFile1(this, message()));
setDelayedReply(true);
return QString();
}
bool ScreenShotDBusInterface1::isTakingScreenshot() const
{
return m_source;
}
void ScreenShotDBusInterface1::showInfoMessage(InfoMessageMode mode)
{
QString text;
switch (mode) {
case InfoMessageMode::Window:
text = i18n("Select window to screen shot with left click or enter.\nEscape or right click to cancel.");
break;
case InfoMessageMode::Screen:
text = i18n("Create screen shot with left click or enter.\nEscape or right click to cancel.");
break;
}
effects->showOnScreenMessage(text, QStringLiteral("spectacle"));
}
void ScreenShotDBusInterface1::hideInfoMessage()
{
effects->hideOnScreenMessage(EffectsHandler::OnScreenMessageHideFlag::SkipsCloseAnimation);
}
void ScreenShotDBusInterface1::handleSourceCancelled()
{
m_sink->cancel();
m_source.reset();
m_sink.reset();
}
void ScreenShotDBusInterface1::handleSourceCompleted()
{
m_source->marshal(m_sink.data());
m_source.reset();
m_sink.reset();
}
void ScreenShotDBusInterface1::bind(ScreenShotSink1 *sink, ScreenShotSource1 *source)
{
m_sink.reset(sink);
m_source.reset(source);
connect(m_source.data(), &ScreenShotSource1::cancelled,
this, &ScreenShotDBusInterface1::handleSourceCancelled);
connect(m_source.data(), &ScreenShotSource1::completed,
this, &ScreenShotDBusInterface1::handleSourceCompleted);
}
void ScreenShotDBusInterface1::takeScreenShot(EffectScreen *screen, ScreenShotFlags flags,
ScreenShotSink1 *sink)
{
bind(sink, new ScreenShotSourceScreen1(m_effect, screen, flags));
}
void ScreenShotDBusInterface1::takeScreenShot(const QList<EffectScreen *> &screens,
ScreenShotFlags flags, ScreenShotSink1 *sink)
{
QList<ScreenShotSource1 *> sources;
sources.reserve(screens.count());
for (EffectScreen *screen : screens) {
sources.append(new ScreenShotSourceScreen1(m_effect, screen, flags));
}
bind(sink, new ScreenShotSourceMulti1(sources));
}
void ScreenShotDBusInterface1::takeScreenShot(const QRect &area, ScreenShotFlags flags,
ScreenShotSink1 *sink)
{
bind(sink, new ScreenShotSourceArea1(m_effect, area, flags));
}
void ScreenShotDBusInterface1::takeScreenShot(EffectWindow *window, ScreenShotFlags flags,
ScreenShotSink1 *sink)
{
bind(sink, new ScreenShotSourceWindow1(m_effect, window, flags));
}
} // namespace KWin
#include "screenshotdbusinterface1.moc"

View file

@ -0,0 +1,172 @@
/*
SPDX-FileCopyrightText: 2010 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "screenshot.h"
#include <QDBusContext>
#include <QDBusMessage>
#include <QDBusUnixFileDescriptor>
namespace KWin
{
class ScreenShotSink1;
class ScreenShotSource1;
/**
* The ScreenshotDBusInterface1 class provides a d-bus api to take screenshots. This implements
* the org.kde.kwin.Screenshot interface.
*
* An application that requests a screenshot must have "org.kde.kwin.Screenshot" listed in its
* X-KDE-DBUS-Restricted-Interfaces desktop file field.
*/
class ScreenShotDBusInterface1 : public QObject, protected QDBusContext
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Screenshot")
public:
explicit ScreenShotDBusInterface1(ScreenShotEffect *effect, QObject *parent = nullptr);
~ScreenShotDBusInterface1() override;
Q_SIGNALS:
/**
* This signal is emitted when a screenshot has been written in an Xpixmap with the
* specified @a handle.
*/
Q_SCRIPTABLE void screenshotCreated(qulonglong handle);
public Q_SLOTS:
Q_SCRIPTABLE void screenshotForWindow(qulonglong winid, int mask = 0);
/**
* Starts an interactive window screenshot session. The user can select a window to
* screenshot.
*
* Once the window is selected the screenshot is saved into a file and the path gets
* returned to the DBus peer.
*
* @param mask The mask for what to include in the screenshot
*/
Q_SCRIPTABLE QString interactive(int mask = 0);
/**
* Starts an interactive window screenshot session. The user can select a window to
* screenshot.
*
* Once the window is selected the screenshot is saved into the @p fd passed to the
* method. It is intended to be used with a pipe, so that the invoking side can just
* read from the pipe. The image gets written into the fd using a QDataStream.
*
* @param fd File descriptor into which the screenshot should be saved
* @param mask The mask for what to include in the screenshot
*/
Q_SCRIPTABLE void interactive(QDBusUnixFileDescriptor fd, 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.
* @param captureCursor Whether to include the cursor in the image
* @returns Path to stored screenshot, or null string in failure case.
*/
Q_SCRIPTABLE QString screenshotFullscreen(bool captureCursor = false);
/**
* Takes a full screen screenshot in a one file format.
*
* Once the screenshot is taken it gets saved into the @p fd passed to the
* method. It is intended to be used with a pipe, so that the invoking side can just
* read from the pipe. The image gets written into the fd using a QDataStream.
*
* @param fd File descriptor into which the screenshot should be saved
* @param captureCursor Whether to include the mouse cursor
* @param shouldReturnNativeSize Whether to return an image according to the virtualGeometry,
* or according to pixel on screen size
*/
Q_SCRIPTABLE void screenshotFullscreen(QDBusUnixFileDescriptor fd,
bool captureCursor = false,
bool shouldReturnNativeSize = false);
/**
* Take a screenshot of the passed screens and return a QList<QImage> in the fd response,
* an image for each screen in pixel-on-screen size when shouldReturnNativeSize is passed,
* or converted to using logicale size if not
*
* @param fd
* @param screensNames the names of the screens whose screenshot to return
* @param captureCursor
* @param shouldReturnNativeSize
*/
Q_SCRIPTABLE void screenshotScreens(QDBusUnixFileDescriptor fd,
const QStringList &screensNames,
bool captureCursor = false,
bool shouldReturnNativeSize = false);
/**
* 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
* @param captureCursor Whether to include the cursor in the image
* @returns Path to stored screenshot, or null string in failure case.
*/
Q_SCRIPTABLE QString screenshotScreen(int screen, bool captureCursor = false);
/**
* Starts an interactive screenshot of a screen session.
*
* The user is asked to select the screen to screenshot.
*
* Once the screenshot is taken it gets saved into the @p fd passed to the
* method. It is intended to be used with a pipe, so that the invoking side can just
* read from the pipe. The image gets written into the fd using a QDataStream.
*
* @param fd File descriptor into which the screenshot should be saved
* @param captureCursor Whether to include the mouse cursor
*/
Q_SCRIPTABLE void screenshotScreen(QDBusUnixFileDescriptor fd, bool captureCursor = false);
/**
* 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
* @param captureCursor Whether to include the cursor in the image
* @returns Path to stored screenshot, or null string in failure case.
*/
Q_SCRIPTABLE QString screenshotArea(int x, int y, int width, int height, bool captureCursor = false);
private Q_SLOTS:
void handleSourceCompleted();
void handleSourceCancelled();
private:
enum class InfoMessageMode { Window, Screen, };
void takeScreenShot(EffectScreen *screen, ScreenShotFlags flags, ScreenShotSink1 *sink);
void takeScreenShot(const QList<EffectScreen *> &screens, ScreenShotFlags flags, ScreenShotSink1 *sink);
void takeScreenShot(const QRect &area, ScreenShotFlags flags, ScreenShotSink1 *sink);
void takeScreenShot(EffectWindow *window, ScreenShotFlags flags, ScreenShotSink1 *sink);
void bind(ScreenShotSink1 *sink, ScreenShotSource1 *source);
bool checkCall() const;
bool isTakingScreenshot() const;
void showInfoMessage(InfoMessageMode mode);
void hideInfoMessage();
ScreenShotEffect *m_effect;
QScopedPointer<ScreenShotSink1> m_sink;
QScopedPointer<ScreenShotSource1> m_source;
};
} // namespace KWin