File indexing completed on 2024-04-28 05:27:35

0001 /*
0002     SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez <aleixpol@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "RecordMe.h"
0008 #include <QLoggingCategory>
0009 #include <QQmlApplicationEngine>
0010 #include <QQmlContext>
0011 #include <QTimer>
0012 
0013 #include "xdp_dbus_screencast_interface.h"
0014 
0015 Q_DECLARE_METATYPE(Stream)
0016 
0017 QDebug operator<<(QDebug debug, const Stream& plug)
0018 {
0019     QDebugStateSaver saver(debug);
0020     debug.nospace() << "Stream(id: " << plug.id << ", opts: " << plug.opts << ')';
0021     return debug;
0022 }
0023 
0024 
0025 const QDBusArgument &operator<<(const QDBusArgument &argument, const Stream &/*stream*/)
0026 {
0027     argument.beginStructure();
0028 //     argument << stream.id << stream.opts;
0029     argument.endStructure();
0030     return argument;
0031 }
0032 
0033 const QDBusArgument &operator>>(const QDBusArgument &argument, Stream &stream)
0034 {
0035     argument.beginStructure();
0036     argument >> stream.id >> stream.opts;
0037     argument.endStructure();
0038     return argument;
0039 }
0040 
0041 const QDBusArgument &operator>>(const QDBusArgument &argument, QList<Stream> &stream)
0042 {
0043     argument.beginArray();
0044     while ( !argument.atEnd() ) {
0045         Stream element;
0046         argument >> element;
0047         stream.append( element );
0048     }
0049     argument.endArray();
0050     return argument;
0051 }
0052 
0053 RecordMe::RecordMe(QObject* parent)
0054     : QObject(parent)
0055     , iface(new OrgFreedesktopPortalScreenCastInterface(
0056         QLatin1String("org.freedesktop.portal.Desktop"), QLatin1String("/org/freedesktop/portal/desktop"), QDBusConnection::sessionBus(), this))
0057     , m_durationTimer(new QTimer(this))
0058     , m_handleToken(QStringLiteral("RecordMe%1").arg(QRandomGenerator::global()->generate()))
0059     , m_engine(new QQmlApplicationEngine(this))
0060 {
0061     m_engine->rootContext()->setContextProperty(QStringLiteral("app"), this);
0062     m_engine->load(QUrl(QStringLiteral("qrc:/main.qml")));
0063 
0064     // create session
0065     const auto sessionParameters = QVariantMap {
0066         { QLatin1String("session_handle_token"), m_handleToken },
0067         { QLatin1String("handle_token"), m_handleToken }
0068     };
0069     auto sessionReply = iface->CreateSession(sessionParameters);
0070     sessionReply.waitForFinished();
0071     if (!sessionReply.isValid()) {
0072         qWarning("Couldn't initialize the remote control session");
0073         exit(1);
0074         return;
0075     }
0076 
0077     const bool ret = QDBusConnection::sessionBus().connect(QString(),
0078                                                            sessionReply.value().path(),
0079                                                            QLatin1String("org.freedesktop.portal.Request"),
0080                                                            QLatin1String("Response"),
0081                                                            this,
0082                                                            SLOT(response(uint, QVariantMap)));
0083     if (!ret) {
0084         qWarning() << "failed to create session";
0085         exit(2);
0086         return;
0087     }
0088 
0089     qDBusRegisterMetaType<Stream>();
0090     qDBusRegisterMetaType<QList<Stream>>();
0091 
0092     m_durationTimer->setSingleShot(true);
0093 }
0094 
0095 RecordMe::~RecordMe() = default;
0096 
0097 void RecordMe::init(const QDBusObjectPath& path)
0098 {
0099     m_path = path;
0100     {
0101         uint32_t cursor_mode;
0102         if (iface->availableCursorModes() & Metadata) {
0103             cursor_mode = Metadata;
0104         } else {
0105             cursor_mode = Hidden;
0106         }
0107         QVariantMap sourcesParameters = {{QLatin1String("handle_token"), m_handleToken},
0108                                          {QLatin1String("types"), iface->availableSourceTypes()},
0109                                          {QLatin1String("multiple"), false}, // for now?
0110                                          {QLatin1String("cursor_mode"), uint(cursor_mode)},
0111                                          {QLatin1String("persist_mode"), uint(m_persistMode)}};
0112 
0113         if (!m_restoreToken.isEmpty()) {
0114             sourcesParameters[QLatin1String("restore_token")] = m_restoreToken;
0115         }
0116 
0117         auto reply = iface->SelectSources(m_path, sourcesParameters);
0118         reply.waitForFinished();
0119 
0120         if (reply.isError()) {
0121             qWarning() << "Could not select sources" << reply.error();
0122             exit(1);
0123             return;
0124         }
0125         qDebug() << "select sources done" << reply.value().path();
0126     }
0127 }
0128 
0129 void RecordMe::response(uint code, const QVariantMap& results)
0130 {
0131     if (code > 0) {
0132         qWarning() << "error!!!" << code << results;
0133         exit(666);
0134         return;
0135     }
0136 
0137     if (results.contains(QLatin1String("restore_token"))) {
0138         qDebug() << "Restore token:" << results[QLatin1String("restore_token")].toString();
0139     }
0140 
0141     const auto streamsIt = results.constFind(QStringLiteral("streams"));
0142     if (streamsIt != results.constEnd()) {
0143         QList<Stream> streams;
0144         streamsIt->value<QDBusArgument>() >> streams;
0145 
0146         handleStreams(streams);
0147         return;
0148     }
0149 
0150     const auto handleIt = results.constFind(QStringLiteral("session_handle"));
0151     if (handleIt != results.constEnd()) {
0152         init(QDBusObjectPath(handleIt->toString()));
0153         return;
0154     }
0155 
0156     qDebug() << "params" << results << code;
0157     if (results.isEmpty()) {
0158         start();
0159         return;
0160     }
0161 }
0162 
0163 void RecordMe::start()
0164 {
0165     const QVariantMap startParameters = {
0166         { QLatin1String("handle_token"), m_handleToken }
0167     };
0168 
0169     auto reply = iface->Start(m_path, QStringLiteral("org.freedesktop.RecordMe"), startParameters);
0170     reply.waitForFinished();
0171 
0172     if (reply.isError()) {
0173         qWarning() << "Could not start stream" << reply.error();
0174         exit(1);
0175         return;
0176     }
0177     qDebug() << "started!" << reply.value().path();
0178 }
0179 
0180 void RecordMe::handleStreams(const QList<Stream> &streams)
0181 {
0182     const QVariantMap startParameters = {
0183         { QLatin1String("handle_token"), m_handleToken }
0184     };
0185 
0186     auto reply = iface->OpenPipeWireRemote(m_path, startParameters);
0187     reply.waitForFinished();
0188 
0189     if (reply.isError()) {
0190         qWarning() << "Could not start stream" << reply.error();
0191         exit(1);
0192         return;
0193     }
0194 
0195     const int fd = reply.value().takeFileDescriptor();
0196 
0197     const auto roots = m_engine->rootObjects();
0198     for (const auto &stream : streams) {
0199         for (auto root : roots) {
0200             auto mo = root->metaObject();
0201             qDebug() << "feeding..." << stream.id << fd;
0202             mo->invokeMethod(root,
0203                              "addStream",
0204                              Q_ARG(QVariant, QVariant::fromValue<quint32>(stream.id)),
0205                              Q_ARG(QVariant, m_handleToken),
0206                              Q_ARG(QVariant, fd),
0207                              Q_ARG(QVariant, true));
0208         }
0209     }
0210 }
0211 
0212 void RecordMe::setPersistMode(PersistMode persistMode)
0213 {
0214     m_persistMode = persistMode;
0215 }
0216 
0217 void RecordMe::setRestoreToken(const QString &restoreToken)
0218 {
0219     m_restoreToken = restoreToken;
0220 }
0221 
0222 void RecordMe::setDuration(int duration)
0223 {
0224     m_durationTimer->setInterval(duration);
0225 }
0226 
0227 #include "moc_RecordMe.cpp"