File indexing completed on 2023-05-30 12:24:36

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0003 
0004 #include <chrono>
0005 #include <optional>
0006 
0007 #include <QBuffer>
0008 #include <QDBusConnection>
0009 #include <QDBusInterface>
0010 #include <QDBusMessage>
0011 #include <QDBusReply>
0012 #include <QDBusUnixFileDescriptor>
0013 #include <QDebug>
0014 #include <QFile>
0015 #include <QGuiApplication>
0016 #include <QImage>
0017 #include <qplatformdefs.h>
0018 
0019 using namespace std::chrono_literals;
0020 
0021 // When the tests are run under an existing session, the well known org.kde.KWin name will be claimed by the real
0022 // kwin, figure out where our test kwin resides on the bus by reverse looking up the PID.
0023 std::optional<QString> kwinService()
0024 {
0025     auto bus = QDBusConnection::sessionBus();
0026 
0027     const QString kwinPid = qEnvironmentVariable("KWIN_PID");
0028     if (kwinPid.isEmpty()) {
0029         return QStringLiteral("org.kde.KWin");
0030     }
0031 
0032     QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"),
0033                                                             QStringLiteral("/org/freedesktop/DBus"),
0034                                                             QStringLiteral("org.freedesktop.DBus"),
0035                                                             QStringLiteral("ListNames"));
0036     QDBusReply<QStringList> namesReply = bus.call(message);
0037     if (namesReply.isValid()) {
0038         const auto names = namesReply.value();
0039         for (const auto &name : names) {
0040             QDBusMessage getPid = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"),
0041                                                                     QStringLiteral("/org/freedesktop/DBus"),
0042                                                                     QStringLiteral("org.freedesktop.DBus"),
0043                                                                     QStringLiteral("GetConnectionUnixProcessID"));
0044             getPid << name;
0045             QDBusReply<quint32> pid = bus.call(getPid);
0046             if (pid.isValid() && QString::number(pid.value()) == kwinPid) {
0047                 return name;
0048             }
0049         }
0050     } else {
0051         qWarning() << namesReply.error();
0052     }
0053 
0054     return std::nullopt;
0055 }
0056 
0057 static QImage allocateImage(const QVariantMap &metadata)
0058 {
0059     bool ok = false;
0060 
0061     const int width = metadata.value(QStringLiteral("width")).toInt(&ok);
0062     if (!ok) {
0063         return {};
0064     }
0065 
0066     const int height = metadata.value(QStringLiteral("height")).toInt(&ok);
0067     if (!ok) {
0068         return {};
0069     }
0070 
0071     const int format = metadata.value(QStringLiteral("format")).toInt(&ok);
0072     if (!ok) {
0073         return {};
0074     }
0075 
0076     return {width, height, QImage::Format(format)};
0077 }
0078 
0079 static QImage readImage(int pipeFd, const QVariantMap &metadata)
0080 {
0081     QFile out;
0082     if (!out.open(pipeFd, QFileDevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
0083         qWarning() << "failed to open out pipe for reading";
0084         ::close(pipeFd);
0085         return {};
0086     }
0087 
0088     QImage result = allocateImage(metadata);
0089     if (result.isNull()) {
0090         qWarning() << "failed to allocate image";
0091         return {};
0092     }
0093 
0094     auto readData = 0;
0095     while (readData < result.sizeInBytes()) {
0096         if (const int ret = out.read(reinterpret_cast<char *>(result.bits() + readData), result.sizeInBytes() - readData); ret >= 0) {
0097             readData += ret;
0098         }
0099     }
0100     return result;
0101 }
0102 
0103 int main(int argc, char **argv)
0104 {
0105     const QGuiApplication app(argc, argv);
0106 
0107     // Unfortunately since the geometries are not including the DPR we can only look at one screen
0108     // and hope that they are all the same :(
0109     //
0110     // Also since the position is entirely wrong on wayland (always 0,0) we currently ignore all of this
0111     // and instead make full screen shots.
0112     //
0113     // const auto dpr = app.primaryScreen()->devicePixelRatio();
0114     //
0115     // auto args const = app.arguments().mid(1);
0116     // const auto positionX = int(args.takeFirst().toInt() * dpr);
0117     // const auto positionY = int(args.takeFirst().toInt() * dpr);
0118     // const auto width = int(args.takeFirst().toInt() * dpr);
0119     // const auto height = int(args.takeFirst().toInt() * dpr);
0120     // Q_ASSERT(args.isEmpty());
0121 
0122     const auto service = kwinService();
0123     if (!service.has_value()) {
0124         qWarning() << "kwin dbus service not resolved";
0125         return 1;
0126     }
0127 
0128     auto pipeFds = std::to_array<int>({0, 0});
0129     if (pipe2(pipeFds.data(), O_CLOEXEC | O_NONBLOCK) != 0) {
0130         qWarning() << "failed to open pipe" << strerror(errno);
0131         return 1;
0132     }
0133 
0134     QVariantList arguments;
0135     arguments.append(QVariantMap());
0136     arguments.append(QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds.at(1))));
0137 
0138     auto bus = QDBusConnection::sessionBus();
0139     QDBusMessage message = QDBusMessage::createMethodCall(service.value(),
0140                                                           QStringLiteral("/org/kde/KWin/ScreenShot2"),
0141                                                           QStringLiteral("org.kde.KWin.ScreenShot2"),
0142                                                           QStringLiteral("CaptureActiveScreen")); // CaptureWorkspace is nicer but only available in plasma6
0143     message << QVariantMap() << QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds.at(1)));
0144 
0145     QDBusPendingCall msg = bus.asyncCall(message);
0146     msg.waitForFinished();
0147     QDBusReply<QVariantMap> reply = msg.reply();
0148     if (!reply.isValid()) {
0149         qWarning() << reply.error();
0150         return 1;
0151     }
0152 
0153     ::close(pipeFds.at(1));
0154     const auto image = readImage(pipeFds.at(0), reply.value());
0155     QBuffer buf;
0156     image.save(&buf, "PNG");
0157     printf("%s", buf.data().toBase64().constData()); // intentionally no newline so we don't need to strip on the py side
0158     return 0;
0159 }
0160 
0161