2018-04-02 19:20:24 +01:00
|
|
|
/**
|
2020-08-17 10:48:10 +01:00
|
|
|
* SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
|
2020-01-23 15:57:08 +00:00
|
|
|
* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
2018-04-02 19:20:24 +01:00
|
|
|
*
|
2020-08-17 10:48:10 +01:00
|
|
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
2018-04-02 19:20:24 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "waylandremoteinput.h"
|
|
|
|
|
|
|
|
#include <QDebug>
|
2022-09-10 22:23:52 +01:00
|
|
|
#include <QSizeF>
|
2018-04-02 19:20:24 +01:00
|
|
|
|
|
|
|
#include <KLocalizedString>
|
2020-01-23 15:57:08 +00:00
|
|
|
#include <QDBusPendingCallWatcher>
|
2022-04-12 15:31:08 +01:00
|
|
|
|
|
|
|
#include <linux/input.h>
|
2020-01-23 15:57:08 +00:00
|
|
|
#include <xkbcommon/xkbcommon.h>
|
2022-04-12 15:31:08 +01:00
|
|
|
|
2020-01-23 15:57:08 +00:00
|
|
|
namespace
|
2022-04-12 15:31:08 +01:00
|
|
|
{
|
2020-01-23 15:57:08 +00:00
|
|
|
// Translation table to keep in sync within all the implementations
|
|
|
|
int SpecialKeysMap[] = {
|
|
|
|
0, // Invalid
|
|
|
|
KEY_BACKSPACE, // 1
|
|
|
|
KEY_TAB, // 2
|
|
|
|
KEY_LINEFEED, // 3
|
|
|
|
KEY_LEFT, // 4
|
|
|
|
KEY_UP, // 5
|
|
|
|
KEY_RIGHT, // 6
|
|
|
|
KEY_DOWN, // 7
|
|
|
|
KEY_PAGEUP, // 8
|
|
|
|
KEY_PAGEDOWN, // 9
|
|
|
|
KEY_HOME, // 10
|
|
|
|
KEY_END, // 11
|
|
|
|
KEY_ENTER, // 12
|
|
|
|
KEY_DELETE, // 13
|
|
|
|
KEY_ESC, // 14
|
|
|
|
KEY_SYSRQ, // 15
|
|
|
|
KEY_SCROLLLOCK, // 16
|
|
|
|
0, // 17
|
|
|
|
0, // 18
|
|
|
|
0, // 19
|
|
|
|
0, // 20
|
|
|
|
KEY_F1, // 21
|
|
|
|
KEY_F2, // 22
|
|
|
|
KEY_F3, // 23
|
|
|
|
KEY_F4, // 24
|
|
|
|
KEY_F5, // 25
|
|
|
|
KEY_F6, // 26
|
|
|
|
KEY_F7, // 27
|
|
|
|
KEY_F8, // 28
|
|
|
|
KEY_F9, // 29
|
|
|
|
KEY_F10, // 30
|
|
|
|
KEY_F11, // 31
|
|
|
|
KEY_F12, // 32
|
2022-04-12 15:31:08 +01:00
|
|
|
};
|
2020-01-23 15:57:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Q_GLOBAL_STATIC(RemoteDesktopSession, s_session);
|
|
|
|
|
|
|
|
RemoteDesktopSession::RemoteDesktopSession()
|
|
|
|
: iface(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"),
|
|
|
|
QLatin1String("/org/freedesktop/portal/desktop"),
|
|
|
|
QDBusConnection::sessionBus(),
|
|
|
|
this))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void RemoteDesktopSession::createSession()
|
|
|
|
{
|
|
|
|
if (isValid()) {
|
|
|
|
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "pass, already created";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_connecting = true;
|
|
|
|
|
|
|
|
// create session
|
|
|
|
const auto handleToken = QStringLiteral("kdeconnect%1").arg(QRandomGenerator::global()->generate());
|
|
|
|
const auto sessionParameters = QVariantMap{{QLatin1String("session_handle_token"), handleToken}, {QLatin1String("handle_token"), handleToken}};
|
|
|
|
auto sessionReply = iface->CreateSession(sessionParameters);
|
|
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(sessionReply);
|
|
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, sessionReply](QDBusPendingCallWatcher *self) {
|
|
|
|
self->deleteLater();
|
|
|
|
if (sessionReply.isError()) {
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Could not create the remote control session" << sessionReply.error();
|
|
|
|
m_connecting = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool b = QDBusConnection::sessionBus().connect(QString(),
|
|
|
|
sessionReply.value().path(),
|
|
|
|
QLatin1String("org.freedesktop.portal.Request"),
|
|
|
|
QLatin1String("Response"),
|
|
|
|
this,
|
|
|
|
SLOT(handleXdpSessionCreated(uint, QVariantMap)));
|
|
|
|
Q_ASSERT(b);
|
|
|
|
|
|
|
|
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "authenticating" << sessionReply.value().path();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void RemoteDesktopSession::handleXdpSessionCreated(uint code, const QVariantMap &results)
|
|
|
|
{
|
|
|
|
if (code != 0) {
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Failed to create session with code" << code << results;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_connecting = false;
|
|
|
|
m_xdpPath = QDBusObjectPath(results.value(QLatin1String("session_handle")).toString());
|
|
|
|
const QVariantMap startParameters = {
|
|
|
|
{QLatin1String("handle_token"), QStringLiteral("kdeconnect%1").arg(QRandomGenerator::global()->generate())},
|
|
|
|
{QStringLiteral("types"), QVariant::fromValue<uint>(7)}, // request all (KeyBoard, Pointer, TouchScreen)
|
|
|
|
};
|
|
|
|
|
|
|
|
QDBusConnection::sessionBus().connect(QString(),
|
|
|
|
m_xdpPath.path(),
|
|
|
|
QLatin1String("org.freedesktop.portal.Session"),
|
|
|
|
QLatin1String("Closed"),
|
|
|
|
this,
|
|
|
|
SLOT(handleXdpSessionFinished(uint, QVariantMap)));
|
|
|
|
|
|
|
|
auto reply = iface->SelectDevices(m_xdpPath, startParameters);
|
|
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply);
|
|
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, reply](QDBusPendingCallWatcher *self) {
|
|
|
|
self->deleteLater();
|
|
|
|
if (reply.isError()) {
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Could not start the remote control session" << reply.error();
|
|
|
|
m_connecting = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool b = QDBusConnection::sessionBus().connect(QString(),
|
|
|
|
reply.value().path(),
|
|
|
|
QLatin1String("org.freedesktop.portal.Request"),
|
|
|
|
QLatin1String("Response"),
|
|
|
|
this,
|
|
|
|
SLOT(handleXdpSessionConfigured(uint, QVariantMap)));
|
|
|
|
Q_ASSERT(b);
|
|
|
|
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "configuring" << reply.value().path();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void RemoteDesktopSession::handleXdpSessionConfigured(uint code, const QVariantMap &results)
|
|
|
|
{
|
|
|
|
if (code != 0) {
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Failed to configure session with code" << code << results;
|
|
|
|
m_connecting = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const QVariantMap startParameters = {
|
|
|
|
{QLatin1String("handle_token"), QStringLiteral("kdeconnect%1").arg(QRandomGenerator::global()->generate())},
|
|
|
|
};
|
|
|
|
auto reply = iface->Start(m_xdpPath, {}, startParameters);
|
|
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply);
|
|
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, reply](QDBusPendingCallWatcher *self) {
|
|
|
|
self->deleteLater();
|
|
|
|
if (reply.isError()) {
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Could not start the remote control session" << reply.error();
|
|
|
|
m_connecting = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void RemoteDesktopSession::handleXdpSessionFinished(uint /*code*/, const QVariantMap & /*results*/)
|
|
|
|
{
|
|
|
|
m_xdpPath = {};
|
|
|
|
}
|
2022-04-12 15:31:08 +01:00
|
|
|
|
2022-09-10 22:23:52 +01:00
|
|
|
WaylandRemoteInput::WaylandRemoteInput(QObject *parent)
|
2018-04-02 19:20:24 +01:00
|
|
|
: AbstractRemoteInput(parent)
|
|
|
|
{
|
2022-04-12 15:31:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
WaylandRemoteInput::~WaylandRemoteInput()
|
|
|
|
{
|
2018-04-02 19:20:24 +01:00
|
|
|
}
|
|
|
|
|
2022-09-10 22:23:52 +01:00
|
|
|
bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
|
2018-04-02 19:20:24 +01:00
|
|
|
{
|
2020-01-23 15:57:08 +00:00
|
|
|
if (!s_session->isValid()) {
|
|
|
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Unable to handle remote input. RemoteDesktop portal not authenticated";
|
|
|
|
s_session->createSession();
|
|
|
|
return false;
|
2018-04-02 19:20:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const float dx = np.get<float>(QStringLiteral("dx"), 0);
|
|
|
|
const float dy = np.get<float>(QStringLiteral("dy"), 0);
|
|
|
|
|
|
|
|
const bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
|
|
|
|
const bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
|
|
|
const bool isMiddleClick = np.get<bool>(QStringLiteral("middleclick"), false);
|
|
|
|
const bool isRightClick = np.get<bool>(QStringLiteral("rightclick"), false);
|
|
|
|
const bool isSingleHold = np.get<bool>(QStringLiteral("singlehold"), false);
|
|
|
|
const bool isSingleRelease = np.get<bool>(QStringLiteral("singlerelease"), false);
|
|
|
|
const bool isScroll = np.get<bool>(QStringLiteral("scroll"), false);
|
|
|
|
const QString key = np.get<QString>(QStringLiteral("key"), QLatin1String(""));
|
|
|
|
const int specialKey = np.get<int>(QStringLiteral("specialKey"), 0);
|
|
|
|
|
2021-07-01 06:38:43 +01:00
|
|
|
if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isSingleRelease || isScroll || !key.isEmpty() || specialKey) {
|
2018-04-02 19:20:24 +01:00
|
|
|
if (isSingleClick) {
|
2020-01-23 15:57:08 +00:00
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
2018-04-02 19:20:24 +01:00
|
|
|
} else if (isDoubleClick) {
|
2020-01-23 15:57:08 +00:00
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
2018-04-02 19:20:24 +01:00
|
|
|
} else if (isMiddleClick) {
|
2020-01-23 15:57:08 +00:00
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 1);
|
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 0);
|
2018-04-02 19:20:24 +01:00
|
|
|
} else if (isRightClick) {
|
2020-01-23 15:57:08 +00:00
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 1);
|
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 0);
|
2022-09-10 22:23:52 +01:00
|
|
|
} else if (isSingleHold) {
|
|
|
|
// For drag'n drop
|
2020-01-23 15:57:08 +00:00
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
2022-09-10 22:23:52 +01:00
|
|
|
} else if (isSingleRelease) {
|
|
|
|
// For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofness.
|
2020-01-23 15:57:08 +00:00
|
|
|
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
2018-04-02 19:20:24 +01:00
|
|
|
} else if (isScroll) {
|
2020-01-23 15:57:08 +00:00
|
|
|
s_session->iface->NotifyPointerAxis(s_session->m_xdpPath, {}, dx, dy);
|
2023-05-16 21:18:38 +01:00
|
|
|
} else if (specialKey || !key.isEmpty()) {
|
|
|
|
bool ctrl = np.get<bool>(QStringLiteral("ctrl"), false);
|
|
|
|
bool alt = np.get<bool>(QStringLiteral("alt"), false);
|
|
|
|
bool shift = np.get<bool>(QStringLiteral("shift"), false);
|
|
|
|
bool super = np.get<bool>(QStringLiteral("super"), false);
|
|
|
|
|
|
|
|
if (ctrl)
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 1);
|
|
|
|
if (alt)
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 1);
|
|
|
|
if (shift)
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 1);
|
|
|
|
if (super)
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTMETA, 1);
|
|
|
|
|
|
|
|
if (specialKey) {
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 1);
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 0);
|
|
|
|
} else if (!key.isEmpty()) {
|
|
|
|
for (const QChar character : key) {
|
|
|
|
const auto keysym = xkb_utf32_to_keysym(character.toLower().unicode());
|
|
|
|
if (keysym != XKB_KEY_NoSymbol) {
|
|
|
|
s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 1).waitForFinished();
|
|
|
|
s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 0).waitForFinished();
|
|
|
|
} else {
|
|
|
|
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Cannot send character" << character;
|
|
|
|
}
|
2020-01-23 15:57:08 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-16 21:18:38 +01:00
|
|
|
|
|
|
|
if (ctrl)
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 0);
|
|
|
|
if (alt)
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 0);
|
|
|
|
if (shift)
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 0);
|
|
|
|
if (super)
|
|
|
|
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTMETA, 0);
|
2018-04-02 19:20:24 +01:00
|
|
|
}
|
2022-09-10 22:23:52 +01:00
|
|
|
} else { // Is a mouse move event
|
2020-01-23 15:57:08 +00:00
|
|
|
s_session->iface->NotifyPointerMotion(s_session->m_xdpPath, {}, dx, dy);
|
2018-04-02 19:20:24 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|