Warning, file /plasma/plasma-mobile/quicksettings/screenshot/screenshotutil.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  * SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
0003  * SPDX-FileCopyrightText: 2018 Bhushan Shah <bshah@kde.org>
0004  * SPDX-FileCopyrightText: 2022 by Devin Lin <devin@kde.org>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "screenshotutil.h"
0010 
0011 #include <fcntl.h>
0012 #include <qplatformdefs.h>
0013 #include <unistd.h>
0014 
0015 #include <KLocalizedString>
0016 #include <KNotification>
0017 
0018 #include <QGuiApplication>
0019 #include <QImage>
0020 #include <QScreen>
0021 #include <QTimer>
0022 #include <QtConcurrent/QtConcurrent>
0023 
0024 constexpr int SCREENSHOT_DELAY = 200;
0025 
0026 /* -- Static Helpers --------------------------------------------------------------------------- */
0027 
0028 static QImage allocateImage(const QVariantMap &metadata)
0029 {
0030     bool ok;
0031 
0032     const uint width = metadata.value(QStringLiteral("width")).toUInt(&ok);
0033     if (!ok) {
0034         return QImage();
0035     }
0036 
0037     const uint height = metadata.value(QStringLiteral("height")).toUInt(&ok);
0038     if (!ok) {
0039         return QImage();
0040     }
0041 
0042     const uint format = metadata.value(QStringLiteral("format")).toUInt(&ok);
0043     if (!ok) {
0044         return QImage();
0045     }
0046 
0047     return QImage(width, height, QImage::Format(format));
0048 }
0049 
0050 static QImage readImage(int fileDescriptor, const QVariantMap &metadata)
0051 {
0052     QFile file;
0053     if (!file.open(fileDescriptor, QFileDevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
0054         close(fileDescriptor);
0055         return QImage();
0056     }
0057 
0058     QImage result = allocateImage(metadata);
0059     if (result.isNull()) {
0060         return QImage();
0061     }
0062 
0063     QDataStream stream(&file);
0064     stream.readRawData(reinterpret_cast<char *>(result.bits()), result.sizeInBytes());
0065 
0066     return result;
0067 }
0068 
0069 ScreenShotUtil::ScreenShotUtil(QObject *parent)
0070     : QObject{parent}
0071 {
0072     m_screenshotInterface = new OrgKdeKWinScreenShot2Interface(QStringLiteral("org.kde.KWin.ScreenShot2"),
0073                                                                QStringLiteral("/org/kde/KWin/ScreenShot2"),
0074                                                                QDBusConnection::sessionBus(),
0075                                                                this);
0076 }
0077 
0078 void ScreenShotUtil::takeScreenShot()
0079 {
0080     // wait ~200 ms to wait for rest of animations
0081     QTimer::singleShot(SCREENSHOT_DELAY, [=]() {
0082         int lPipeFds[2];
0083         if (pipe2(lPipeFds, O_CLOEXEC) != 0) {
0084             qWarning() << "Could not take screenshot";
0085             return;
0086         }
0087 
0088         // We don't have access to the ScreenPool so we'll just take the first screen
0089         QVariantMap options;
0090         options.insert(QStringLiteral("native-resolution"), true);
0091 
0092         auto pendingCall = m_screenshotInterface->CaptureScreen(qGuiApp->screens().constFirst()->name(), options, QDBusUnixFileDescriptor(lPipeFds[1]));
0093         close(lPipeFds[1]);
0094         auto pipeFileDescriptor = lPipeFds[0];
0095 
0096         auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
0097         connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, pipeFileDescriptor]() {
0098             watcher->deleteLater();
0099             const QDBusPendingReply<QVariantMap> reply = *watcher;
0100 
0101             if (reply.isError()) {
0102                 qWarning() << "Screenshot request failed:" << reply.error().message();
0103             } else {
0104                 handleMetaDataReceived(reply, pipeFileDescriptor);
0105             }
0106         });
0107     });
0108 }
0109 
0110 void ScreenShotUtil::handleMetaDataReceived(const QVariantMap &metadata, int fd)
0111 {
0112     const QString type = metadata.value(QStringLiteral("type")).toString();
0113     if (type != QLatin1String("raw")) {
0114         qWarning() << "Unsupported metadata type:" << type;
0115         return;
0116     }
0117 
0118     auto watcher = new QFutureWatcher<QImage>(this);
0119     connect(watcher, &QFutureWatcher<QImage>::finished, this, [watcher]() {
0120         watcher->deleteLater();
0121 
0122         QString filePath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
0123         if (filePath.isEmpty()) {
0124             qWarning() << "Couldn't find a writable location for the screenshot!";
0125             return;
0126         }
0127         QDir picturesDir(filePath);
0128         if (!picturesDir.mkpath(QStringLiteral("Screenshots"))) {
0129             qWarning() << "Couldn't create folder at" << picturesDir.path() + QStringLiteral("/Screenshots") << "to take screenshot.";
0130             return;
0131         }
0132         filePath += QStringLiteral("/Screenshots/Screenshot_%1.png").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_hhmmss")));
0133         const auto m_result = watcher->result();
0134         if (m_result.isNull() || !m_result.save(filePath)) {
0135             qWarning() << "Screenshot failed";
0136         } else {
0137             KNotification *notif = new KNotification("captured");
0138             notif->setComponentName(QStringLiteral("plasma_phone_components"));
0139             notif->setTitle(i18n("New Screenshot"));
0140             notif->setUrls({QUrl::fromLocalFile(filePath)});
0141             notif->setText(i18n("New screenshot saved to %1", filePath));
0142             notif->sendEvent();
0143         }
0144     });
0145     watcher->setFuture(QtConcurrent::run(readImage, fd, metadata));
0146 }