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 "remotedesktop.h"
0011 #include "remotedesktop_debug.h"
0012 #include "remotedesktopdialog.h"
0013 #include "request.h"
0014 #include "restoredata.h"
0015 #include "session.h"
0016 #include "utils.h"
0017 #include "waylandintegration.h"
0018 #include <KLocalizedString>
0019 #include <KNotification>
0020 #include <QGuiApplication>
0021 #include <QRegion>
0022 #include <QScreen>
0023 
0024 RemoteDesktopPortal::RemoteDesktopPortal(QObject *parent)
0025     : QDBusAbstractAdaptor(parent)
0026 {
0027 }
0028 
0029 RemoteDesktopPortal::~RemoteDesktopPortal()
0030 {
0031 }
0032 
0033 uint RemoteDesktopPortal::CreateSession(const QDBusObjectPath &handle,
0034                                         const QDBusObjectPath &session_handle,
0035                                         const QString &app_id,
0036                                         const QVariantMap &options,
0037                                         QVariantMap &results)
0038 {
0039     Q_UNUSED(results);
0040     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "CreateSession called with parameters:";
0041     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    handle: " << handle.path();
0042     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0043     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    app_id: " << app_id;
0044     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0045 
0046     Session *session = Session::createSession(this, Session::RemoteDesktop, app_id, session_handle.path());
0047 
0048     if (!session) {
0049         return 2;
0050     }
0051 
0052     if (!WaylandIntegration::isStreamingAvailable()) {
0053         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "zkde_screencast_unstable_v1 does not seem to be available";
0054         return 2;
0055     }
0056 
0057     connect(session, &Session::closed, [session] {
0058         auto remoteDesktopSession = qobject_cast<RemoteDesktopSession *>(session);
0059         const auto streams = remoteDesktopSession->streams();
0060         for (const WaylandIntegration::Stream &stream : streams) {
0061             WaylandIntegration::stopStreaming(stream.nodeId);
0062         }
0063     });
0064 
0065     return 0;
0066 }
0067 
0068 uint RemoteDesktopPortal::SelectDevices(const QDBusObjectPath &handle,
0069                                         const QDBusObjectPath &session_handle,
0070                                         const QString &app_id,
0071                                         const QVariantMap &options,
0072                                         QVariantMap &results)
0073 {
0074     Q_UNUSED(results);
0075     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "SelectDevices called with parameters:";
0076     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    handle: " << handle.path();
0077     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0078     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    app_id: " << app_id;
0079     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0080 
0081     const auto types = static_cast<RemoteDesktopPortal::DeviceTypes>(options.value(QStringLiteral("types")).toUInt());
0082     if (types == None) {
0083         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "There are no devices to remotely control";
0084         return 2;
0085     }
0086 
0087     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0088 
0089     if (!session) {
0090         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to select sources on non-existing session " << session_handle.path();
0091         return 2;
0092     }
0093 
0094     session->setDeviceTypes(types);
0095     session->setPersistMode(ScreenCastPortal::PersistMode(options.value(QStringLiteral("persist_mode")).toUInt()));
0096     session->setRestoreData(options.value(QStringLiteral("restore_data")));
0097 
0098     return 0;
0099 }
0100 
0101 uint RemoteDesktopPortal::Start(const QDBusObjectPath &handle,
0102                                 const QDBusObjectPath &session_handle,
0103                                 const QString &app_id,
0104                                 const QString &parent_window,
0105                                 const QVariantMap &options,
0106                                 QVariantMap &results)
0107 {
0108     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "Start called with parameters:";
0109     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    handle: " << handle.path();
0110     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0111     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    app_id: " << app_id;
0112     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    parent_window: " << parent_window;
0113     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0114 
0115     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0116 
0117     if (!session) {
0118         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call start on non-existing session " << session_handle.path();
0119         return 2;
0120     }
0121 
0122     if (QGuiApplication::screens().isEmpty()) {
0123         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Failed to show dialog as there is no screen to select";
0124         return 2;
0125     }
0126 
0127     const ScreenCastPortal::PersistMode persist = session->persistMode();
0128     QList<Output> selectedOutputs;
0129 
0130     bool restored = false;
0131 
0132     if (persist != ScreenCastPortal::NoPersist && session->restoreData().isValid()) {
0133         const RestoreData restoreData = qdbus_cast<RestoreData>(session->restoreData().value<QDBusArgument>());
0134         if (restoreData.session == QLatin1String("KDE") && restoreData.version == RestoreData::currentRestoreDataVersion()) {
0135             // check we asked for the same key content both times; if not, don't restore
0136             // some settings (like ScreenCast multipleSources or cursorMode) don't involve user prompts so use whatever was explicitly
0137             // requested this time
0138             if (session->deviceTypes() != restoreData.payload["devices"].value<RemoteDesktopPortal::DeviceTypes>()) {
0139                 qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "Not restoring session as requested devices don't match";
0140             } else if (session->screenSharingEnabled() != restoreData.payload["screenShareEnabled"].toBool()) {
0141                 qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "Not restoring session as requested screen sharing doesn't match";
0142             } else {
0143                 restored = true;
0144             }
0145         }
0146     }
0147 
0148     if (restored) {
0149         auto notification = new KNotification(QStringLiteral("remotedesktopstarted"), KNotification::CloseOnTimeout);
0150         notification->setTitle(i18nc("title of notification about input systems taken over", "Remote control session started"));
0151         notification->setText(RemoteDesktopDialog::buildDescription(app_id, session->deviceTypes(), session->screenSharingEnabled()));
0152         notification->setIconName(QStringLiteral("krfb"));
0153         notification->sendEvent();
0154     } else {
0155         QScopedPointer<RemoteDesktopDialog, QScopedPointerDeleteLater> remoteDesktopDialog(
0156             new RemoteDesktopDialog(app_id, session->deviceTypes(), session->screenSharingEnabled()));
0157         Utils::setParentWindow(remoteDesktopDialog->windowHandle(), parent_window);
0158         Request::makeClosableDialogRequest(handle, remoteDesktopDialog.get());
0159         connect(session, &Session::closed, remoteDesktopDialog.data(), &RemoteDesktopDialog::reject);
0160 
0161         if (!remoteDesktopDialog->exec()) {
0162             return 1;
0163         }
0164     }
0165 
0166     if (session->screenSharingEnabled()) {
0167         WaylandIntegration::Streams streams;
0168         const auto screens = qGuiApp->screens();
0169         if (session->multipleSources() || screens.count() == 1) {
0170             for (const auto &screen : screens) {
0171                 auto stream = WaylandIntegration::startStreamingOutput(screen, Screencasting::Metadata);
0172                 if (!stream.isValid()) {
0173                     return 2;
0174                 }
0175                 streams << stream;
0176             }
0177         } else {
0178             streams << WaylandIntegration::startStreamingWorkspace(Screencasting::Metadata);
0179         }
0180 
0181         session->setStreams(streams);
0182         results.insert(QStringLiteral("streams"), QVariant::fromValue<WaylandIntegration::Streams>(streams));
0183     } else {
0184         qCWarning(XdgDesktopPortalKdeRemoteDesktop()) << "Only stream input";
0185         session->refreshDescription();
0186     }
0187     session->acquireStreamingInput();
0188 
0189     results.insert(QStringLiteral("devices"), QVariant::fromValue<uint>(session->deviceTypes()));
0190     results.insert(QStringLiteral("clipboard_enabled"), false);
0191     if (session->persistMode() != ScreenCastPortal::NoPersist) {
0192         results.insert("persist_mode", quint32(persist));
0193         if (persist != ScreenCastPortal::NoPersist) {
0194             const RestoreData restoreData = {
0195                 "KDE",
0196                 RestoreData::currentRestoreDataVersion(),
0197                 QVariantMap{{"screenShareEnabled", session->screenSharingEnabled()}, {"devices", QVariant::fromValue(session->deviceTypes())}}};
0198             results.insert("restore_data", QVariant::fromValue<RestoreData>(restoreData));
0199         }
0200     }
0201 
0202     return 0;
0203 }
0204 
0205 void RemoteDesktopPortal::NotifyPointerMotion(const QDBusObjectPath &session_handle, const QVariantMap &options, double dx, double dy)
0206 {
0207     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerMotion called with parameters:";
0208     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0209     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0210     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    dx: " << dx;
0211     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    dy: " << dy;
0212 
0213     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0214 
0215     if (!session) {
0216         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerMotion on non-existing session " << session_handle.path();
0217         return;
0218     }
0219 
0220     WaylandIntegration::requestPointerMotion(QSizeF(dx, dy));
0221 }
0222 
0223 void RemoteDesktopPortal::NotifyPointerMotionAbsolute(const QDBusObjectPath &session_handle, const QVariantMap &options, uint stream, double x, double y)
0224 {
0225     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerMotionAbsolute called with parameters:";
0226     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0227     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0228     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    stream: " << stream;
0229     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    x: " << x;
0230     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    y: " << y;
0231 
0232     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0233 
0234     if (!session) {
0235         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerMotionAbsolute on non-existing session " << session_handle.path();
0236         return;
0237     }
0238 
0239     WaylandIntegration::requestPointerMotionAbsolute(stream, QPointF(x, y));
0240 }
0241 
0242 void RemoteDesktopPortal::NotifyPointerButton(const QDBusObjectPath &session_handle, const QVariantMap &options, int button, uint state)
0243 {
0244     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerButton called with parameters:";
0245     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0246     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0247     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    button: " << button;
0248     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    state: " << state;
0249 
0250     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0251 
0252     if (!session) {
0253         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerButton on non-existing session " << session_handle.path();
0254         return;
0255     }
0256 
0257     if (state) {
0258         WaylandIntegration::requestPointerButtonPress(button);
0259     } else {
0260         WaylandIntegration::requestPointerButtonRelease(button);
0261     }
0262 }
0263 
0264 void RemoteDesktopPortal::NotifyPointerAxis(const QDBusObjectPath &session_handle, const QVariantMap &options, double dx, double dy)
0265 {
0266     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerAxis called with parameters:";
0267     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0268     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0269     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    dx: " << dx;
0270     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    dy: " << dy;
0271 
0272     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0273 
0274     if (!session) {
0275         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyKeyboardKeysym on non-existing session " << session_handle.path();
0276         return;
0277     }
0278 
0279     WaylandIntegration::requestPointerAxis(dx, dy);
0280 }
0281 
0282 void RemoteDesktopPortal::NotifyPointerAxisDiscrete(const QDBusObjectPath &session_handle, const QVariantMap &options, uint axis, int steps)
0283 {
0284     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerAxisDiscrete called with parameters:";
0285     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0286     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0287     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    axis: " << axis;
0288     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    steps: " << steps;
0289 
0290     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0291 
0292     if (!session) {
0293         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerAxisDiscrete on non-existing session " << session_handle.path();
0294         return;
0295     }
0296 
0297     WaylandIntegration::requestPointerAxisDiscrete(!axis ? Qt::Vertical : Qt::Horizontal, steps);
0298 }
0299 
0300 void RemoteDesktopPortal::NotifyKeyboardKeysym(const QDBusObjectPath &session_handle, const QVariantMap &options, int keysym, uint state)
0301 {
0302     Q_UNUSED(options)
0303 
0304     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0305 
0306     if (!session) {
0307         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyKeyboardKeysym on non-existing session " << session_handle.path();
0308         return;
0309     }
0310 
0311     WaylandIntegration::requestKeyboardKeysym(keysym, state != 0);
0312 }
0313 
0314 void RemoteDesktopPortal::NotifyKeyboardKeycode(const QDBusObjectPath &session_handle, const QVariantMap &options, int keycode, uint state)
0315 {
0316     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyKeyboardKeycode called with parameters:";
0317     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0318     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0319     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    keycode: " << keycode;
0320     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    state: " << state;
0321 
0322     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0323 
0324     if (!session) {
0325         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyKeyboardKeycode on non-existing session " << session_handle.path();
0326         return;
0327     }
0328 
0329     WaylandIntegration::requestKeyboardKeycode(keycode, state != 0);
0330 }
0331 
0332 void RemoteDesktopPortal::NotifyTouchDown(const QDBusObjectPath &session_handle, const QVariantMap &options, uint stream, uint slot, int x, int y)
0333 {
0334     Q_UNUSED(options)
0335     Q_UNUSED(stream)
0336 
0337     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0338     if (!session) {
0339         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerAxisDiscrete on non-existing session " << session_handle.path();
0340         return;
0341     }
0342     WaylandIntegration::requestTouchDown(slot, QPoint(x, y));
0343 }
0344 
0345 void RemoteDesktopPortal::NotifyTouchMotion(const QDBusObjectPath &session_handle, const QVariantMap &options, uint stream, uint slot, int x, int y)
0346 {
0347     Q_UNUSED(options)
0348     Q_UNUSED(stream)
0349 
0350     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0351     if (!session) {
0352         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerAxisDiscrete on non-existing session " << session_handle.path();
0353         return;
0354     }
0355     WaylandIntegration::requestTouchMotion(slot, QPoint(x, y));
0356 }
0357 
0358 void RemoteDesktopPortal::NotifyTouchUp(const QDBusObjectPath &session_handle, const QVariantMap &options, uint slot)
0359 {
0360     Q_UNUSED(options)
0361 
0362     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0363     if (!session) {
0364         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerAxisDiscrete on non-existing session " << session_handle.path();
0365         return;
0366     }
0367 
0368     WaylandIntegration::requestTouchUp(slot);
0369 }