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