File indexing completed on 2024-04-28 16:55:47
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 "screencast_debug.h" 0014 #include "screenchooserdialog.h" 0015 #include "session.h" 0016 #include "utils.h" 0017 #include "waylandintegration.h" 0018 0019 #include <KConfigGroup> 0020 #include <KLocalizedString> 0021 #include <KSharedConfig> 0022 #include <KWayland/Client/plasmawindowmodel.h> 0023 0024 #include <QDBusArgument> 0025 #include <QDBusMetaType> 0026 #include <QDataStream> 0027 #include <QIODevice> 0028 0029 struct RestoreData { 0030 static uint currentRestoreDataVersion() 0031 { 0032 return 1; 0033 } 0034 0035 QString session; 0036 quint32 version = 0; 0037 QVariantMap payload; 0038 }; 0039 0040 const QDBusArgument &operator<<(QDBusArgument &arg, const RestoreData &data) 0041 { 0042 arg.beginStructure(); 0043 arg << data.session; 0044 arg << data.version; 0045 0046 QByteArray payloadSerialised; 0047 { 0048 QDataStream ds(&payloadSerialised, QIODevice::WriteOnly); 0049 ds << data.payload; 0050 } 0051 0052 arg << QDBusVariant(payloadSerialised); 0053 arg.endStructure(); 0054 return arg; 0055 } 0056 const QDBusArgument &operator>>(const QDBusArgument &arg, RestoreData &data) 0057 { 0058 arg.beginStructure(); 0059 arg >> data.session; 0060 arg >> data.version; 0061 0062 QDBusVariant payloadVariant; 0063 arg >> payloadVariant; 0064 { 0065 QByteArray payloadSerialised = payloadVariant.variant().toByteArray(); 0066 QDataStream ds(&payloadSerialised, QIODevice::ReadOnly); 0067 ds >> data.payload; 0068 } 0069 arg.endStructure(); 0070 return arg; 0071 } 0072 0073 QDebug operator<<(QDebug dbg, const RestoreData &c) 0074 { 0075 dbg.nospace() << "RestoreData(" << c.session << ", " << c.version << ", " << c.payload << ")"; 0076 return dbg.space(); 0077 } 0078 0079 Q_DECLARE_METATYPE(RestoreData) 0080 0081 ScreenCastPortal::ScreenCastPortal(QObject *parent) 0082 : QDBusAbstractAdaptor(parent) 0083 { 0084 qDBusRegisterMetaType<RestoreData>(); 0085 } 0086 0087 ScreenCastPortal::~ScreenCastPortal() 0088 { 0089 } 0090 0091 bool ScreenCastPortal::inhibitionsEnabled() const 0092 { 0093 if (!WaylandIntegration::isStreamingAvailable()) { 0094 return false; 0095 } 0096 0097 auto cfg = KSharedConfig::openConfig(QStringLiteral("plasmanotifyrc")); 0098 0099 KConfigGroup grp(cfg, "DoNotDisturb"); 0100 0101 return grp.readEntry("WhenScreenSharing", true); 0102 } 0103 0104 uint ScreenCastPortal::CreateSession(const QDBusObjectPath &handle, 0105 const QDBusObjectPath &session_handle, 0106 const QString &app_id, 0107 const QVariantMap &options, 0108 QVariantMap &results) 0109 { 0110 Q_UNUSED(results) 0111 0112 qCDebug(XdgDesktopPortalKdeScreenCast) << "CreateSession called with parameters:"; 0113 qCDebug(XdgDesktopPortalKdeScreenCast) << " handle: " << handle.path(); 0114 qCDebug(XdgDesktopPortalKdeScreenCast) << " session_handle: " << session_handle.path(); 0115 qCDebug(XdgDesktopPortalKdeScreenCast) << " app_id: " << app_id; 0116 qCDebug(XdgDesktopPortalKdeScreenCast) << " options: " << options; 0117 0118 Session *session = Session::createSession(this, Session::ScreenCast, app_id, session_handle.path()); 0119 0120 if (!session) { 0121 return 2; 0122 } 0123 0124 if (!WaylandIntegration::isStreamingAvailable()) { 0125 qCWarning(XdgDesktopPortalKdeScreenCast) << "zkde_screencast_unstable_v1 does not seem to be available"; 0126 return 2; 0127 } 0128 0129 connect(session, &Session::closed, [session] { 0130 auto screencastSession = qobject_cast<ScreenCastSession *>(session); 0131 const auto streams = screencastSession->streams(); 0132 for (const WaylandIntegration::Stream &stream : streams) { 0133 WaylandIntegration::stopStreaming(stream.nodeId); 0134 } 0135 }); 0136 return 0; 0137 } 0138 0139 uint ScreenCastPortal::SelectSources(const QDBusObjectPath &handle, 0140 const QDBusObjectPath &session_handle, 0141 const QString &app_id, 0142 const QVariantMap &options, 0143 QVariantMap &results) 0144 { 0145 Q_UNUSED(results) 0146 0147 qCDebug(XdgDesktopPortalKdeScreenCast) << "SelectSource called with parameters:"; 0148 qCDebug(XdgDesktopPortalKdeScreenCast) << " handle: " << handle.path(); 0149 qCDebug(XdgDesktopPortalKdeScreenCast) << " session_handle: " << session_handle.path(); 0150 qCDebug(XdgDesktopPortalKdeScreenCast) << " app_id: " << app_id; 0151 qCDebug(XdgDesktopPortalKdeScreenCast) << " options: " << options; 0152 0153 ScreenCastSession *session = qobject_cast<ScreenCastSession *>(Session::getSession(session_handle.path())); 0154 0155 if (!session) { 0156 qCWarning(XdgDesktopPortalKdeScreenCast) << "Tried to select sources on non-existing session " << session_handle.path(); 0157 return 2; 0158 } 0159 0160 session->setOptions(options); 0161 0162 // Might be also a RemoteDesktopSession 0163 if (session->type() == Session::RemoteDesktop) { 0164 RemoteDesktopSession *remoteDesktopSession = qobject_cast<RemoteDesktopSession *>(session); 0165 if (remoteDesktopSession) { 0166 remoteDesktopSession->setScreenSharingEnabled(true); 0167 } 0168 } 0169 0170 return 0; 0171 } 0172 0173 uint ScreenCastPortal::Start(const QDBusObjectPath &handle, 0174 const QDBusObjectPath &session_handle, 0175 const QString &app_id, 0176 const QString &parent_window, 0177 const QVariantMap &options, 0178 QVariantMap &results) 0179 { 0180 qCDebug(XdgDesktopPortalKdeScreenCast) << "Start called with parameters:"; 0181 qCDebug(XdgDesktopPortalKdeScreenCast) << " handle: " << handle.path(); 0182 qCDebug(XdgDesktopPortalKdeScreenCast) << " session_handle: " << session_handle.path(); 0183 qCDebug(XdgDesktopPortalKdeScreenCast) << " app_id: " << app_id; 0184 qCDebug(XdgDesktopPortalKdeScreenCast) << " parent_window: " << parent_window; 0185 qCDebug(XdgDesktopPortalKdeScreenCast) << " options: " << options; 0186 0187 QPointer<ScreenCastSession> session = qobject_cast<ScreenCastSession *>(Session::getSession(session_handle.path())); 0188 0189 if (!session) { 0190 qCWarning(XdgDesktopPortalKdeScreenCast) << "Tried to call start on non-existing session " << session_handle.path(); 0191 return 2; 0192 } 0193 0194 if (WaylandIntegration::screens().isEmpty()) { 0195 qCWarning(XdgDesktopPortalKdeScreenCast) << "Failed to show dialog as there is no screen to select"; 0196 return 2; 0197 } 0198 0199 const PersistMode persist = session->persistMode(); 0200 bool valid = false; 0201 QList<Output> selectedOutputs; 0202 QVector<QMap<int, QVariant>> selectedWindows; 0203 if (persist != NoPersist && session->restoreData().isValid()) { 0204 const RestoreData restoreData = qdbus_cast<RestoreData>(session->restoreData().value<QDBusArgument>()); 0205 if (restoreData.session == QLatin1String("KDE") && restoreData.version == RestoreData::currentRestoreDataVersion()) { 0206 const QVariantMap restoreDataPayload = restoreData.payload; 0207 const QVariantList restoreOutputs = restoreDataPayload[QStringLiteral("outputs")].toList(); 0208 if (!restoreOutputs.isEmpty()) { 0209 OutputsModel model(OutputsModel::WorkspaceIncluded, this); 0210 for (const auto &outputUniqueId : restoreOutputs) { 0211 for (int i = 0, c = model.rowCount(); i < c; ++i) { 0212 const Output &iOutput = model.outputAt(i); 0213 if (iOutput.uniqueId() == outputUniqueId) { 0214 selectedOutputs << iOutput; 0215 } 0216 } 0217 } 0218 valid = selectedOutputs.count() == restoreOutputs.count(); 0219 } 0220 0221 const QStringList restoreWindows = restoreDataPayload[QStringLiteral("windows")].toStringList(); 0222 if (!restoreWindows.isEmpty()) { 0223 const KWayland::Client::PlasmaWindowModel model(WaylandIntegration::plasmaWindowManagement()); 0224 for (const QString &windowUuid : restoreWindows) { 0225 for (int i = 0, c = model.rowCount(); i < c; ++i) { 0226 const QModelIndex index = model.index(i, 0); 0227 0228 if (model.data(index, KWayland::Client::PlasmaWindowModel::Uuid) == windowUuid) { 0229 selectedWindows << model.itemData(index); 0230 } 0231 } 0232 } 0233 valid = selectedWindows.count() == restoreWindows.count(); 0234 } 0235 } 0236 } 0237 0238 bool allowRestore = valid; 0239 if (!valid) { 0240 QScopedPointer<ScreenChooserDialog, QScopedPointerDeleteLater> screenDialog( 0241 new ScreenChooserDialog(app_id, session->multipleSources(), SourceTypes(session->types()))); 0242 connect(session, &Session::closed, screenDialog.data(), &ScreenChooserDialog::reject); 0243 Utils::setParentWindow(screenDialog->windowHandle(), parent_window); 0244 Request::makeClosableDialogRequest(handle, screenDialog.get()); 0245 valid = screenDialog->exec(); 0246 if (valid) { 0247 allowRestore = screenDialog->allowRestore(); 0248 selectedOutputs = screenDialog->selectedOutputs(); 0249 selectedWindows = screenDialog->selectedWindows(); 0250 } 0251 } 0252 0253 if (valid && session) { 0254 QVariantList outputs; 0255 QStringList windows; 0256 WaylandIntegration::Streams streams; 0257 Screencasting::CursorMode cursorMode = Screencasting::CursorMode(session->cursorMode()); 0258 for (const auto &output : qAsConst(selectedOutputs)) { 0259 WaylandIntegration::Stream stream; 0260 switch (output.outputType()) { 0261 case WaylandIntegration::WaylandOutput::Workspace: 0262 stream = WaylandIntegration::startStreamingWorkspace(cursorMode); 0263 break; 0264 case WaylandIntegration::WaylandOutput::Virtual: 0265 stream = WaylandIntegration::startStreamingVirtual(output.uniqueId(), {1920, 1080}, cursorMode); 0266 break; 0267 default: 0268 stream = WaylandIntegration::startStreamingOutput(output.waylandOutputName(), cursorMode); 0269 break; 0270 } 0271 0272 if (!stream.isValid()) { 0273 qCWarning(XdgDesktopPortalKdeScreenCast) << "Invalid screen!" << output.outputType() << output.uniqueId(); 0274 return 2; 0275 } 0276 0277 if (allowRestore) { 0278 outputs += output.uniqueId(); 0279 } 0280 streams << stream; 0281 } 0282 for (const auto &win : qAsConst(selectedWindows)) { 0283 WaylandIntegration::Stream stream = WaylandIntegration::startStreamingWindow(win, cursorMode); 0284 if (!stream.isValid()) { 0285 qCWarning(XdgDesktopPortalKdeScreenCast) << "Invalid window!" << win; 0286 return 2; 0287 } 0288 0289 if (allowRestore) { 0290 windows += win[KWayland::Client::PlasmaWindowModel::Uuid].toString(); 0291 } 0292 streams << stream; 0293 } 0294 0295 if (streams.isEmpty()) { 0296 qCWarning(XdgDesktopPortalKdeScreenCast) << "Pipewire stream is not ready to be streamed"; 0297 return 2; 0298 } 0299 0300 session->setStreams(streams); 0301 results.insert(QStringLiteral("streams"), QVariant::fromValue<WaylandIntegration::Streams>(streams)); 0302 if (allowRestore) { 0303 results.insert("persist_mode", quint32(persist)); 0304 if (persist != NoPersist) { 0305 const RestoreData restoreData = {"KDE", 0306 RestoreData::currentRestoreDataVersion(), 0307 QVariantMap{ 0308 {"outputs", outputs}, 0309 {"windows", windows}, 0310 }}; 0311 results.insert("restore_data", QVariant::fromValue<RestoreData>(restoreData)); 0312 } 0313 } 0314 0315 if (inhibitionsEnabled()) { 0316 new NotificationInhibition(app_id, i18nc("Do not disturb mode is enabled because...", "Screen sharing in progress"), session); 0317 } 0318 return 0; 0319 } 0320 0321 return 1; 0322 }