File indexing completed on 2024-04-28 05:36:52

0001 /*
0002  * SPDX-FileCopyrightText: 2018 Red Hat Inc
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  *
0006  * SPDX-FileCopyrightText: 2018 Jan Grulich <jgrulich@redhat.com>
0007  * SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0008  */
0009 
0010 #include "screencast.h"
0011 #include "notificationinhibition.h"
0012 #include "request.h"
0013 #include "restoredata.h"
0014 #include "screencast_debug.h"
0015 #include "screenchooserdialog.h"
0016 #include "session.h"
0017 #include "utils.h"
0018 #include "waylandintegration.h"
0019 
0020 #include <KConfigGroup>
0021 #include <KLocalizedString>
0022 #include <KSharedConfig>
0023 #include <KWayland/Client/plasmawindowmodel.h>
0024 
0025 #include <QDBusArgument>
0026 #include <QDBusMetaType>
0027 #include <QDataStream>
0028 #include <QGuiApplication>
0029 #include <QIODevice>
0030 
0031 ScreenCastPortal::ScreenCastPortal(QObject *parent)
0032     : QDBusAbstractAdaptor(parent)
0033 {
0034     qDBusRegisterMetaType<RestoreData>();
0035 }
0036 
0037 ScreenCastPortal::~ScreenCastPortal()
0038 {
0039 }
0040 
0041 bool ScreenCastPortal::inhibitionsEnabled() const
0042 {
0043     if (!WaylandIntegration::isStreamingAvailable()) {
0044         return false;
0045     }
0046 
0047     auto cfg = KSharedConfig::openConfig(QStringLiteral("plasmanotifyrc"));
0048 
0049     KConfigGroup grp(cfg, "DoNotDisturb");
0050 
0051     return grp.readEntry("WhenScreenSharing", true);
0052 }
0053 
0054 uint ScreenCastPortal::CreateSession(const QDBusObjectPath &handle,
0055                                      const QDBusObjectPath &session_handle,
0056                                      const QString &app_id,
0057                                      const QVariantMap &options,
0058                                      QVariantMap &results)
0059 {
0060     Q_UNUSED(results)
0061 
0062     qCDebug(XdgDesktopPortalKdeScreenCast) << "CreateSession called with parameters:";
0063     qCDebug(XdgDesktopPortalKdeScreenCast) << "    handle: " << handle.path();
0064     qCDebug(XdgDesktopPortalKdeScreenCast) << "    session_handle: " << session_handle.path();
0065     qCDebug(XdgDesktopPortalKdeScreenCast) << "    app_id: " << app_id;
0066     qCDebug(XdgDesktopPortalKdeScreenCast) << "    options: " << options;
0067 
0068     Session *session = Session::createSession(this, Session::ScreenCast, app_id, session_handle.path());
0069 
0070     if (!session) {
0071         return 2;
0072     }
0073 
0074     if (!WaylandIntegration::isStreamingAvailable()) {
0075         qCWarning(XdgDesktopPortalKdeScreenCast) << "zkde_screencast_unstable_v1 does not seem to be available";
0076         return 2;
0077     }
0078 
0079     connect(session, &Session::closed, [session] {
0080         auto screencastSession = qobject_cast<ScreenCastSession *>(session);
0081         const auto streams = screencastSession->streams();
0082         for (const WaylandIntegration::Stream &stream : streams) {
0083             WaylandIntegration::stopStreaming(stream.nodeId);
0084         }
0085     });
0086     return 0;
0087 }
0088 
0089 uint ScreenCastPortal::SelectSources(const QDBusObjectPath &handle,
0090                                      const QDBusObjectPath &session_handle,
0091                                      const QString &app_id,
0092                                      const QVariantMap &options,
0093                                      QVariantMap &results)
0094 {
0095     Q_UNUSED(results)
0096 
0097     qCDebug(XdgDesktopPortalKdeScreenCast) << "SelectSource called with parameters:";
0098     qCDebug(XdgDesktopPortalKdeScreenCast) << "    handle: " << handle.path();
0099     qCDebug(XdgDesktopPortalKdeScreenCast) << "    session_handle: " << session_handle.path();
0100     qCDebug(XdgDesktopPortalKdeScreenCast) << "    app_id: " << app_id;
0101     qCDebug(XdgDesktopPortalKdeScreenCast) << "    options: " << options;
0102 
0103     ScreenCastSession *session = qobject_cast<ScreenCastSession *>(Session::getSession(session_handle.path()));
0104 
0105     if (!session) {
0106         qCWarning(XdgDesktopPortalKdeScreenCast) << "Tried to select sources on non-existing session " << session_handle.path();
0107         return 2;
0108     }
0109 
0110     session->setOptions(options);
0111     // Might be also a RemoteDesktopSession
0112     if (session->type() == Session::RemoteDesktop) {
0113         RemoteDesktopSession *remoteDesktopSession = qobject_cast<RemoteDesktopSession *>(session);
0114         if (remoteDesktopSession) {
0115             remoteDesktopSession->setScreenSharingEnabled(true);
0116         }
0117     } else {
0118         session->setPersistMode(ScreenCastPortal::PersistMode(options.value(QStringLiteral("persist_mode")).toUInt()));
0119         session->setRestoreData(options.value(QStringLiteral("restore_data")));
0120     }
0121 
0122     return 0;
0123 }
0124 
0125 uint ScreenCastPortal::Start(const QDBusObjectPath &handle,
0126                              const QDBusObjectPath &session_handle,
0127                              const QString &app_id,
0128                              const QString &parent_window,
0129                              const QVariantMap &options,
0130                              QVariantMap &results)
0131 {
0132     qCDebug(XdgDesktopPortalKdeScreenCast) << "Start called with parameters:";
0133     qCDebug(XdgDesktopPortalKdeScreenCast) << "    handle: " << handle.path();
0134     qCDebug(XdgDesktopPortalKdeScreenCast) << "    session_handle: " << session_handle.path();
0135     qCDebug(XdgDesktopPortalKdeScreenCast) << "    app_id: " << app_id;
0136     qCDebug(XdgDesktopPortalKdeScreenCast) << "    parent_window: " << parent_window;
0137     qCDebug(XdgDesktopPortalKdeScreenCast) << "    options: " << options;
0138 
0139     QPointer<ScreenCastSession> session = qobject_cast<ScreenCastSession *>(Session::getSession(session_handle.path()));
0140 
0141     if (!session) {
0142         qCWarning(XdgDesktopPortalKdeScreenCast) << "Tried to call start on non-existing session " << session_handle.path();
0143         return 2;
0144     }
0145 
0146     if (QGuiApplication::screens().isEmpty()) {
0147         qCWarning(XdgDesktopPortalKdeScreenCast) << "Failed to show dialog as there is no screen to select";
0148         return 2;
0149     }
0150 
0151     const PersistMode persist = session->persistMode();
0152     bool valid = false;
0153     QList<Output> selectedOutputs;
0154     QList<QMap<int, QVariant>> selectedWindows;
0155     QRect selectedRegion;
0156     if (persist != NoPersist && session->restoreData().isValid()) {
0157         const RestoreData restoreData = qdbus_cast<RestoreData>(session->restoreData().value<QDBusArgument>());
0158         if (restoreData.session == QLatin1String("KDE") && restoreData.version == RestoreData::currentRestoreDataVersion()) {
0159             const QVariantMap restoreDataPayload = restoreData.payload;
0160             const QVariantList restoreOutputs = restoreDataPayload[QStringLiteral("outputs")].toList();
0161             if (!restoreOutputs.isEmpty()) {
0162                 OutputsModel model(OutputsModel::WorkspaceIncluded, this);
0163                 for (const auto &outputUniqueId : restoreOutputs) {
0164                     for (int i = 0, c = model.rowCount(); i < c; ++i) {
0165                         const Output &iOutput = model.outputAt(i);
0166                         if (iOutput.uniqueId() == outputUniqueId) {
0167                             selectedOutputs << iOutput;
0168                         }
0169                     }
0170                 }
0171                 valid = selectedOutputs.count() == restoreOutputs.count();
0172             }
0173 
0174             const QStringList restoreWindows = restoreDataPayload[QStringLiteral("windows")].toStringList();
0175             if (!restoreWindows.isEmpty()) {
0176                 const KWayland::Client::PlasmaWindowModel model(WaylandIntegration::plasmaWindowManagement());
0177                 for (const QString &windowUuid : restoreWindows) {
0178                     for (int i = 0, c = model.rowCount(); i < c; ++i) {
0179                         const QModelIndex index = model.index(i, 0);
0180 
0181                         if (model.data(index, KWayland::Client::PlasmaWindowModel::Uuid) == windowUuid) {
0182                             selectedWindows << model.itemData(index);
0183                         }
0184                     }
0185                 }
0186                 valid = selectedWindows.count() == restoreWindows.count();
0187             }
0188         }
0189     }
0190 
0191     bool allowRestore = valid;
0192     if (!valid) {
0193         QScopedPointer<ScreenChooserDialog, QScopedPointerDeleteLater> screenDialog(
0194             new ScreenChooserDialog(app_id, session->multipleSources(), SourceTypes(session->types())));
0195         connect(session, &Session::closed, screenDialog.data(), &ScreenChooserDialog::reject);
0196         Utils::setParentWindow(screenDialog->windowHandle(), parent_window);
0197         Request::makeClosableDialogRequest(handle, screenDialog.get());
0198         valid = screenDialog->exec();
0199         if (valid) {
0200             allowRestore = screenDialog->allowRestore();
0201             selectedOutputs = screenDialog->selectedOutputs();
0202             selectedWindows = screenDialog->selectedWindows();
0203             selectedRegion = screenDialog->selectedRegion();
0204         }
0205     }
0206 
0207     if (valid && session) {
0208         QVariantList outputs;
0209         QStringList windows;
0210         WaylandIntegration::Streams streams;
0211         Screencasting::CursorMode cursorMode = Screencasting::CursorMode(session->cursorMode());
0212         for (const auto &output : std::as_const(selectedOutputs)) {
0213             WaylandIntegration::Stream stream;
0214             switch (output.outputType()) {
0215             case Output::Region:
0216                 stream = WaylandIntegration::startStreamingRegion(selectedRegion, cursorMode);
0217                 break;
0218             case Output::Workspace:
0219                 stream = WaylandIntegration::startStreamingWorkspace(cursorMode);
0220                 break;
0221             case Output::Virtual:
0222                 stream = WaylandIntegration::startStreamingVirtual(output.uniqueId(), {1920, 1080}, cursorMode);
0223                 break;
0224             default:
0225                 stream = WaylandIntegration::startStreamingOutput(output.screen(), cursorMode);
0226                 break;
0227             }
0228 
0229             if (!stream.isValid()) {
0230                 qCWarning(XdgDesktopPortalKdeScreenCast) << "Invalid screen!" << output.outputType() << output.uniqueId();
0231                 return 2;
0232             }
0233 
0234             if (allowRestore) {
0235                 outputs += output.uniqueId();
0236             }
0237             streams << stream;
0238         }
0239         for (const auto &win : std::as_const(selectedWindows)) {
0240             WaylandIntegration::Stream stream = WaylandIntegration::startStreamingWindow(win, cursorMode);
0241             if (!stream.isValid()) {
0242                 qCWarning(XdgDesktopPortalKdeScreenCast) << "Invalid window!" << win;
0243                 return 2;
0244             }
0245 
0246             if (allowRestore) {
0247                 windows += win[KWayland::Client::PlasmaWindowModel::Uuid].toString();
0248             }
0249             streams << stream;
0250         }
0251 
0252         if (streams.isEmpty()) {
0253             qCWarning(XdgDesktopPortalKdeScreenCast) << "Pipewire stream is not ready to be streamed";
0254             return 2;
0255         }
0256 
0257         session->setStreams(streams);
0258         results.insert(QStringLiteral("streams"), QVariant::fromValue<WaylandIntegration::Streams>(streams));
0259         if (allowRestore) {
0260             results.insert("persist_mode", quint32(persist));
0261             if (persist != NoPersist) {
0262                 const RestoreData restoreData = {"KDE",
0263                                                  RestoreData::currentRestoreDataVersion(),
0264                                                  QVariantMap{
0265                                                      {"outputs", outputs},
0266                                                      {"windows", windows},
0267                                                  }};
0268                 results.insert("restore_data", QVariant::fromValue<RestoreData>(restoreData));
0269             }
0270         }
0271 
0272         if (inhibitionsEnabled()) {
0273             new NotificationInhibition(app_id, i18nc("Do not disturb mode is enabled because...", "Screen sharing in progress"), session);
0274         }
0275         return 0;
0276     }
0277 
0278     return 1;
0279 }