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 }