mousepad: On Wayland use ei to send events

This is to make setting the absolute cursor position work.
The remote desktop portal only allows screencasting sessions
to use NotifyPointerMotionAbsolute.
This commit is contained in:
David Redondo 2024-07-03 08:53:03 +02:00
parent 5a11df5d7c
commit 8c4c7d7baa
3 changed files with 280 additions and 28 deletions

View file

@ -1,9 +1,11 @@
kdeconnect_add_plugin(kdeconnect_mousepad SOURCES mousepadplugin.cpp abstractremoteinput.cpp) kdeconnect_add_plugin(kdeconnect_mousepad SOURCES mousepadplugin.cpp abstractremoteinput.cpp)
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
pkg_check_modules(PKG_libei REQUIRED IMPORTED_TARGET libei-1.0)
target_sources(kdeconnect_mousepad PUBLIC waylandremoteinput.cpp ${SRCS}) target_sources(kdeconnect_mousepad PUBLIC waylandremoteinput.cpp ${SRCS})
target_sources(kdeconnect_mousepad PRIVATE ${wayland_SRCS}) target_sources(kdeconnect_mousepad PRIVATE ${wayland_SRCS})
target_link_libraries(kdeconnect_mousepad Wayland::Client Qt::WaylandClient PkgConfig::XkbCommon) target_link_libraries(kdeconnect_mousepad Wayland::Client Qt::WaylandClient PkgConfig::XkbCommon PkgConfig::PKG_libei)
if (WITH_X11) if (WITH_X11)
find_package(LibFakeKey REQUIRED) find_package(LibFakeKey REQUIRED)

View file

