File indexing completed on 2024-12-01 13:42: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 "session.h"
0015 #include "utils.h"
0016 #include "waylandintegration.h"
0017 #include <KLocalizedString>
0018 #include <KNotification>
0019 #include <QGuiApplication>
0020 #include <QRegion>
0021 #include <QScreen>
0022 
0023 RemoteDesktopPortal::RemoteDesktopPortal(QObject *parent)
0024     : QDBusAbstractAdaptor(parent)
0025 {
0026 }
0027 
0028 RemoteDesktopPortal::~RemoteDesktopPortal()
0029 {
0030 }
0031 
0032 uint RemoteDesktopPortal::CreateSession(const QDBusObjectPath &handle,
0033                                         const QDBusObjectPath &session_handle,
0034                                         const QString &app_id,
0035                                         const QVariantMap &options,
0036                                         QVariantMap &results)
0037 {
0038     Q_UNUSED(results);
0039     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "CreateSession called with parameters:";
0040     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    handle: " << handle.path();
0041     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0042     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    app_id: " << app_id;
0043     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0044 
0045     Session *session = Session::createSession(this, Session::RemoteDesktop, app_id, session_handle.path());
0046 
0047     if (!session) {
0048         return 2;
0049     }
0050 
0051     if (!WaylandIntegration::isStreamingAvailable()) {
0052         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "zkde_screencast_unstable_v1 does not seem to be available";
0053         return 2;
0054     }
0055 
0056     connect(session, &Session::closed, [session] {
0057         auto remoteDesktopSession = qobject_cast<RemoteDesktopSession *>(session);
0058         const auto streams = remoteDesktopSession->streams();
0059         for (const WaylandIntegration::Stream &stream : streams) {
0060             WaylandIntegration::stopStreaming(stream.nodeId);
0061         }
0062     });
0063 
0064     return 0;
0065 }
0066 
0067 uint RemoteDesktopPortal::SelectDevices(const QDBusObjectPath &handle,
0068                                         const QDBusObjectPath &session_handle,
0069                                         const QString &app_id,
0070                                         const QVariantMap &options,
0071                                         QVariantMap &results)
0072 {
0073     Q_UNUSED(results);
0074     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "SelectDevices called with parameters:";
0075     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    handle: " << handle.path();
0076     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0077     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    app_id: " << app_id;
0078     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0079 
0080     const auto types = static_cast<RemoteDesktopPortal::DeviceTypes>(options.value(QStringLiteral("types")).toUInt());
0081     if (types == None) {
0082         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "There are no devices to remotely control";
0083         return 2;
0084     }
0085 
0086     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0087 
0088     if (!session) {
0089         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to select sources on non-existing session " << session_handle.path();
0090         return 2;
0091     }
0092 
0093     session->setDeviceTypes(types);
0094 
0095     return 0;
0096 }
0097 
0098 uint RemoteDesktopPortal::Start(const QDBusObjectPath &handle,
0099                                 const QDBusObjectPath &session_handle,
0100                                 const QString &app_id,
0101                                 const QString &parent_window,
0102                                 const QVariantMap &options,
0103                                 QVariantMap &results)
0104 {
0105     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "Start called with parameters:";
0106     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    handle: " << handle.path();
0107     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0108     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    app_id: " << app_id;
0109     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    parent_window: " << parent_window;
0110     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0111 
0112     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0113 
0114     if (!session) {
0115         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call start on non-existing session " << session_handle.path();
0116         return 2;
0117     }
0118 
0119     if (WaylandIntegration::screens().isEmpty()) {
0120         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Failed to show dialog as there is no screen to select";
0121         return 2;
0122     }
0123 
0124     if (app_id.isEmpty()) {
0125         // non-sandboxed local applications are generally able to take over the system already without much hassle.
0126         // Instead of making users interact with the dialog (which they probably can't because this is for remote access), just
0127         // show a notification so they are aware of it.
0128         auto notification = new KNotification(QStringLiteral("remotedesktopstarted"), KNotification::CloseOnTimeout);
0129         notification->setTitle(i18nc("title of notification about input systems taken over", "Remote control session started"));
0130         notification->setText(RemoteDesktopDialog::buildDescription(app_id, session->deviceTypes(), session->screenSharingEnabled()));
0131         notification->setIconName(QStringLiteral("krfb"));
0132         notification->sendEvent();
0133     } else {
0134         QScopedPointer<RemoteDesktopDialog, QScopedPointerDeleteLater> remoteDesktopDialog(
0135             new RemoteDesktopDialog(app_id, session->deviceTypes(), session->screenSharingEnabled()));
0136         Utils::setParentWindow(remoteDesktopDialog->windowHandle(), parent_window);
0137         Request::makeClosableDialogRequest(handle, remoteDesktopDialog.get());
0138         connect(session, &Session::closed, remoteDesktopDialog.data(), &RemoteDesktopDialog::reject);
0139 
0140         if (!remoteDesktopDialog->exec()) {
0141             return 1;
0142         }
0143     }
0144 
0145     if (session->screenSharingEnabled()) {
0146         WaylandIntegration::Streams streams;
0147         const auto screens = qGuiApp->screens();
0148         if (session->multipleSources() || screens.count() == 1) {
0149             for (const auto &screen : screens) {
0150                 auto stream = WaylandIntegration::startStreamingOutput(screen, Screencasting::Metadata);
0151                 if (!stream.isValid()) {
0152                     return 2;
0153                 }
0154                 streams << stream;
0155             }
0156         } else {
0157             streams << WaylandIntegration::startStreamingWorkspace(Screencasting::Metadata);
0158         }
0159 
0160         session->setStreams(streams);
0161         results.insert(QStringLiteral("streams"), QVariant::fromValue<WaylandIntegration::Streams>(streams));
0162     } else {
0163         qCWarning(XdgDesktopPortalKdeRemoteDesktop()) << "Only stream input";
0164         session->refreshDescription();
0165     }
0166     session->acquireStreamingInput();
0167 
0168     results.insert(QStringLiteral("devices"), QVariant::fromValue<uint>(session->deviceTypes()));
0169 
0170     return 0;
0171 }
0172 
0173 void RemoteDesktopPortal::NotifyPointerMotion(const QDBusObjectPath &session_handle, const QVariantMap &options, double dx, double dy)
0174 {
0175     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerMotion called with parameters:";
0176     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0177     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0178     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    dx: " << dx;
0179     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    dy: " << dy;
0180 
0181     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0182 
0183     if (!session) {
0184         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerMotion on non-existing session " << session_handle.path();
0185         return;
0186     }
0187 
0188     WaylandIntegration::requestPointerMotion(QSizeF(dx, dy));
0189 }
0190 
0191 void RemoteDesktopPortal::NotifyPointerMotionAbsolute(const QDBusObjectPath &session_handle, const QVariantMap &options, uint stream, double x, double y)
0192 {
0193     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerMotionAbsolute called with parameters:";
0194     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0195     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0196     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    stream: " << stream;
0197     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    x: " << x;
0198     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    y: " << y;
0199 
0200     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0201 
0202     if (!session) {
0203         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerMotionAbsolute on non-existing session " << session_handle.path();
0204         return;
0205     }
0206 
0207     WaylandIntegration::requestPointerMotionAbsolute(stream, QPointF(x, y));
0208 }
0209 
0210 void RemoteDesktopPortal::NotifyPointerButton(const QDBusObjectPath &session_handle, const QVariantMap &options, int button, uint state)
0211 {
0212     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerButton called with parameters:";
0213     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0214     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0215     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    button: " << button;
0216     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    state: " << state;
0217 
0218     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0219 
0220     if (!session) {
0221         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerButton on non-existing session " << session_handle.path();
0222         return;
0223     }
0224 
0225     if (state) {
0226         WaylandIntegration::requestPointerButtonPress(button);
0227     } else {
0228         WaylandIntegration::requestPointerButtonRelease(button);
0229     }
0230 }
0231 
0232 void RemoteDesktopPortal::NotifyPointerAxis(const QDBusObjectPath &session_handle, const QVariantMap &options, double dx, double dy)
0233 {
0234     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerAxis called with parameters:";
0235     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0236     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0237     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    dx: " << dx;
0238     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    dy: " << dy;
0239 
0240     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0241 
0242     if (!session) {
0243         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyKeyboardKeysym on non-existing session " << session_handle.path();
0244         return;
0245     }
0246 
0247     WaylandIntegration::requestPointerAxis(dx, dy);
0248 }
0249 
0250 void RemoteDesktopPortal::NotifyPointerAxisDiscrete(const QDBusObjectPath &session_handle, const QVariantMap &options, uint axis, int steps)
0251 {
0252     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyPointerAxisDiscrete called with parameters:";
0253     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0254     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0255     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    axis: " << axis;
0256     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    steps: " << steps;
0257 
0258     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0259 
0260     if (!session) {
0261         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerAxisDiscrete on non-existing session " << session_handle.path();
0262         return;
0263     }
0264 
0265     WaylandIntegration::requestPointerAxisDiscrete(!axis ? Qt::Vertical : Qt::Horizontal, steps);
0266 }
0267 
0268 void RemoteDesktopPortal::NotifyKeyboardKeysym(const QDBusObjectPath &session_handle, const QVariantMap &options, int keysym, uint state)
0269 {
0270     Q_UNUSED(options)
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::requestKeyboardKeysym(keysym, state != 0);
0280 }
0281 
0282 void RemoteDesktopPortal::NotifyKeyboardKeycode(const QDBusObjectPath &session_handle, const QVariantMap &options, int keycode, uint state)
0283 {
0284     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "NotifyKeyboardKeycode called with parameters:";
0285     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    session_handle: " << session_handle.path();
0286     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    options: " << options;
0287     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    keycode: " << keycode;
0288     qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "    state: " << state;
0289 
0290     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0291 
0292     if (!session) {
0293         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyKeyboardKeycode on non-existing session " << session_handle.path();
0294         return;
0295     }
0296 
0297     WaylandIntegration::requestKeyboardKeycode(keycode, state != 0);
0298 }
0299 
0300 void RemoteDesktopPortal::NotifyTouchDown(const QDBusObjectPath &session_handle, const QVariantMap &options, uint stream, uint slot, int x, int y)
0301 {
0302     Q_UNUSED(options)
0303     Q_UNUSED(stream)
0304 
0305     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0306     if (!session) {
0307         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerAxisDiscrete on non-existing session " << session_handle.path();
0308         return;
0309     }
0310     WaylandIntegration::requestTouchDown(slot, QPoint(x, y));
0311 }
0312 
0313 void RemoteDesktopPortal::NotifyTouchMotion(const QDBusObjectPath &session_handle, const QVariantMap &options, uint stream, uint slot, int x, int y)
0314 {
0315     Q_UNUSED(options)
0316     Q_UNUSED(stream)
0317 
0318     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0319     if (!session) {
0320         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerAxisDiscrete on non-existing session " << session_handle.path();
0321         return;
0322     }
0323     WaylandIntegration::requestTouchMotion(slot, QPoint(x, y));
0324 }
0325 
0326 void RemoteDesktopPortal::NotifyTouchUp(const QDBusObjectPath &session_handle, const QVariantMap &options, uint slot)
0327 {
0328     Q_UNUSED(options)
0329 
0330     RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession *>(Session::getSession(session_handle.path()));
0331     if (!session) {
0332         qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call NotifyPointerAxisDiscrete on non-existing session " << session_handle.path();
0333         return;
0334     }
0335 
0336     WaylandIntegration::requestTouchUp(slot);
0337 }