File indexing completed on 2024-04-28 16:55:48

0001 /*
0002  * SPDX-FileCopyrightText: 2018 Red Hat Inc
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  *
0006  * SPDX-FileCopyrightText: 2018 Jan Grulich <jgrulich@redhat.com>
0007  */
0008 
0009 #include "screenshotdialog.h"
0010 #include "screenshotdialog_debug.h"
0011 
0012 #include <QPushButton>
0013 
0014 #include <KLocalizedString>
0015 #include <QDBusInterface>
0016 #include <QDBusPendingCall>
0017 #include <QDBusPendingReply>
0018 #include <QDBusUnixFileDescriptor>
0019 #include <QFutureWatcher>
0020 #include <QStandardItemModel>
0021 #include <QTimer>
0022 #include <QWindow>
0023 #include <QtConcurrentRun>
0024 #include <qplatformdefs.h>
0025 
0026 #include <fcntl.h>
0027 #include <poll.h>
0028 #include <unistd.h>
0029 
0030 static int readData(int fd, QByteArray &data)
0031 {
0032     char buffer[4096];
0033     pollfd pfds[1];
0034     pfds[0].fd = fd;
0035     pfds[0].events = POLLIN;
0036 
0037     while (true) {
0038         // give user 30 sec to click a window, afterwards considered as error
0039         const int ready = poll(pfds, 1, 30000);
0040         if (ready < 0) {
0041             if (errno != EINTR) {
0042                 qWarning() << "poll() failed:" << strerror(errno);
0043                 return -1;
0044             }
0045         } else if (ready == 0) {
0046             qDebug() << "failed to read screenshot: timeout";
0047             return -1;
0048         } else if (pfds[0].revents & POLLIN) {
0049             const int n = QT_READ(fd, buffer, sizeof(buffer));
0050 
0051             if (n < 0) {
0052                 qWarning() << "read() failed:" << strerror(errno);
0053                 return -1;
0054             } else if (n == 0) {
0055                 return 0;
0056             } else if (n > 0) {
0057                 data.append(buffer, n);
0058             }
0059         } else if (pfds[0].revents & POLLHUP) {
0060             return 0;
0061         } else {
0062             qWarning() << "failed to read screenshot: pipe is broken";
0063             return -1;
0064         }
0065     }
0066 
0067     Q_UNREACHABLE();
0068 }
0069 
0070 static QImage readImage(int pipeFd)
0071 {
0072     QByteArray content;
0073     if (readData(pipeFd, content) != 0) {
0074         close(pipeFd);
0075         return QImage();
0076     }
0077     close(pipeFd);
0078     QDataStream ds(content);
0079     QImage image;
0080     ds >> image;
0081     return image;
0082 }
0083 
0084 ScreenshotDialog::ScreenshotDialog(QObject *parent)
0085     : QuickDialog(parent)
0086 {
0087     QStandardItemModel *model = new QStandardItemModel(this);
0088     model->appendRow(new QStandardItem(i18n("Full Screen")));
0089     model->appendRow(new QStandardItem(i18n("Current Screen")));
0090     model->appendRow(new QStandardItem(i18n("Active Window")));
0091     create(QStringLiteral("qrc:/ScreenshotDialog.qml"),
0092            {
0093                {"app", QVariant::fromValue<QObject *>(this)},
0094                {"screenshotTypesModel", QVariant::fromValue<QObject *>(model)},
0095            });
0096 }
0097 
0098 QImage ScreenshotDialog::image() const
0099 {
0100     return m_image;
0101 }
0102 
0103 void ScreenshotDialog::takeScreenshotNonInteractive()
0104 {
0105     QFuture<QImage> future = takeScreenshot();
0106     if (!future.isStarted()) {
0107         return;
0108     }
0109 
0110     future.waitForFinished();
0111     m_image = future.result();
0112 }
0113 
0114 void ScreenshotDialog::takeScreenshotInteractive()
0115 {
0116     const QFuture<QImage> future = takeScreenshot();
0117     if (!future.isStarted()) {
0118         return;
0119     }
0120 
0121     QFutureWatcher<QImage> *watcher = new QFutureWatcher<QImage>(this);
0122     QObject::connect(watcher, &QFutureWatcher<QImage>::finished, this, [watcher, this] {
0123         watcher->deleteLater();
0124         m_image = watcher->result();
0125         m_theDialog->setProperty("screenshotImage", m_image);
0126     });
0127 
0128     watcher->setFuture(future);
0129 }
0130 
0131 QFuture<QImage> ScreenshotDialog::takeScreenshot()
0132 {
0133     int pipeFds[2];
0134     if (pipe2(pipeFds, O_CLOEXEC | O_NONBLOCK) != 0) {
0135         Q_EMIT failed();
0136         return {};
0137     }
0138 
0139     const bool withBorders = m_theDialog->property("withBorders").toBool();
0140     const bool withCursor = m_theDialog->property("withCursor").toBool();
0141 
0142     QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot"));
0143     auto types = ScreenshotType(m_theDialog->property("screenshotType").toInt());
0144     if (types == ActiveWindow) {
0145         int mask = 0;
0146         if (withBorders) {
0147             mask = Borders;
0148         }
0149         if (withCursor) {
0150             mask |= Cursor;
0151         }
0152         interface.asyncCall(QStringLiteral("interactive"), QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds[1])), mask);
0153     } else {
0154         interface.asyncCall(types ? QStringLiteral("screenshotScreen") : QStringLiteral("screenshotFullscreen"),
0155                             QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds[1])),
0156                             withCursor,
0157                             true);
0158     }
0159     ::close(pipeFds[1]);
0160 
0161     return QtConcurrent::run(readImage, pipeFds[0]);
0162 }