@ -15,7 +15,10 @@
#include <KSharedConfig> #include <KSharedConfig>
#include <QDBusPendingCallWatcher> #include <QDBusPendingCallWatcher>
#include <libei.h>
#include <linux/input.h> #include <linux/input.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
namespace namespace
@ -60,12 +63,67 @@ int SpecialKeysMap[] = {
Q_GLOBAL_STATIC(RemoteDesktopSession, s_session); Q_GLOBAL_STATIC(RemoteDesktopSession, s_session);
class Xkb
{
public:
Xkb()
{
m_xkbcontext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS));
m_xkbkeymap.reset(xkb_keymap_new_from_names(m_xkbcontext.get(), nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS));
m_xkbstate.reset(xkb_state_new(m_xkbkeymap.get()));
}
Xkb(int keymapFd, int size)
{
m_xkbcontext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS));
char *map = static_cast<char *>(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, keymapFd, 0));
if (map != MAP_FAILED) {
m_xkbkeymap.reset(xkb_keymap_new_from_string(m_xkbcontext.get(), map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS));
munmap(map, size);
}
close(keymapFd);
if (!m_xkbkeymap) {
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Failed to create keymap";
m_xkbkeymap.reset(xkb_keymap_new_from_names(m_xkbcontext.get(), nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS));
}
m_xkbstate.reset(xkb_state_new(m_xkbkeymap.get()));
}
std::optional<int> keycodeFromKeysym(xkb_keysym_t keysym)
{
auto layout = xkb_state_serialize_layout(m_xkbstate.get(), XKB_STATE_LAYOUT_EFFECTIVE);
const xkb_keycode_t max = xkb_keymap_max_keycode(m_xkbkeymap.get());
for (xkb_keycode_t keycode = xkb_keymap_min_keycode(m_xkbkeymap.get()); keycode < max; keycode++) {
uint levelCount = xkb_keymap_num_levels_for_key(m_xkbkeymap.get(), keycode, layout);
for (uint currentLevel = 0; currentLevel < levelCount; currentLevel++) {
const xkb_keysym_t *syms;
uint num_syms = xkb_keymap_key_get_syms_by_level(m_xkbkeymap.get(), keycode, layout, currentLevel, &syms);
for (uint sym = 0; sym < num_syms; sym++) {
if (syms[sym] == keysym) {
return {keycode - 8};
}
}
}
}
return {};
}
private:
template<auto D>
using deleter = std::integral_constant<decltype(D), D>;
std::unique_ptr<xkb_context, deleter<xkb_context_unref>> m_xkbcontext;
std::unique_ptr<xkb_keymap, deleter<xkb_keymap_unref>> m_xkbkeymap;
std::unique_ptr<xkb_state, deleter<xkb_state_unref>> m_xkbstate;
};
RemoteDesktopSession::RemoteDesktopSession() RemoteDesktopSession::RemoteDesktopSession()
: iface(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"), : iface(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"),
QLatin1String("/org/freedesktop/portal/desktop"), QLatin1String("/org/freedesktop/portal/desktop"),
QDBusConnection::sessionBus(), QDBusConnection::sessionBus(),
this)) this))
, m_eiNotifier(QSocketNotifier::Read)
, m_xkb(new Xkb)
{ {
connect(&m_eiNotifier, &QSocketNotifier::activated, this, &RemoteDesktopSession::handleEiEvents);
} }
void RemoteDesktopSession::createSession() void RemoteDesktopSession::createSession()
@ -188,11 +246,184 @@ void RemoteDesktopSession::handleXdpSessionStarted(uint code, const QVariantMap
KConfigGroup stateConfig = KSharedConfig::openStateConfig()->group(QStringLiteral("mousepad")); KConfigGroup stateConfig = KSharedConfig::openStateConfig()->group(QStringLiteral("mousepad"));
stateConfig.writeEntry(QStringLiteral("RestoreToken"), results[QStringLiteral("restore_token")].toString()); stateConfig.writeEntry(QStringLiteral("RestoreToken"), results[QStringLiteral("restore_token")].toString());
auto call = iface->ConnectToEIS(m_xdpPath, {});
connect(new QDBusPendingCallWatcher(call), &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *watcher) {
watcher->deleteLater();
QDBusReply<QDBusUnixFileDescriptor> reply = *watcher;
if (!reply.isValid()) {
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Error connecting to eis" << reply.error();
return;
}
connectToEi(reply.value().takeFileDescriptor());
});
}
void RemoteDesktopSession::connectToEi(int fd)
{
m_eiNotifier.setSocket(fd);
m_eiNotifier.setEnabled(true);
m_ei = ei_new_sender(this);
ei_log_set_handler(m_ei, nullptr);
ei_setup_backend_fd(m_ei, fd);
}
void RemoteDesktopSession::handleEiEvents()
{
ei_dispatch(m_ei);
while (auto event = ei_get_event(m_ei)) {
const auto type = ei_event_get_type(event);
switch (type) {
case EI_EVENT_CONNECT:
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Connected to ei";
break;
case EI_EVENT_DISCONNECT:
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Disconnected from ei";
break;
case EI_EVENT_SEAT_ADDED: {
auto seat = ei_event_get_seat(event);
ei_seat_bind_capabilities(seat,
EI_DEVICE_CAP_KEYBOARD,
EI_DEVICE_CAP_POINTER,
EI_DEVICE_CAP_POINTER_ABSOLUTE,
EI_DEVICE_CAP_BUTTON,
EI_DEVICE_CAP_SCROLL,
nullptr);
break;
}
case EI_EVENT_SEAT_REMOVED:
break;
case EI_EVENT_DEVICE_ADDED: {
auto device = ei_event_get_device(event);
if (ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD) && !m_keyboard) {
auto keymap = ei_device_keyboard_get_keymap(device);
if (ei_keymap_get_type(keymap) != EI_KEYMAP_TYPE_XKB) {
break;
}
m_xkb.reset(new Xkb(ei_keymap_get_fd(keymap), ei_keymap_get_size(keymap)));
m_keyboard = device;
}
if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !m_pointer) {
m_pointer = device;
}
if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) && !m_absolutePointer) {
m_absolutePointer = device;
}
} break;
case EI_EVENT_DEVICE_REMOVED: {
auto device = ei_event_get_device(event);
if (device == m_keyboard) {
m_keyboard = nullptr;
}
if (device == m_pointer) {
m_pointer = nullptr;
}
if (device == m_absolutePointer) {
m_absolutePointer = nullptr;
}
break;
}
case EI_EVENT_DEVICE_PAUSED:
break;
case EI_EVENT_DEVICE_RESUMED:
ei_device_start_emulating(ei_event_get_device(event), 0);
break;
case EI_EVENT_FRAME:
case EI_EVENT_POINTER_MOTION:
case EI_EVENT_POINTER_MOTION_ABSOLUTE:
case EI_EVENT_BUTTON_BUTTON:
case EI_EVENT_SCROLL_DELTA:
case EI_EVENT_SCROLL_DISCRETE:
case EI_EVENT_SCROLL_STOP:
case EI_EVENT_SCROLL_CANCEL:
case EI_EVENT_KEYBOARD_MODIFIERS:
case EI_EVENT_KEYBOARD_KEY:
case EI_EVENT_DEVICE_START_EMULATING:
case EI_EVENT_DEVICE_STOP_EMULATING:
case EI_EVENT_TOUCH_DOWN:
case EI_EVENT_TOUCH_MOTION:
case EI_EVENT_TOUCH_UP:
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Unexpected event of type" << ei_event_get_type(event);
break;
}
ei_event_unref(event);
}
} }
void RemoteDesktopSession::handleXdpSessionFinished(uint /*code*/, const QVariantMap & /*results*/) void RemoteDesktopSession::handleXdpSessionFinished(uint /*code*/, const QVariantMap & /*results*/)
{ {
m_xdpPath = {}; m_xdpPath = {};
m_eiNotifier.setEnabled(false);
ei_unref(m_ei);
m_pointer = nullptr;
m_keyboard = nullptr;
m_absolutePointer = nullptr;
}
void RemoteDesktopSession::pointerButton(int button, bool down)
{
if (m_ei && m_pointer) {
ei_device_button_button(m_pointer, button, down);
ei_device_frame(m_pointer, ei_now(m_ei));
} else {
iface->NotifyPointerButton(m_xdpPath, {}, BTN_LEFT, down);
}
}
void RemoteDesktopSession::pointerAxis(double dx, double dy)
{
if (m_ei && m_pointer) {
// Qt/Kdeconnect use inverted vertical scroll direction compared to libei
ei_device_scroll_delta(m_pointer, dx, dy * -1);
ei_device_frame(m_pointer, ei_now(m_ei));
} else {
iface->NotifyPointerAxis(m_xdpPath, {}, dx, dy);
}
}
void RemoteDesktopSession::pointerMotion(double dx, double dy)
{
if (m_ei && m_pointer) {
ei_device_pointer_motion(m_pointer, dx, dy);
ei_device_frame(m_pointer, ei_now(m_ei));
} else {
iface->NotifyPointerMotion(m_xdpPath, {}, dx, dy);
}
}
void RemoteDesktopSession::pointerMotionAbsolute(double x, double y)
{
if (m_ei && m_absolutePointer) {
qDebug() << x << y;
ei_device_pointer_motion_absolute(m_absolutePointer, x, y);
ei_device_frame(m_absolutePointer, ei_now(m_ei));
} else {
iface->NotifyPointerMotionAbsolute(m_xdpPath, {}, 0, x, y);
}
}
void RemoteDesktopSession::keyboardKeycode(int key, bool press)
{
if (m_ei && m_keyboard) {
ei_device_keyboard_key(m_keyboard, key, press);
ei_device_frame(m_keyboard, ei_now(m_ei));
} else {
iface->NotifyKeyboardKeycode(m_xdpPath, {}, key, press);
}
}
void RemoteDesktopSession::keyboardKeysym(int sym, bool press)
{
if (m_ei && m_keyboard) {
if (auto code = m_xkb->keycodeFromKeysym(sym)) {
ei_device_keyboard_key(m_keyboard, *code, press);
ei_device_frame(m_keyboard, ei_now(m_ei));
} else {
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "failed to convert keysym" << sym;
}
} else {
iface->NotifyKeyboardKeysym(m_xdpPath, {}, sym, press).waitForFinished();
}
} }
WaylandRemoteInput::WaylandRemoteInput(QObject *parent) WaylandRemoteInput::WaylandRemoteInput(QObject *parent)
@ -225,26 +456,26 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isSingleRelease || isScroll || !key.isEmpty() || specialKey) { if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isSingleRelease || isScroll || !key.isEmpty() || specialKey) {
if (isSingleClick) { if (isSingleClick) {
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1); s_session->pointerButton(BTN_LEFT, true);
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0); s_session->pointerButton(BTN_LEFT, false);
} else if (isDoubleClick) { } else if (isDoubleClick) {
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1); s_session->pointerButton(BTN_LEFT, true);
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0); s_session->pointerButton(BTN_LEFT, false);
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1); s_session->pointerButton(BTN_LEFT, true);
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0); s_session->pointerButton(BTN_LEFT, false);
} else if (isMiddleClick) { } else if (isMiddleClick) {
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 1); s_session->pointerButton(BTN_MIDDLE, true);
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 0); s_session->pointerButton(BTN_MIDDLE, false);
} else if (isRightClick) { } else if (isRightClick) {
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 1); s_session->pointerButton(BTN_RIGHT, true);
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 0); s_session->pointerButton(BTN_RIGHT, false);
} else if (isSingleHold) { } else if (isSingleHold) {
// For drag'n drop // For drag'n drop
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1); s_session->pointerButton(BTN_LEFT, true);
} else if (isSingleRelease) { } else if (isSingleRelease) {
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0); s_session->pointerButton(BTN_LEFT, false);
} else if (isScroll) { } else if (isScroll) {
s_session->iface->NotifyPointerAxis(s_session->m_xdpPath, {}, dx, dy); s_session->pointerAxis(dx, dy);
} else if (specialKey || !key.isEmpty()) { } else if (specialKey || !key.isEmpty()) {
bool ctrl = np.get<bool>(QStringLiteral("ctrl"), false); bool ctrl = np.get<bool>(QStringLiteral("ctrl"), false);
bool alt = np.get<bool>(QStringLiteral("alt"), false); bool alt = np.get<bool>(QStringLiteral("alt"), false);
@ -252,23 +483,23 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
bool super = np.get<bool>(QStringLiteral("super"), false); bool super = np.get<bool>(QStringLiteral("super"), false);
if (ctrl) if (ctrl)
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 1); s_session->keyboardKeycode(KEY_LEFTCTRL, true);
if (alt) if (alt)
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 1); s_session->keyboardKeycode(KEY_LEFTALT, true);
if (shift) if (shift)
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 1); s_session->keyboardKeycode(KEY_LEFTSHIFT, true);
if (super) if (super)
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTMETA, 1); s_session->keyboardKeycode(KEY_LEFTMETA, true);
if (specialKey) { if (specialKey) {
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 1); s_session->keyboardKeycode(SpecialKeysMap[specialKey], true);
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 0); s_session->keyboardKeycode(SpecialKeysMap[specialKey], false);
} else if (!key.isEmpty()) { } else if (!key.isEmpty()) {
for (const QChar character : key) { for (const QChar character : key) {
const auto keysym = xkb_utf32_to_keysym(character.toLower().unicode()); const auto keysym = xkb_utf32_to_keysym(character.toLower().unicode());
if (keysym != XKB_KEY_NoSymbol) { if (keysym != XKB_KEY_NoSymbol) {
s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 1).waitForFinished(); s_session->keyboardKeysym(keysym, true);
s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 0).waitForFinished(); s_session->keyboardKeysym(keysym, false);
} else { } else {
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Cannot send character" << character; qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Cannot send character" << character;
} }
@ -276,19 +507,19 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
} }
if (ctrl) if (ctrl)
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 0); s_session->keyboardKeycode(KEY_LEFTCTRL, false);
if (alt) if (alt)
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 0); s_session->keyboardKeycode(KEY_LEFTALT, false);
if (shift) if (shift)
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 0); s_session->keyboardKeycode(KEY_LEFTSHIFT, false);
if (super) if (super)
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTMETA, 0); s_session->keyboardKeycode(KEY_LEFTMETA, false);
} }
} else { // Is a mouse move event } else { // Is a mouse move event
if (dx || dy) if (dx || dy)
s_session->iface->NotifyPointerMotion(s_session->m_xdpPath, {}, dx, dy); s_session->pointerMotion(dx, dy);
else if ((np.has(QStringLiteral("x")) || np.has(QStringLiteral("y")))) else if ((np.has(QStringLiteral("x")) || np.has(QStringLiteral("y"))))
s_session->iface->NotifyPointerMotionAbsolute(s_session->m_xdpPath, {}, 0, x, y); s_session->pointerMotionAbsolute(x, y);
} }
return true; return true;
} }

