File indexing completed on 2024-04-21 04:56:50

0001 /**
0002  * SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
0003  * SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "waylandremoteinput.h"
0009 
0010 #include <QDebug>
0011 #include <QSizeF>
0012 
0013 #include <KConfigGroup>
0014 #include <KLocalizedString>
0015 #include <KSharedConfig>
0016 #include <QDBusPendingCallWatcher>
0017 
0018 #include <linux/input.h>
0019 #include <xkbcommon/xkbcommon.h>
0020 
0021 namespace
0022 {
0023 // Translation table to keep in sync within all the implementations
0024 int SpecialKeysMap[] = {
0025     0, // Invalid
0026     KEY_BACKSPACE, // 1
0027     KEY_TAB, // 2
0028     KEY_LINEFEED, // 3
0029     KEY_LEFT, // 4
0030     KEY_UP, // 5
0031     KEY_RIGHT, // 6
0032     KEY_DOWN, // 7
0033     KEY_PAGEUP, // 8
0034     KEY_PAGEDOWN, // 9
0035     KEY_HOME, // 10
0036     KEY_END, // 11
0037     KEY_ENTER, // 12
0038     KEY_DELETE, // 13
0039     KEY_ESC, // 14
0040     KEY_SYSRQ, // 15
0041     KEY_SCROLLLOCK, // 16
0042     0, // 17
0043     0, // 18
0044     0, // 19
0045     0, // 20
0046     KEY_F1, // 21
0047     KEY_F2, // 22
0048     KEY_F3, // 23
0049     KEY_F4, // 24
0050     KEY_F5, // 25
0051     KEY_F6, // 26
0052     KEY_F7, // 27
0053     KEY_F8, // 28
0054     KEY_F9, // 29
0055     KEY_F10, // 30
0056     KEY_F11, // 31
0057     KEY_F12, // 32
0058 };
0059 }
0060 
0061 Q_GLOBAL_STATIC(RemoteDesktopSession, s_session);
0062 
0063 RemoteDesktopSession::RemoteDesktopSession()
0064     : iface(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"),
0065                                                            QLatin1String("/org/freedesktop/portal/desktop"),
0066                                                            QDBusConnection::sessionBus(),
0067                                                            this))
0068 {
0069 }
0070 
0071 void RemoteDesktopSession::createSession()
0072 {
0073     if (isValid()) {
0074         qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "pass, already created";
0075         return;
0076     }
0077 
0078     m_connecting = true;
0079 
0080     // create session
0081     const auto handleToken = QStringLiteral("kdeconnect%1").arg(QRandomGenerator::global()->generate());
0082     const auto sessionParameters = QVariantMap{{QLatin1String("session_handle_token"), handleToken}, {QLatin1String("handle_token"), handleToken}};
0083     auto sessionReply = iface->CreateSession(sessionParameters);
0084     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(sessionReply);
0085     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, sessionReply](QDBusPendingCallWatcher *self) {
0086         self->deleteLater();
0087         if (sessionReply.isError()) {
0088             qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Could not create the remote control session" << sessionReply.error();
0089             m_connecting = false;
0090             return;
0091         }
0092 
0093         bool b = QDBusConnection::sessionBus().connect(QString(),
0094                                                        sessionReply.value().path(),
0095                                                        QLatin1String("org.freedesktop.portal.Request"),
0096                                                        QLatin1String("Response"),
0097                                                        this,
0098                                                        SLOT(handleXdpSessionCreated(uint, QVariantMap)));
0099         Q_ASSERT(b);
0100 
0101         qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "authenticating" << sessionReply.value().path();
0102     });
0103 }
0104 
0105 void RemoteDesktopSession::handleXdpSessionCreated(uint code, const QVariantMap &results)
0106 {
0107     if (code != 0) {
0108         qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Failed to create session with code" << code << results;
0109         return;
0110     }
0111 
0112     m_connecting = false;
0113     m_xdpPath = QDBusObjectPath(results.value(QLatin1String("session_handle")).toString());
0114     QVariantMap startParameters = {
0115         {QLatin1String("handle_token"), QStringLiteral("kdeconnect%1").arg(QRandomGenerator::global()->generate())},
0116         {QStringLiteral("types"), QVariant::fromValue<uint>(7)}, // request all (KeyBoard, Pointer, TouchScreen)
0117         {QLatin1String("persist_mode"), QVariant::fromValue<uint>(2)}, // Persist permission until explicitly revoked by user
0118     };
0119 
0120     KConfigGroup stateConfig = KSharedConfig::openStateConfig()->group(QStringLiteral("mousepad"));
0121     QString restoreToken = stateConfig.readEntry(QStringLiteral("RestoreToken"), QString());
0122     if (restoreToken.length() > 0) {
0123         startParameters[QLatin1String("restore_token")] = restoreToken;
0124     }
0125 
0126     QDBusConnection::sessionBus().connect(QString(),
0127                                           m_xdpPath.path(),
0128                                           QLatin1String("org.freedesktop.portal.Session"),
0129                                           QLatin1String("Closed"),
0130                                           this,
0131                                           SLOT(handleXdpSessionFinished(uint, QVariantMap)));
0132 
0133     auto reply = iface->SelectDevices(m_xdpPath, startParameters);
0134     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply);
0135     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, reply](QDBusPendingCallWatcher *self) {
0136         self->deleteLater();
0137         if (reply.isError()) {
0138             qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Could not start the remote control session" << reply.error();
0139             m_connecting = false;
0140             return;
0141         }
0142 
0143         bool b = QDBusConnection::sessionBus().connect(QString(),
0144                                                        reply.value().path(),
0145                                                        QLatin1String("org.freedesktop.portal.Request"),
0146                                                        QLatin1String("Response"),
0147                                                        this,
0148                                                        SLOT(handleXdpSessionConfigured(uint, QVariantMap)));
0149         Q_ASSERT(b);
0150         qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "configuring" << reply.value().path();
0151     });
0152 }
0153 
0154 void RemoteDesktopSession::handleXdpSessionConfigured(uint code, const QVariantMap &results)
0155 {
0156     if (code != 0) {
0157         qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Failed to configure session with code" << code << results;
0158         m_connecting = false;
0159         return;
0160     }
0161     const QVariantMap startParameters = {
0162         {QLatin1String("handle_token"), QStringLiteral("kdeconnect%1").arg(QRandomGenerator::global()->generate())},
0163     };
0164     auto reply = iface->Start(m_xdpPath, {}, startParameters);
0165     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply);
0166     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, reply](QDBusPendingCallWatcher *self) {
0167         self->deleteLater();
0168         if (reply.isError()) {
0169             qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Could not start the remote control session" << reply.error();
0170             m_connecting = false;
0171             return;
0172         }
0173 
0174         bool b = QDBusConnection::sessionBus().connect(QString(),
0175                                                        reply.value().path(),
0176                                                        QLatin1String("org.freedesktop.portal.Request"),
0177                                                        QLatin1String("Response"),
0178                                                        this,
0179                                                        SLOT(handleXdpSessionStarted(uint, QVariantMap)));
0180         Q_ASSERT(b);
0181         qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "starting" << reply.value().path();
0182     });
0183 }
0184 
0185 void RemoteDesktopSession::handleXdpSessionStarted(uint code, const QVariantMap &results)
0186 {
0187     Q_UNUSED(code);
0188 
0189     KConfigGroup stateConfig = KSharedConfig::openStateConfig()->group(QStringLiteral("mousepad"));
0190     stateConfig.writeEntry(QStringLiteral("RestoreToken"), results[QStringLiteral("restore_token")].toString());
0191 }
0192 
0193 void RemoteDesktopSession::handleXdpSessionFinished(uint /*code*/, const QVariantMap & /*results*/)
0194 {
0195     m_xdpPath = {};
0196 }
0197 
0198 WaylandRemoteInput::WaylandRemoteInput(QObject *parent)
0199     : AbstractRemoteInput(parent)
0200 {
0201 }
0202 
0203 bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
0204 {
0205     if (!s_session->isValid()) {
0206         qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Unable to handle remote input. RemoteDesktop portal not authenticated";
0207         s_session->createSession();
0208         return false;
0209     }
0210 
0211     const float dx = np.get<float>(QStringLiteral("dx"), 0);
0212     const float dy = np.get<float>(QStringLiteral("dy"), 0);
0213 
0214     const bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
0215     const bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
0216     const bool isMiddleClick = np.get<bool>(QStringLiteral("middleclick"), false);
0217     const bool isRightClick = np.get<bool>(QStringLiteral("rightclick"), false);
0218     const bool isSingleHold = np.get<bool>(QStringLiteral("singlehold"), false);
0219     const bool isSingleRelease = np.get<bool>(QStringLiteral("singlerelease"), false);
0220     const bool isScroll = np.get<bool>(QStringLiteral("scroll"), false);
0221     const QString key = np.get<QString>(QStringLiteral("key"), QLatin1String(""));
0222     const int specialKey = np.get<int>(QStringLiteral("specialKey"), 0);
0223 
0224     if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isSingleRelease || isScroll || !key.isEmpty() || specialKey) {
0225         if (isSingleClick) {
0226             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
0227             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
0228         } else if (isDoubleClick) {
0229             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
0230             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
0231             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
0232             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
0233         } else if (isMiddleClick) {
0234             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 1);
0235             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 0);
0236         } else if (isRightClick) {
0237             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 1);
0238             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 0);
0239         } else if (isSingleHold) {
0240             // For drag'n drop
0241             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
0242         } else if (isSingleRelease) {
0243             // For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofness.
0244             s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
0245         } else if (isScroll) {
0246             s_session->iface->NotifyPointerAxis(s_session->m_xdpPath, {}, dx, dy);
0247         } else if (specialKey || !key.isEmpty()) {
0248             bool ctrl = np.get<bool>(QStringLiteral("ctrl"), false);
0249             bool alt = np.get<bool>(QStringLiteral("alt"), false);
0250             bool shift = np.get<bool>(QStringLiteral("shift"), false);
0251             bool super = np.get<bool>(QStringLiteral("super"), false);
0252 
0253             if (ctrl)
0254                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 1);
0255             if (alt)
0256                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 1);
0257             if (shift)
0258                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 1);
0259             if (super)
0260                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTMETA, 1);
0261 
0262             if (specialKey) {
0263                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 1);
0264                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 0);
0265             } else if (!key.isEmpty()) {
0266                 for (const QChar character : key) {
0267                     const auto keysym = xkb_utf32_to_keysym(character.toLower().unicode());
0268                     if (keysym != XKB_KEY_NoSymbol) {
0269                         s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 1).waitForFinished();
0270                         s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 0).waitForFinished();
0271                     } else {
0272                         qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Cannot send character" << character;
0273                     }
0274                 }
0275             }
0276 
0277             if (ctrl)
0278                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 0);
0279             if (alt)
0280                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 0);
0281             if (shift)
0282                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 0);
0283             if (super)
0284                 s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTMETA, 0);
0285         }
0286     } else { // Is a mouse move event
0287         s_session->iface->NotifyPointerMotion(s_session->m_xdpPath, {}, dx, dy);
0288     }
0289     return true;
0290 }
0291 
0292 #include "moc_waylandremoteinput.cpp"