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

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 "PlasmaRecordMe.h"
0008 #include <QCoreApplication>
0009 #include <QDir>
0010 #include <QGuiApplication>
0011 #include <QLoggingCategory>
0012 #include <QProcess>
0013 #include <QQmlApplicationEngine>
0014 #include <QQmlContext>
0015 #include <QQuickItem>
0016 #include <QQuickView>
0017 #include <QRegularExpression>
0018 #include <QScreen>
0019 #include <QThread>
0020 #include <QTimer>
0021 #include <QUuid>
0022 
0023 
0024 PlasmaRecordMe::PlasmaRecordMe(Screencasting::CursorMode cursorMode, const QStringList &sources, bool doSelection, QObject *parent)
0025     : QObject(parent)
0026     , m_cursorMode(cursorMode)
0027     , m_durationTimer(new QTimer(this))
0028     , m_sources(sources)
0029     , m_engine(new QQmlApplicationEngine(this))
0030 {
0031     connect(m_engine, &QQmlEngine::quit, qGuiApp, &QCoreApplication::quit);
0032     m_engine->setInitialProperties({
0033         { QStringLiteral("app"), QVariant::fromValue<QObject *>(this) },
0034     });
0035     m_engine->load(QUrl(QStringLiteral("qrc:/main.qml")));
0036 
0037     m_screencasting = new Screencasting(this);
0038 
0039     m_durationTimer->setSingleShot(true);
0040 
0041     for (auto screen : qGuiApp->screens()) {
0042         addScreen(screen);
0043     }
0044     connect(qGuiApp, &QGuiApplication::screenAdded, this, &PlasmaRecordMe::addScreen);
0045 
0046     if (m_sources.isEmpty() || m_sources.contains(QLatin1String("region"))) {
0047         connect(this, &PlasmaRecordMe::workspaceChanged, this, [this] {
0048             qDeleteAll(m_workspaceStreams);
0049             m_workspaceStreams += m_screencasting->createRegionStream(m_workspace, 1, m_cursorMode);
0050             m_workspaceStreams += m_screencasting->createRegionStream(m_workspace, 1, m_cursorMode);
0051             start(m_workspaceStreams[0], true);
0052             start(m_workspaceStreams[1], false);
0053         });
0054     }
0055 
0056     if (doSelection) {
0057         requestSelection();
0058     }
0059 
0060     for (const auto &source : m_sources) {
0061         bool ok = false;
0062         auto node = source.toInt(&ok);
0063         if (ok) {
0064             startNode(node);
0065         }
0066     }
0067 
0068     for (auto screen : qGuiApp->screens()) {
0069         m_workspace |= screen->geometry();
0070     }
0071     Q_EMIT workspaceChanged();
0072 }
0073 
0074 PlasmaRecordMe::~PlasmaRecordMe()
0075 {
0076 }
0077 
0078 bool PlasmaRecordMe::matches(const QString &value)
0079 {
0080     if (m_sources.isEmpty()) {
0081         return true;
0082     }
0083 
0084     for (const auto &source : m_sources) {
0085         const QRegularExpression rx(source);
0086         if (rx.match(value).hasMatch()) {
0087             return true;
0088         }
0089     }
0090     return false;
0091 }
0092 
0093 void PlasmaRecordMe::addWindow(const QVariantList &uuid, const QString &appId)
0094 {
0095     if (matches(appId) && uuid.size() > 0) {
0096         QString uuidString = uuid.at(0).value<QString>();
0097         auto stream = m_screencasting->createWindowStream(uuidString, m_cursorMode);
0098         stream->setObjectName(appId);
0099         start(stream, true);
0100         stream = m_screencasting->createWindowStream(uuidString, m_cursorMode);
0101         stream->setObjectName(appId);
0102         start(stream, false);
0103     }
0104 }
0105 
0106 void PlasmaRecordMe::addStream(int nodeid, const QString &displayText, int fd, bool allowDmaBuf)
0107 {
0108     const auto roots = m_engine->rootObjects();
0109     for (auto root : roots) {
0110         auto mo = root->metaObject();
0111         mo->invokeMethod(root,
0112                          "addStream",
0113                          Q_ARG(QVariant, QVariant::fromValue<int>(nodeid)),
0114                          Q_ARG(QVariant, displayText),
0115                          Q_ARG(QVariant, fd),
0116                          Q_ARG(QVariant, allowDmaBuf));
0117     }
0118 }
0119 
0120 void PlasmaRecordMe::startNode(int node)
0121 {
0122     addStream(node, QStringLiteral("raw node %1").arg(node), 0, true);
0123 }
0124 
0125 void PlasmaRecordMe::addScreen(QScreen *screen)
0126 {
0127     auto f = [this, screen] {
0128         start(m_screencasting->createOutputStream(screen, m_cursorMode), false);
0129         start(m_screencasting->createOutputStream(screen, m_cursorMode), true);
0130     };
0131     if (matches(screen->name())) {
0132         connect(this, &PlasmaRecordMe::cursorModeChanged, screen, f);
0133         f();
0134     } else if (matches(screen->model())) {
0135         connect(this, &PlasmaRecordMe::cursorModeChanged, screen, f);
0136         f();
0137     }
0138 }
0139 
0140 void PlasmaRecordMe::start(ScreencastingStream *stream, bool allowDmaBuf)
0141 {
0142     qDebug() << "start" << stream;
0143     connect(stream, &ScreencastingStream::failed, this, [this] (const QString &error) {
0144         qWarning() << "stream failed" << error;const auto roots = m_engine->rootObjects();
0145         for (auto root : roots) {
0146             auto mo = root->metaObject();
0147             mo->invokeMethod(root, "showPassiveNotification", Qt::QueuedConnection, Q_ARG(QVariant, QVariant(error))
0148                                                                                   , Q_ARG(QVariant, {})
0149                                                                                   , Q_ARG(QVariant, {})
0150                                                                                   , Q_ARG(QVariant, {})
0151             );
0152         }
0153     });
0154     connect(stream, &ScreencastingStream::closed, this, [this, stream] {
0155         auto nodeId = stream->property("nodeid").toInt();
0156         qDebug() << "bye bye" << stream << nodeId;
0157 
0158         const auto roots = m_engine->rootObjects();
0159         for (auto root : roots) {
0160             auto mo = root->metaObject();
0161             mo->invokeMethod(root, "removeStream", Qt::QueuedConnection, Q_ARG(QVariant, QVariant::fromValue<quint32>(nodeId)));
0162         }
0163     });
0164     connect(stream, &ScreencastingStream::created, this, [this, stream, allowDmaBuf](quint32 nodeId) {
0165         stream->setProperty("nodeid", nodeId);
0166         qDebug() << "starting..." << nodeId;
0167         addStream(nodeId, stream->objectName(), 0, allowDmaBuf);
0168     });
0169     connect(this, &PlasmaRecordMe::cursorModeChanged, stream, &ScreencastingStream::closed);
0170 }
0171 
0172 void PlasmaRecordMe::setDuration(int duration)
0173 {
0174     m_durationTimer->setInterval(duration);
0175 }
0176 
0177 void PlasmaRecordMe::createVirtualMonitor()
0178 {
0179     m_screencasting->createVirtualMonitorStream(QStringLiteral("recordme"), {1920, 1080}, 1, m_cursorMode);
0180 }
0181 
0182 void PlasmaRecordMe::setRegionPressed(const QString &screenName, int x, int y)
0183 {
0184     const auto screens = QGuiApplication::screens();
0185     auto screenIt = std::find_if(screens.begin(), screens.end(), [&screenName](auto screen) {
0186         return screen->name() == screenName;
0187     });
0188     m_region.setTopLeft((*screenIt)->geometry().topLeft() + QPoint{x, y});
0189 }
0190 
0191 void PlasmaRecordMe::setRegionMoved(const QString &screenName, int x, int y)
0192 {
0193     const auto screens = QGuiApplication::screens();
0194     auto screenIt = std::find_if(screens.begin(), screens.end(), [&screenName](auto screen) {
0195         return screen->name() == screenName;
0196     });
0197     m_region.setBottomRight((*screenIt)->geometry().topLeft() + QPoint{x, y});
0198     Q_EMIT regionChanged(m_region);
0199 }
0200 
0201 void PlasmaRecordMe::setRegionReleased(const QString &screenName, int x, int y)
0202 {
0203     setRegionMoved(screenName, x, y);
0204     m_region = m_region.normalized();
0205     Q_EMIT regionFinal(m_region);
0206 }
0207 
0208 void PlasmaRecordMe::requestSelection()
0209 {
0210     for (auto *screen : qApp->screens()) {
0211         auto view = new QQuickView(m_engine, nullptr);
0212         view->setScreen(screen);
0213         view->setInitialProperties({{QStringLiteral("app"), QVariant::fromValue<QObject *>(this)}});
0214         view->setSource(QUrl(QStringLiteral("qrc:/RegionSelector.qml")));
0215         view->setColor(Qt::transparent);
0216         view->showFullScreen();
0217         connect(this, &PlasmaRecordMe::regionFinal, view, &QQuickView::deleteLater);
0218     }
0219 
0220     connect(this, &PlasmaRecordMe::regionFinal, this, [this](const QRect &region) {
0221         for (auto stream : m_regionStreams) {
0222             Q_EMIT stream->closed();
0223         }
0224         qDeleteAll(m_regionStreams);
0225         m_regionStreams += m_screencasting->createRegionStream(region, 1, m_cursorMode);
0226         m_regionStreams += m_screencasting->createRegionStream(region, 1, m_cursorMode);
0227         start(m_regionStreams[0], true);
0228         start(m_regionStreams[1], false);
0229     });
0230 }
0231 
0232 QRect PlasmaRecordMe::region() const
0233 {
0234     return m_region.normalized();
0235 }
0236 
0237 #include "moc_PlasmaRecordMe.cpp"