// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL // SPDX-FileCopyrightText: 2022 Harald Sitter #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; // When the tests are run under an existing session, the well known org.kde.KWin name will be claimed by the real // kwin, figure out where our test kwin resides on the bus by reverse looking up the PID. std::optional kwinService() { auto bus = QDBusConnection::sessionBus(); const QString kwinPid = qEnvironmentVariable("KWIN_PID"); if (kwinPid.isEmpty()) { return QStringLiteral("org.kde.KWin"); } QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"), QStringLiteral("/org/freedesktop/DBus"), QStringLiteral("org.freedesktop.DBus"), QStringLiteral("ListNames")); QDBusReply namesReply = bus.call(message); if (namesReply.isValid()) { const auto names = namesReply.value(); for (const auto &name : names) { QDBusMessage getPid = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"), QStringLiteral("/org/freedesktop/DBus"), QStringLiteral("org.freedesktop.DBus"), QStringLiteral("GetConnectionUnixProcessID")); getPid << name; QDBusReply pid = bus.call(getPid); if (pid.isValid() && QString::number(pid.value()) == kwinPid) { return name; } } } else { qWarning() << namesReply.error(); } return std::nullopt; } static QImage allocateImage(const QVariantMap &metadata) { bool ok = false; const int width = metadata.value(QStringLiteral("width")).toInt(&ok); if (!ok) { return {}; } const int height = metadata.value(QStringLiteral("height")).toInt(&ok); if (!ok) { return {}; } const int format = metadata.value(QStringLiteral("format")).toInt(&ok); if (!ok) { return {}; } return {width, height, QImage::Format(format)}; } static QImage readImage(int pipeFd, const QVariantMap &metadata) { QFile out; if (!out.open(pipeFd, QFileDevice::ReadOnly, QFileDevice::AutoCloseHandle)) { qWarning() << "failed to open out pipe for reading"; ::close(pipeFd); return {}; } QImage result = allocateImage(metadata); if (result.isNull()) { qWarning() << "failed to allocate image"; return {}; } auto readData = 0; while (readData < result.sizeInBytes()) { if (const int ret = out.read(reinterpret_cast(result.bits() + readData), result.sizeInBytes() - readData); ret >= 0) { readData += ret; } } return result; } int main(int argc, char **argv) { const QGuiApplication app(argc, argv); // Unfortunately since the geometries are not including the DPR we can only look at one screen // and hope that they are all the same :( // // Also since the position is entirely wrong on wayland (always 0,0) we currently ignore all of this // and instead make full screen shots. // // const auto dpr = app.primaryScreen()->devicePixelRatio(); // // auto args const = app.arguments().mid(1); // const auto positionX = int(args.takeFirst().toInt() * dpr); // const auto positionY = int(args.takeFirst().toInt() * dpr); // const auto width = int(args.takeFirst().toInt() * dpr); // const auto height = int(args.takeFirst().toInt() * dpr); // Q_ASSERT(args.isEmpty()); const auto service = kwinService(); if (!service.has_value()) { qWarning() << "kwin dbus service not resolved"; return 1; } auto pipeFds = std::to_array({0, 0}); if (pipe2(pipeFds.data(), O_CLOEXEC | O_NONBLOCK) != 0) { qWarning() << "failed to open pipe" << strerror(errno); return 1; } QVariantList arguments; arguments.append(QVariantMap()); arguments.append(QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds.at(1)))); auto bus = QDBusConnection::sessionBus(); QDBusMessage message = QDBusMessage::createMethodCall(service.value(), QStringLiteral("/org/kde/KWin/ScreenShot2"), QStringLiteral("org.kde.KWin.ScreenShot2"), QStringLiteral("CaptureActiveScreen")); // CaptureWorkspace is nicer but only available in plasma6 message << QVariantMap() << QVariant::fromValue(QDBusUnixFileDescriptor(pipeFds.at(1))); QDBusPendingCall msg = bus.asyncCall(message); msg.waitForFinished(); QDBusReply reply = msg.reply(); if (!reply.isValid()) { qWarning() << reply.error(); return 1; } ::close(pipeFds.at(1)); const auto image = readImage(pipeFds.at(0), reply.value()); QBuffer buf; image.save(&buf, "PNG"); printf("%s", buf.data().toBase64().constData()); // intentionally no newline so we don't need to strip on the py side return 0; }