#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class WindowReceiver : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.codex.ReforgerQueueKWinProbe") public: QString json; QString error; bool done = false; public slots: void WindowsJson(const QString &payload) { json = payload; done = true; QCoreApplication::quit(); } void Error(const QString &message) { error = message; done = true; QCoreApplication::quit(); } }; QDBusConnection sessionBus() { QDBusConnection bus = QDBusConnection::sessionBus(); if (bus.isConnected()) { return bus; } QString runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR"); if (runtimeDir.isEmpty()) { runtimeDir = QStringLiteral("/run/user/%1").arg(getuid()); } return QDBusConnection::connectToBus( QStringLiteral("unix:path=%1/bus").arg(runtimeDir), QStringLiteral("reforger-queue-session")); } QString jsString(const QString &value) { QJsonArray array; array.append(value); QByteArray encoded = QJsonDocument(array).toJson(QJsonDocument::Compact); encoded.chop(1); encoded.remove(0, 1); return QString::fromUtf8(encoded); } int capture(const QString &method, const QString &target, const QString &outputPath, QTextStream &out, QTextStream &err) { QFile file(outputPath); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { err << "failed to open output file: " << file.errorString() << "\n"; return 1; } QDBusConnection bus = sessionBus(); if (!bus.isConnected()) { err << "failed to connect to the session D-Bus: " << bus.lastError().message() << "\n"; return 1; } QDBusInterface iface( QStringLiteral("org.kde.KWin"), QStringLiteral("/org/kde/KWin/ScreenShot2"), QStringLiteral("org.kde.KWin.ScreenShot2"), bus); if (!iface.isValid()) { err << "failed to connect to KWin ScreenShot2: " << iface.lastError().message() << "\n"; return 1; } QVariantMap options; QDBusUnixFileDescriptor fd(file.handle()); QDBusReply reply = iface.call(method, target, options, QVariant::fromValue(fd)); file.close(); if (!reply.isValid()) { err << method << " failed for " << target << ": " << reply.error().name() << ": " << reply.error().message() << "\n"; return 1; } QJsonObject resultJson; const QVariantMap resultMap = reply.value(); for (auto it = resultMap.constBegin(); it != resultMap.constEnd(); ++it) { resultJson.insert(it.key(), QJsonValue::fromVariant(it.value())); } out << QJsonDocument(resultJson).toJson(QJsonDocument::Compact) << "\n"; return 0; } QString windowProbeScript(const QString &service, const QString &path, const QString &interface) { return QStringLiteral(R"JS( function safeString(value) { if (value === undefined || value === null) { return ""; } return String(value); } function safeNumber(value) { var number = Number(value); return isNaN(number) ? 0 : number; } function safeBool(value) { return Boolean(value); } function prop(object, name, fallback) { try { if (object[name] === undefined || object[name] === null) { return fallback; } return object[name]; } catch (error) { return fallback; } } function rect(value) { if (!value) { return {"x": 0, "y": 0, "width": 0, "height": 0}; } return { "x": Math.round(safeNumber(prop(value, "x", 0))), "y": Math.round(safeNumber(prop(value, "y", 0))), "width": Math.round(safeNumber(prop(value, "width", 0))), "height": Math.round(safeNumber(prop(value, "height", 0))) }; } function outputName(window) { try { if (window.output && window.output.name) { return String(window.output.name); } } catch (error) { } return ""; } function windowInfo(window) { var geometry = rect(prop(window, "frameGeometry", prop(window, "bufferGeometry", null))); return { "internalId": safeString(prop(window, "internalId", "")), "pid": safeNumber(prop(window, "pid", 0)), "caption": safeString(prop(window, "caption", "")), "resourceClass": safeString(prop(window, "resourceClass", "")), "resourceName": safeString(prop(window, "resourceName", "")), "windowRole": safeString(prop(window, "windowRole", "")), "desktopFileName": safeString(prop(window, "desktopFileName", "")), "output": outputName(window), "frameGeometry": geometry, "normalWindow": safeBool(prop(window, "normalWindow", false)), "fullScreen": safeBool(prop(window, "fullScreen", false)), "minimized": safeBool(prop(window, "minimized", false)), "hidden": safeBool(prop(window, "hidden", false)), "keepAbove": safeBool(prop(window, "keepAbove", false)), "skipTaskbar": safeBool(prop(window, "skipTaskbar", false)) }; } try { var windows = workspace.windowList().map(windowInfo); callDBus(%1, %2, %3, "WindowsJson", JSON.stringify(windows)); } catch (error) { callDBus(%1, %2, %3, "Error", safeString(error && error.stack ? error.stack : error)); } )JS") .arg(jsString(service), jsString(path), jsString(interface)); } int listWindows(QCoreApplication &app, QTextStream &out, QTextStream &err) { QDBusConnection bus = sessionBus(); if (!bus.isConnected()) { err << "failed to connect to the session D-Bus: " << bus.lastError().message() << "\n"; return 1; } const QString service = QStringLiteral("org.codex.ReforgerQueueKWinProbe%1").arg(QCoreApplication::applicationPid()); const QString path = QStringLiteral("/Probe"); const QString interface = QStringLiteral("org.codex.ReforgerQueueKWinProbe"); WindowReceiver receiver; if (!bus.registerService(service)) { err << "failed to register D-Bus service " << service << ": " << bus.lastError().message() << "\n"; return 1; } if (!bus.registerObject(path, &receiver, QDBusConnection::ExportAllSlots)) { err << "failed to register D-Bus object " << path << ": " << bus.lastError().message() << "\n"; bus.unregisterService(service); return 1; } QTemporaryFile scriptFile; scriptFile.setFileTemplate(QDir::tempPath() + QStringLiteral("/reforger-kwin-window-probe-XXXXXX.js")); if (!scriptFile.open()) { err << "failed to create temporary KWin script: " << scriptFile.errorString() << "\n"; bus.unregisterObject(path); bus.unregisterService(service); return 1; } scriptFile.write(windowProbeScript(service, path, interface).toUtf8()); scriptFile.close(); const QString pluginName = QStringLiteral("reforger-queue-window-probe-%1").arg(QCoreApplication::applicationPid()); QDBusInterface scripting( QStringLiteral("org.kde.KWin"), QStringLiteral("/Scripting"), QStringLiteral("org.kde.kwin.Scripting"), bus); if (!scripting.isValid()) { err << "failed to connect to KWin scripting: " << scripting.lastError().message() << "\n"; bus.unregisterObject(path); bus.unregisterService(service); return 1; } QDBusReply loadReply = scripting.call(QStringLiteral("loadScript"), scriptFile.fileName(), pluginName); if (!loadReply.isValid() || loadReply.value() < 0) { err << "failed to load KWin window probe script: " << loadReply.error().name() << ": " << loadReply.error().message() << "\n"; bus.unregisterObject(path); bus.unregisterService(service); return 1; } const int scriptId = loadReply.value(); QDBusInterface script( QStringLiteral("org.kde.KWin"), QStringLiteral("/Scripting/Script%1").arg(scriptId), QStringLiteral("org.kde.kwin.Script"), bus); QDBusMessage runReply = script.call(QStringLiteral("run")); if (runReply.type() == QDBusMessage::ErrorMessage) { err << "failed to run KWin window probe script: " << runReply.errorName() << ": " << runReply.errorMessage() << "\n"; scripting.call(QStringLiteral("unloadScript"), pluginName); bus.unregisterObject(path); bus.unregisterService(service); return 1; } QTimer::singleShot(5000, &app, [&receiver]() { if (!receiver.done) { receiver.error = QStringLiteral("timed out waiting for KWin window probe results"); receiver.done = true; QCoreApplication::quit(); } }); app.exec(); scripting.call(QStringLiteral("unloadScript"), pluginName); bus.unregisterObject(path); bus.unregisterService(service); if (!receiver.error.isEmpty()) { err << receiver.error << "\n"; return 1; } if (receiver.json.isEmpty()) { err << "KWin window probe returned no data\n"; return 1; } out << receiver.json << "\n"; return 0; } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QTextStream out(stdout); QTextStream err(stderr); const QStringList args = QCoreApplication::arguments(); if (args.size() == 3 && !args.at(1).startsWith(QLatin1String("--"))) { return capture(QStringLiteral("CaptureScreen"), args.at(1), args.at(2), out, err); } if (args.size() == 2 && args.at(1) == QLatin1String("--list-windows")) { return listWindows(app, out, err); } if (args.size() == 4 && args.at(1) == QLatin1String("--capture-screen")) { return capture(QStringLiteral("CaptureScreen"), args.at(2), args.at(3), out, err); } if (args.size() == 4 && args.at(1) == QLatin1String("--capture-window")) { return capture(QStringLiteral("CaptureWindow"), args.at(2), args.at(3), out, err); } err << "usage:\n" << " " << args.value(0) << " OUTPUT_NAME OUTPUT_FILE\n" << " " << args.value(0) << " --capture-screen OUTPUT_NAME OUTPUT_FILE\n" << " " << args.value(0) << " --capture-window INTERNAL_ID OUTPUT_FILE\n" << " " << args.value(0) << " --list-windows\n"; return 64; } #include "kwin_capture_screen.moc"