Screenshot effect: add a screenshotScreens(...) to screenshot specific screens

This commit is contained in:
Méven Car 2020-08-26 18:21:54 +02:00 committed by Méven Car
parent 99bed106bf
commit 309a656e00
3 changed files with 140 additions and 39 deletions

View file

@ -23,6 +23,8 @@
#include <QMatrix4x4>
#include <xcb/xcb_image.h>
#include <QPoint>
#include <QGuiApplication>
#include <QScreen>
#include <KLocalizedString>
#include <KNotification>
@ -30,6 +32,8 @@
#include <unistd.h>
#include "../service_utils.h"
Q_DECLARE_METATYPE(QStringList)
class ComparableQPoint : public QPoint
{
public:
@ -66,6 +70,8 @@ const static QString s_errorInvalidAreaMsg = QStringLiteral("Invalid area reques
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");
bool ScreenShotEffect::supported()
{
@ -355,39 +361,43 @@ void ScreenShotEffect::postPaintScreen()
m_multipleOutputsRendered = m_multipleOutputsRendered.united(intersection);
if (m_multipleOutputsRendered.boundingRect() == m_scheduledGeometry) {
// Recompute coordinates
if (m_nativeSize) {
computeCoordinatesAfterScaling();
if (m_orderImg.isEmpty()) {
// Recompute coordinates
if (m_nativeSize) {
computeCoordinatesAfterScaling();
}
// find the output image size
int width = 0;
int height = 0;
QMap<ComparableQPoint, QImage>::const_iterator i;
for (i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) {
const auto pos = i.key();
const auto img = i.value();
width = qMax(width, pos.x() + img.width());
height = qMax(height, pos.y() + img.height());
}
QImage multipleOutputsImage = QImage(width, height, QImage::Format_ARGB32);
QPainter p;
p.begin(&multipleOutputsImage);
// reassemble images together
for (i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) {
auto pos = i.key();
auto img = i.value();
// disable dpr rendering, we already took care of this
img.setDevicePixelRatio(1.0);
p.drawImage(pos, img);
}
p.end();
sendReplyImage(multipleOutputsImage);
} else {
sendReplyImages();
}
// find the output image size
int width = 0;
int height = 0;
QMap<ComparableQPoint, QImage>::const_iterator i;
for (i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) {
const auto pos = i.key();
const auto img = i.value();
width = qMax(width, pos.x() + img.width());
height = qMax(height, pos.y() + img.height());
}
QImage multipleOutputsImage = QImage(width, height, QImage::Format_ARGB32);
QPainter p;
p.begin(&multipleOutputsImage);
// reassemble images together
for (i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) {
auto pos = i.key();
auto img = i.value();
// disable dpr rendering, we already took care of this
img.setDevicePixelRatio(1.0);
p.drawImage(pos, img);
}
p.end();
sendReplyImage(multipleOutputsImage);
}
} else {
@ -411,10 +421,41 @@ void ScreenShotEffect::sendReplyImage(const QImage &img)
close(fd);
}
}, m_fd, img);
m_fd = -1;
} else {
QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(img)));
}
clearState();
}
void ScreenShotEffect::sendReplyImages()
{
QList<QImage> outputImages;
for (const QPoint &pos : qAsConst(m_orderImg)) {
auto it = m_cacheOutputsImages.find(pos);
if (it != m_cacheOutputsImages.constEnd()) {
outputImages.append(*it);
}
}
QtConcurrent::run(
[] (int fd, const QList<QImage> &outputImages) {
QFile file;
if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) {
QDataStream ds(&file);
ds.setVersion(QDataStream::Qt_DefaultCompiledVersion);
ds << outputImages;
file.close();
} else {
close(fd);
}
}, m_fd, outputImages);
clearState();
}
void ScreenShotEffect::clearState()
{
m_fd = -1;
m_scheduledGeometry = QRect();
m_multipleOutputsRendered = QRegion();
m_captureCursor = false;
@ -422,6 +463,7 @@ void ScreenShotEffect::sendReplyImage(const QImage &img)
m_cacheOutputsImages.clear();
m_cachedOutputGeometry = QRect();
m_nativeSize = false;
m_orderImg.clear();
}
QString ScreenShotEffect::saveTempImage(const QImage &img)
@ -661,6 +703,50 @@ void ScreenShotEffect::screenshotScreen(QDBusUnixFileDescriptor fd, bool capture
);
}
void ScreenShotEffect::screenshotScreens(QDBusUnixFileDescriptor fd, const QStringList &screensNames, bool captureCursor, bool shouldReturnNativeSize)
{
if (!checkCall()) {
return;
}
m_fd = dup(fd.fileDescriptor());
if (m_fd == -1) {
sendErrorReply(s_errorFd, s_errorFdMsg);
return;
}
m_captureCursor = captureCursor;
m_nativeSize = shouldReturnNativeSize;
m_orderImg = QList<QPoint>();
m_scheduledGeometry = QRect();
const QList<QScreen *> screens = QGuiApplication::screens();
QStringList lscreensNames = screensNames;
for (const QScreen *screen : screens) {
const int indexName = lscreensNames.indexOf(screen->name());
if (indexName != -1) {
lscreensNames.removeAt(indexName);
const auto screenGeom = screen->geometry();
if (!screenGeom.isValid()) {
close(m_fd);
clearState();
sendErrorReply(s_errorScreenMissing, s_errorScreenMissingMsg + " : " + screen->name());
return;
}
m_scheduledGeometry = m_scheduledGeometry.united(screenGeom);
m_orderImg.insert(indexName, screenGeom.topLeft());
}
}
if (!lscreensNames.isEmpty()) {
close(m_fd);
clearState();
sendErrorReply(s_errorScreenMissing, s_errorScreenMissingMsg + " : " + lscreensNames.join(", "));
return;
}
effects->addRepaint(m_scheduledGeometry);
}
QString ScreenShotEffect::screenshotArea(int x, int y, int width, int height, bool captureCursor)
{
if (!checkCall()) {

View file

@ -22,6 +22,11 @@ class ComparableQPoint;
namespace KWin
{
/**
* The screenshot effet allows to takes screenshot, by window, area, screen, etc...
*
* 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
{
Q_OBJECT
@ -77,10 +82,7 @@ public Q_SLOTS:
*/
Q_SCRIPTABLE QString screenshotFullscreen(bool captureCursor = false);
/**
* Starts an interactive screenshot session.
*
* The user is asked to confirm that a screenshot is taken by having to actively
* click and giving the possibility to cancel.
* 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
@ -91,6 +93,16 @@ public Q_SLOTS:
* @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.
@ -135,6 +147,8 @@ private:
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
@ -152,6 +166,7 @@ private:
QRect m_cachedOutputGeometry;
QRegion m_multipleOutputsRendered;
QMap<ComparableQPoint, QImage> m_cacheOutputsImages;
QList<QPoint> m_orderImg;
bool m_captureCursor = false;
bool m_nativeSize = false;
enum class WindowMode {

View file

@ -50,12 +50,12 @@ static QStringList fetchProcessServiceField(const QString &executablePath, const
return fieldValues;
}
static QStringList fetchRequestedInterfaces(const QString &executablePath)
static inline QStringList fetchRequestedInterfaces(const QString &executablePath)
{
return fetchProcessServiceField(executablePath, s_waylandInterfaceName);
}
static QStringList fetchRestrictedDBusInterfacesFromPid(const uint pid)
static inline QStringList fetchRestrictedDBusInterfacesFromPid(const uint pid)
{
const auto executablePath = QFileInfo(QStringLiteral("/proc/%1/exe").arg(pid)).symLinkTarget();
return fetchProcessServiceField(executablePath, s_dbusRestrictedInterfaceName);