diff --git a/CMakeLists.txt b/CMakeLists.txt index 0636c9530..faa92c667 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,7 @@ else() find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS WaylandClient) find_package(PlasmaWaylandProtocols REQUIRED) find_package(WaylandProtocols REQUIRED) + pkg_check_modules(XkbCommon IMPORTED_TARGET xkbcommon) endif() find_package(KF5PeopleVCard) diff --git a/daemon/org.kde.kdeconnect.daemon.desktop.cmake b/daemon/org.kde.kdeconnect.daemon.desktop.cmake index 64dfba3a3..42b7e4bf5 100644 --- a/daemon/org.kde.kdeconnect.daemon.desktop.cmake +++ b/daemon/org.kde.kdeconnect.daemon.desktop.cmake @@ -3,7 +3,6 @@ Type=Application Exec=${KDE_INSTALL_FULL_LIBEXECDIR}/kdeconnectd X-KDE-StartupNotify=false X-KDE-autostart-phase=1 -X-KDE-Wayland-Interfaces=org_kde_kwin_fake_input X-GNOME-Autostart-enabled=true NoDisplay=true Icon=kdeconnect diff --git a/plugins/mousepad/CMakeLists.txt b/plugins/mousepad/CMakeLists.txt index edf8c155a..526949a70 100644 --- a/plugins/mousepad/CMakeLists.txt +++ b/plugins/mousepad/CMakeLists.txt @@ -1,6 +1,13 @@ kdeconnect_add_plugin(kdeconnect_mousepad SOURCES mousepadplugin.cpp abstractremoteinput.cpp) if(UNIX AND NOT APPLE) + qt5_add_dbus_interface( + SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/xdp_dbus_remotedesktop_interface.xml + xdp_dbus_remotedesktop_interface + ) + + target_sources(kdeconnect_mousepad PUBLIC waylandremoteinput.cpp ${SRCS}) find_package(LibFakeKey QUIET) set_package_properties(LibFakeKey PROPERTIES DESCRIPTION "fake key events" @@ -20,7 +27,7 @@ if(UNIX AND NOT APPLE) endif() target_sources(kdeconnect_mousepad PRIVATE ${wayland_SRCS}) - target_link_libraries(kdeconnect_mousepad Wayland::Client Qt::WaylandClient) + target_link_libraries(kdeconnect_mousepad Wayland::Client Qt::WaylandClient PkgConfig::XkbCommon) target_sources(kdeconnect_mousepad PUBLIC waylandremoteinput.cpp) set(HAVE_WAYLAND TRUE) @@ -37,7 +44,7 @@ endif() set(HAVE_WINDOWS ${WIN32}) set(HAVE_X11 ${LibFakeKey_FOUND}) set(HAVE_MACOS ${APPLE}) -configure_file(config-mousepad.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-mousepad.h ) +configure_file(config-mousepad.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-mousepad.h) target_link_libraries(kdeconnect_mousepad kdeconnectcore Qt::Gui KF5::I18n) diff --git a/plugins/mousepad/abstractremoteinput.cpp b/plugins/mousepad/abstractremoteinput.cpp index 8ba8e9cb2..3c7ad3368 100644 --- a/plugins/mousepad/abstractremoteinput.cpp +++ b/plugins/mousepad/abstractremoteinput.cpp @@ -6,6 +6,7 @@ #include "abstractremoteinput.h" +Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MOUSEPAD, "kdeconnect.plugin.mousepad") AbstractRemoteInput::AbstractRemoteInput(QObject *parent) : QObject(parent) { diff --git a/plugins/mousepad/abstractremoteinput.h b/plugins/mousepad/abstractremoteinput.h index 3b70d0c10..2c4f07ea0 100644 --- a/plugins/mousepad/abstractremoteinput.h +++ b/plugins/mousepad/abstractremoteinput.h @@ -7,10 +7,13 @@ #ifndef ABSTRACTREMOTEINPUT_H #define ABSTRACTREMOTEINPUT_H +#include #include #include +Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MOUSEPAD) + class AbstractRemoteInput : public QObject { Q_OBJECT diff --git a/plugins/mousepad/waylandremoteinput.cpp b/plugins/mousepad/waylandremoteinput.cpp index 9ab2c24fa..96be436ab 100644 --- a/plugins/mousepad/waylandremoteinput.cpp +++ b/plugins/mousepad/waylandremoteinput.cpp @@ -1,5 +1,6 @@ /** * SPDX-FileCopyrightText: 2015 Martin Gräßlin + * SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ @@ -10,42 +11,177 @@ #include #include - -#include "qwayland-fake-input.h" -#include +#include #include +#include -class FakeInput : public QWaylandClientExtensionTemplate, public QtWayland::org_kde_kwin_fake_input +namespace { -public: - FakeInput() - : QWaylandClientExtensionTemplate(4) - { - } +// 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 }; +} + +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(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 = {}; +} WaylandRemoteInput::WaylandRemoteInput(QObject *parent) : AbstractRemoteInput(parent) - , m_waylandAuthenticationRequested(false) { - m_fakeInput = new FakeInput; } WaylandRemoteInput::~WaylandRemoteInput() { - delete m_fakeInput; } bool WaylandRemoteInput::handlePacket(const NetworkPacket &np) { - if (!m_fakeInput->isActive()) { - return true; - } - - if (!m_waylandAuthenticationRequested) { - m_fakeInput->authenticate(i18n("KDE Connect"), i18n("Use your phone as a touchpad and keyboard")); - m_waylandAuthenticationRequested = true; + if (!s_session->isValid()) { + qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Unable to handle remote input. RemoteDesktop portal not authenticated"; + s_session->createSession(); + return false; } const float dx = np.get(QStringLiteral("dx"), 0); @@ -63,36 +199,43 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np) if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isSingleRelease || isScroll || !key.isEmpty() || specialKey) { if (isSingleClick) { - m_fakeInput->button(BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); - m_fakeInput->button(BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); - + s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1); + s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0); } else if (isDoubleClick) { - m_fakeInput->button(BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); - m_fakeInput->button(BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); - - m_fakeInput->button(BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); - m_fakeInput->button(BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); + 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); } else if (isMiddleClick) { - m_fakeInput->button(BTN_MIDDLE, WL_POINTER_BUTTON_STATE_PRESSED); - m_fakeInput->button(BTN_MIDDLE, WL_POINTER_BUTTON_STATE_RELEASED); + s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 1); + s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 0); } else if (isRightClick) { - m_fakeInput->button(BTN_RIGHT, WL_POINTER_BUTTON_STATE_PRESSED); - m_fakeInput->button(BTN_RIGHT, WL_POINTER_BUTTON_STATE_RELEASED); + s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 1); + s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 0); } else if (isSingleHold) { // For drag'n drop - m_fakeInput->button(BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); + s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1); } else if (isSingleRelease) { // For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofness. - m_fakeInput->button(BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); + s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0); } else if (isScroll) { - m_fakeInput->axis(WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_double(-dy)); - - } else if (!key.isEmpty() || specialKey) { - // TODO: implement key support + s_session->iface->NotifyPointerAxis(s_session->m_xdpPath, {}, dx, dy); + } else 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.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; + } + } } - } else { // Is a mouse move event - m_fakeInput->pointer_motion(wl_fixed_from_double(dx), wl_fixed_from_double(dy)); + s_session->iface->NotifyPointerMotion(s_session->m_xdpPath, {}, dx, dy); } return true; } diff --git a/plugins/mousepad/waylandremoteinput.h b/plugins/mousepad/waylandremoteinput.h index 244ebb657..44648506a 100644 --- a/plugins/mousepad/waylandremoteinput.h +++ b/plugins/mousepad/waylandremoteinput.h @@ -1,5 +1,6 @@ /** * SPDX-FileCopyrightText: 2018 Albert Vaca Cintora + * SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ @@ -8,9 +9,33 @@ #define WAYLANDREMOTEINPUT_H #include "abstractremoteinput.h" +#include "xdp_dbus_remotedesktop_interface.h" +#include class FakeInput; +class RemoteDesktopSession : public QObject +{ + Q_OBJECT +public: + RemoteDesktopSession(); + void createSession(); + bool isValid() const + { + return m_connecting || !m_xdpPath.path().isEmpty(); + } + OrgFreedesktopPortalRemoteDesktopInterface *const iface; + QDBusObjectPath m_xdpPath; + bool m_connecting = false; + +private Q_SLOTS: + void handleXdpSessionCreated(uint code, const QVariantMap &results); + void handleXdpSessionConfigured(uint code, const QVariantMap &results); + void handleXdpSessionFinished(uint code, const QVariantMap &results); + +private: +}; + class WaylandRemoteInput : public AbstractRemoteInput { Q_OBJECT @@ -20,12 +45,10 @@ public: ~WaylandRemoteInput(); bool handlePacket(const NetworkPacket &np) override; - -private: - void setupWaylandIntegration(); - - FakeInput *m_fakeInput; - bool m_waylandAuthenticationRequested; + bool hasKeyboardSupport() override + { + return true; + } }; #endif diff --git a/plugins/mousepad/xdp_dbus_remotedesktop_interface.xml b/plugins/mousepad/xdp_dbus_remotedesktop_interface.xml new file mode 100644 index 000000000..054ea2b63 --- /dev/null +++ b/plugins/mousepad/xdp_dbus_remotedesktop_interface.xml @@ -0,0 +1,374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +