File indexing completed on 2024-04-28 05:36:53
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 <QDBusConnection> 0016 #include <QDBusMessage> 0017 #include <QDBusReply> 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 allocateImage(const QVariantMap &metadata) 0071 { 0072 bool ok; 0073 0074 const uint width = metadata.value(QStringLiteral("width")).toUInt(&ok); 0075 if (!ok) { 0076 return QImage(); 0077 } 0078 0079 const uint height = metadata.value(QStringLiteral("height")).toUInt(&ok); 0080 if (!ok) { 0081 return QImage(); 0082 } 0083 0084 const uint format = metadata.value(QStringLiteral("format")).toUInt(&ok); 0085 if (!ok) { 0086 return QImage(); 0087 } 0088 0089 return QImage(width, height, QImage::Format(format)); 0090 } 0091 0092 static QImage readImage(int pipeFd, const QVariantMap &metadata) 0093 { 0094 QByteArray content; 0095 if (readData(pipeFd, content) != 0) { 0096 close(pipeFd); 0097 return QImage(); 0098 } 0099 close(pipeFd); 0100 0101 QImage result = allocateImage(metadata); 0102 if (result.isNull()) { 0103 return QImage(); 0104 } 0105 0106 QDataStream ds(content); 0107 ds.readRawData(reinterpret_cast<char *>(result.bits()), result.sizeInBytes()); 0108 return result; 0109 } 0110 0111 ScreenshotDialog::ScreenshotDialog(QObject *parent) 0112 : QuickDialog(parent) 0113 { 0114 QStandardItemModel *model = new QStandardItemModel(this); 0115 model->appendRow(new QStandardItem(i18n("Full Screen"))); 0116 model->appendRow(new QStandardItem(i18n("Current Screen"))); 0117 model->appendRow(new QStandardItem(i18n("Active Window"))); 0118 create(QStringLiteral("qrc:/ScreenshotDialog.qml"), 0119 { 0120 {"app", QVariant::fromValue<QObject *>(this)}, 0121 {"screenshotTypesModel", QVariant::fromValue<QObject *>(model)}, 0122 }); 0123 } 0124 0125 QImage ScreenshotDialog::image() const 0126 { 0127 return m_image; 0128 } 0129 0130 void ScreenshotDialog::takeScreenshotNonInteractive() 0131 { 0132 QFuture<QImage> future = takeScreenshot(); 0133 if (!future.isStarted()) { 0134 return; 0135 } 0136 0137 future.waitForFinished(); 0138 m_image = future.result(); 0139 } 0140 0141 void ScreenshotDialog::takeScreenshotInteractive() 0142 { 0143 const QFuture<QImage> future = takeScreenshot(); 0144 if (!future.isStarted()) { 0145 return; 0146 } 0147 0148 QFutureWatcher<QImage> *watcher = new QFutureWatcher<QImage>(this); 0149 QObject::connect(watcher, &QFutureWatcher<QImage>::finished, this, [watcher, this] { 0150 watcher->deleteLater(); 0151 m_image = watcher->result(); 0152 m_theDialog->setProperty("screenshotImage", m_image); 0153 }); 0154 0155 watcher->setFuture(future); 0156 } 0157 0158 QFuture<QImage> ScreenshotDialog::takeScreenshot() 0159 { 0160 int pipeFds[2]; 0161 if (pipe2(pipeFds, O_CLOEXEC | O_NONBLOCK) != 0) { 0162 Q_EMIT failed(); 0163 return {}; 0164 } 0165 0166 QVariantMap options; 0167 options.insert(QStringLiteral("native-resolution"), true); // match xdg-desktop-portal-gnome, and fixes flameshot!3164 0168 if (m_theDialog->property("withCursor").toBool()) { 0169 options.insert(QStringLiteral("include-cursor"), true); 0170 } 0171 if (m_theDialog->property("withBorders").toBool()) { 0172 options.insert(QStringLiteral("include-decoration"), true); 0173 } 0174 0175 QString method; 0176 switch (ScreenshotType(m_theDialog->property("screenshotType").toInt())) { 0177 case FullScreen: 0178 method = QStringLiteral("CaptureWorkspace"); 0179 break; 0180 case CurrentScreen: 0181 method = QStringLiteral("CaptureActiveScreen"); 0182 break; 0183 case ActiveWindow: 0184 method = QStringLiteral("CaptureActiveWindow"); 0185 break; 0186 } 0187 0188 QVariantList arguments; 0189 arguments.append(options); 0190 arguments.append(QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds[1]))); 0191 0192 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin.ScreenShot2"), 0193 QStringLiteral("/org/kde/KWin/ScreenShot2"), 0194 QStringLiteral("org.kde.KWin.ScreenShot2"), 0195 method); 0196 message.setArguments(arguments); 0197 0198 QDBusReply<QVariantMap> reply = QDBusConnection::sessionBus().call(message); 0199 ::close(pipeFds[1]); 0200 if (!reply.isValid()) { 0201 qCWarning(XdgDesktopPortalKdeScreenshotDialog) << method << "failed:" << reply.error(); 0202 close(pipeFds[0]); 0203 return QFuture<QImage>(); 0204 } 0205 0206 return QtConcurrent::run(readImage, pipeFds[0], reply); 0207 }