View file

@ -10,8 +10,12 @@
#include "abstractremoteinput.h" #include "abstractremoteinput.h"
#include "generated/systeminterfaces/remotedesktop.h" #include "generated/systeminterfaces/remotedesktop.h"
#include <QDBusObjectPath> #include <QDBusObjectPath>
#include <QSocketNotifier>
class FakeInput; class FakeInput;
class Xkb;
struct ei;
struct ei_device;
class RemoteDesktopSession : public QObject class RemoteDesktopSession : public QObject
{ {
@ -27,6 +31,13 @@ public:
QDBusObjectPath m_xdpPath; QDBusObjectPath m_xdpPath;
bool m_connecting = false; bool m_connecting = false;
void pointerButton(int button, bool press);
void pointerAxis(double dx, double dy);
void pointerMotion(double dx, double dy);
void pointerMotionAbsolute(double x, double y);
void keyboardKeysym(int sym, bool press);
void keyboardKeycode(int key, bool press);
private Q_SLOTS: private Q_SLOTS:
void handleXdpSessionCreated(uint code, const QVariantMap &results); void handleXdpSessionCreated(uint code, const QVariantMap &results);
void handleXdpSessionConfigured(uint code, const QVariantMap &results); void handleXdpSessionConfigured(uint code, const QVariantMap &results);
@ -34,6 +45,14 @@ private Q_SLOTS:
void handleXdpSessionFinished(uint code, const QVariantMap &results); void handleXdpSessionFinished(uint code, const QVariantMap &results);
private: private:
void connectToEi(int fd);
void handleEiEvents();
QSocketNotifier m_eiNotifier;
std::unique_ptr<Xkb> m_xkb;
ei *m_ei = nullptr;
ei_device *m_keyboard = nullptr;
ei_device *m_pointer = nullptr;
ei_device *m_absolutePointer = nullptr;
}; };
class WaylandRemoteInput : public AbstractRemoteInput class WaylandRemoteInput : public AbstractRemoteInput