From fc2447071e3bae3f21270be03d025a928872340d Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Thu, 27 Jan 2022 20:14:53 +0100 Subject: [PATCH] Xwayland: Allow users to optionally let Xwayland eavesdrop on certain modes It's somewhat popular for voice communication applications to support Push-to-Talk. This means that the process itself expects to get all of the system input. This behaviour albeit sound does not work on Wayland systems. This commit adds an option to let legacy X11 applications that assume they will be getting all information to do so until these apps are properly ported to the XDP GlobalShortcuts. --- src/input.cpp | 5 +- src/inputmethod.cpp | 3 +- src/kcmkwin/CMakeLists.txt | 1 + src/kcmkwin/kwinxwayland/CMakeLists.txt | 25 +++ src/kcmkwin/kwinxwayland/Messages.sh | 2 + .../kwinxwayland/kcm_kwinxwayland.json | 15 ++ src/kcmkwin/kwinxwayland/kcmkwinxwayland.cpp | 38 +++++ src/kcmkwin/kwinxwayland/kcmkwinxwayland.h | 36 +++++ .../kwinxwayland/kwinxwaylandsettings.kcfg | 18 +++ .../kwinxwayland/kwinxwaylandsettings.kcfgc | 8 + .../kwinxwayland/package/contents/ui/main.qml | 70 +++++++++ src/kwin.kcfg | 10 ++ src/options.cpp | 14 ++ src/options.h | 18 +++ src/wayland/keyboard_interface.cpp | 15 +- src/wayland/keyboard_interface.h | 2 + src/wayland/seat_interface.cpp | 1 + src/wayland/seat_interface.h | 5 + src/xwayland/xwayland.cpp | 147 ++++++++++++++++++ src/xwayland/xwayland.h | 3 + src/xwayland/xwaylandlauncher.h | 1 + 21 files changed, 429 insertions(+), 8 deletions(-) create mode 100644 src/kcmkwin/kwinxwayland/CMakeLists.txt create mode 100644 src/kcmkwin/kwinxwayland/Messages.sh create mode 100644 src/kcmkwin/kwinxwayland/kcm_kwinxwayland.json create mode 100644 src/kcmkwin/kwinxwayland/kcmkwinxwayland.cpp create mode 100644 src/kcmkwin/kwinxwayland/kcmkwinxwayland.h create mode 100644 src/kcmkwin/kwinxwayland/kwinxwaylandsettings.kcfg create mode 100644 src/kcmkwin/kwinxwayland/kwinxwaylandsettings.kcfgc create mode 100644 src/kcmkwin/kwinxwayland/package/contents/ui/main.qml diff --git a/src/input.cpp b/src/input.cpp index 1215a9a8b6..0b8724b88b 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -238,12 +238,13 @@ void InputEventFilter::passToWaylandServer(QKeyEvent *event) } KWaylandServer::SeatInterface *seat = waylandServer()->seat(); + const int keyCode = event->nativeScanCode(); switch (event->type()) { case QEvent::KeyPress: - seat->notifyKeyboardKey(event->nativeScanCode(), KWaylandServer::KeyboardKeyState::Pressed); + seat->notifyKeyboardKey(keyCode, KWaylandServer::KeyboardKeyState::Pressed); break; case QEvent::KeyRelease: - seat->notifyKeyboardKey(event->nativeScanCode(), KWaylandServer::KeyboardKeyState::Released); + seat->notifyKeyboardKey(keyCode, KWaylandServer::KeyboardKeyState::Released); break; default: break; diff --git a/src/inputmethod.cpp b/src/inputmethod.cpp index ffd738393d..17920504b4 100644 --- a/src/inputmethod.cpp +++ b/src/inputmethod.cpp @@ -615,7 +615,8 @@ void InputMethod::setPreeditString(uint32_t serial, const QString &text, const Q void InputMethod::key(quint32 /*serial*/, quint32 /*time*/, quint32 keyCode, bool pressed) { - waylandServer()->seat()->notifyKeyboardKey(keyCode, pressed ? KWaylandServer::KeyboardKeyState::Pressed : KWaylandServer::KeyboardKeyState::Released); + waylandServer()->seat()->notifyKeyboardKey(keyCode, + pressed ? KWaylandServer::KeyboardKeyState::Pressed : KWaylandServer::KeyboardKeyState::Released); } void InputMethod::modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group) diff --git a/src/kcmkwin/CMakeLists.txt b/src/kcmkwin/CMakeLists.txt index e81c12c867..6694585145 100644 --- a/src/kcmkwin/CMakeLists.txt +++ b/src/kcmkwin/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(kwinscripts) add_subdirectory(kwindesktop) add_subdirectory(kwineffects) add_subdirectory(kwinvirtualkeyboard) +add_subdirectory(kwinxwayland) if (KWIN_BUILD_TABBOX) add_subdirectory(kwintabbox) diff --git a/src/kcmkwin/kwinxwayland/CMakeLists.txt b/src/kcmkwin/kwinxwayland/CMakeLists.txt new file mode 100644 index 0000000000..37deb40213 --- /dev/null +++ b/src/kcmkwin/kwinxwayland/CMakeLists.txt @@ -0,0 +1,25 @@ +#SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalzez +#SPDX-License-Identifier: BSD-3-Clause + +add_definitions(-DTRANSLATION_DOMAIN=\"kcm_kwinxwayland\") + +kcmutils_generate_module_data( + kcm_kwinxwayland_PART_SRCS + MODULE_DATA_HEADER kwinxwaylanddata.h + MODULE_DATA_CLASS_NAME KWinXwaylandData + SETTINGS_HEADERS kwinxwaylandsettings.h + SETTINGS_CLASSES KWinXwaylandSettings +) + +kconfig_add_kcfg_files(kcm_kwinxwayland_PART_SRCS kwinxwaylandsettings.kcfgc GENERATE_MOC) +kcoreaddons_add_plugin(kcm_kwinxwayland SOURCES kcmkwinxwayland.cpp ${kcm_kwinxwayland_PART_SRCS} INSTALL_NAMESPACE plasma/kcms/systemsettings) +target_include_directories(kcm_kwinxwayland PRIVATE ${CMAKE_SOURCE_DIR}) +kcmutils_generate_desktop_file(kcm_kwinxwayland) + +target_link_libraries(kcm_kwinxwayland + KF5::I18n + KF5::KCMUtils + KF5::QuickAddons + Wayland::Client +) +kpackage_install_package(package kcm_kwinxwayland kcms) diff --git a/src/kcmkwin/kwinxwayland/Messages.sh b/src/kcmkwin/kwinxwayland/Messages.sh new file mode 100644 index 0000000000..c43cd42c2e --- /dev/null +++ b/src/kcmkwin/kwinxwayland/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml` -o $podir/kcm_kwinxwayland.pot diff --git a/src/kcmkwin/kwinxwayland/kcm_kwinxwayland.json b/src/kcmkwin/kwinxwayland/kcm_kwinxwayland.json new file mode 100644 index 0000000000..ef6fa3a86f --- /dev/null +++ b/src/kcmkwin/kwinxwayland/kcm_kwinxwayland.json @@ -0,0 +1,15 @@ +{ + "Categories": "Qt;KDE;", + "KPlugin": { + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=systemsettings&component=kcm_kwinxwayland", + "Description": "Select which keys will be globally available to legacy X11 apps", + "Icon": "xorg", + "Name": "Legacy X11 App Support" + }, + "X-KDE-Keywords": "xwayland,global,keys,forward", + "X-KDE-OnlyShowOnQtPlatforms": [ + "wayland" + ], + "X-KDE-ParentApp": "kcontrol", + "X-KDE-System-Settings-Parent-Category": "applications" +} diff --git a/src/kcmkwin/kwinxwayland/kcmkwinxwayland.cpp b/src/kcmkwin/kwinxwayland/kcmkwinxwayland.cpp new file mode 100644 index 0000000000..00ae369ccf --- /dev/null +++ b/src/kcmkwin/kwinxwayland/kcmkwinxwayland.cpp @@ -0,0 +1,38 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kcmkwinxwayland.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +K_PLUGIN_FACTORY_WITH_JSON(KcmXwaylandFactory, "kcm_kwinxwayland.json", registerPlugin(); registerPlugin();) + +KcmXwayland::KcmXwayland(QObject *parent, const QVariantList &args) + : KQuickAddons::ManagedConfigModule(parent) + , m_data(new KWinXwaylandData(this)) + , m_settings(new KWinXwaylandSettings(m_data)) +{ + registerSettings(m_settings); + qmlRegisterAnonymousType("org.kde.kwin.kwinxwaylandsettings", 1); + + setAboutData(new KAboutData(QStringLiteral("kcm_kwinxwayland"), + i18n("Legacy X11 App Support"), + QStringLiteral("1.0"), + i18n("Allow legacy X11 apps to read keystrokes typed in other apps"), + KAboutLicense::GPL)); +} + +KcmXwayland::~KcmXwayland() = default; + +#include "kcmkwinxwayland.moc" diff --git a/src/kcmkwin/kwinxwayland/kcmkwinxwayland.h b/src/kcmkwin/kwinxwayland/kcmkwinxwayland.h new file mode 100644 index 0000000000..d905175936 --- /dev/null +++ b/src/kcmkwin/kwinxwayland/kcmkwinxwayland.h @@ -0,0 +1,36 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include +#include + +#include + +class KWinXwaylandData; + +class KcmXwayland : public KQuickAddons::ManagedConfigModule +{ + Q_OBJECT + Q_PROPERTY(KWinXwaylandSettings *settings READ settings CONSTANT) + +public: + explicit KcmXwayland(QObject *parent = nullptr, const QVariantList &list = QVariantList()); + ~KcmXwayland() override; + + KWinXwaylandSettings *settings() const + { + return m_settings; + } + +private: + void refresh(); + + KWinXwaylandData *const m_data; + KWinXwaylandSettings *const m_settings; +}; diff --git a/src/kcmkwin/kwinxwayland/kwinxwaylandsettings.kcfg b/src/kcmkwin/kwinxwayland/kwinxwaylandsettings.kcfg new file mode 100644 index 0000000000..5e0c9178fd --- /dev/null +++ b/src/kcmkwin/kwinxwayland/kwinxwaylandsettings.kcfg @@ -0,0 +1,18 @@ + + + + + + + + + + + + None + + + diff --git a/src/kcmkwin/kwinxwayland/kwinxwaylandsettings.kcfgc b/src/kcmkwin/kwinxwayland/kwinxwaylandsettings.kcfgc new file mode 100644 index 0000000000..9660d4fe5e --- /dev/null +++ b/src/kcmkwin/kwinxwayland/kwinxwaylandsettings.kcfgc @@ -0,0 +1,8 @@ +File=kwinxwaylandsettings.kcfg +ClassName=KWinXwaylandSettings +Mutators=true +DefaultValueGetters=true +GenerateProperties=true +ParentInConstructor=true +Notifiers=true +GlobalEnums=true diff --git a/src/kcmkwin/kwinxwayland/package/contents/ui/main.qml b/src/kcmkwin/kwinxwayland/package/contents/ui/main.qml new file mode 100644 index 0000000000..8cd8bab25b --- /dev/null +++ b/src/kcmkwin/kwinxwayland/package/contents/ui/main.qml @@ -0,0 +1,70 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + + +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as QQC2 +import org.kde.kirigami 2.6 as Kirigami +import org.kde.kcm 1.3 as KCM +import org.kde.kwin.kwinxwaylandsettings 1.0 +import org.kde.kquickcontrols 2.0 + +KCM.SimpleKCM { + id: root + KCM.ConfigModule.buttons: KCM.ConfigModule.Default | KCM.ConfigModule.Apply + KCM.ConfigModule.quickHelp: i18n("This module lets configure which keyboard events are forwarded to X11 apps regardless of their focus.") + KCM.SettingStateBinding { + configObject: kcm.settings + settingName: "Xwayland" + } + implicitWidth: Kirigami.Units.gridUnit * 48 + implicitHeight: Kirigami.Units.gridUnit * 33 + + QQC2.ButtonGroup { + buttons: column.children + exclusive: true + checkedButton: buttons[kcm.settings.xwaylandEavesdrops] + onCheckedButtonChanged: { + let idx = -1 + for (const x in buttons) { + if (buttons[x] === checkedButton) { + idx = x + } + } + kcm.settings.xwaylandEavesdrops = idx + } + } + + ColumnLayout { + id: column + QQC2.Label { + Layout.fillWidth: true + text: i18n("Allow legacy X11 apps to read keystrokes typed in all apps:") + } + + QQC2.RadioButton { + text: i18n("Never") + } + QQC2.RadioButton { + text: i18n("Only Meta, Control, Alt, and Shift keys") + } + QQC2.RadioButton { + text: i18n("All keys, but only while Meta, Ctrl, Alt, or Shift keys are pressed") + } + QQC2.RadioButton { + id: always + text: i18n("Always") + } + + Kirigami.InlineMessage { + Layout.fillWidth: true + type: Kirigami.MessageType.Warning + text: i18n("Note that using this setting will reduce system security to that of the X11 session by permitting malicious software to steal passwords and spy on the text that you type. Make sure you understand and accept this risk.") + visible: always.checked + } + } +} diff --git a/src/kwin.kcfg b/src/kwin.kcfg index 17d0c8486c..ffc7b1e353 100644 --- a/src/kwin.kcfg +++ b/src/kwin.kcfg @@ -352,5 +352,15 @@ 3 + + + + + + + + + None + diff --git a/src/options.cpp b/src/options.cpp index e74d46cb58..3ee7d3f93f 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -20,6 +20,7 @@ #include #include "settings.h" +#include "workspace.h" #include #include @@ -55,6 +56,7 @@ Options::Options(QObject *parent) , m_hideUtilityWindowsForInactive(false) , m_xwaylandCrashPolicy(Options::defaultXwaylandCrashPolicy()) , m_xwaylandMaxCrashCount(Options::defaultXwaylandMaxCrashCount()) + , m_xwaylandEavesdrops(Options::defaultXwaylandEavesdrops()) , m_latencyPolicy(Options::defaultLatencyPolicy()) , m_renderTimeEstimator(Options::defaultRenderTimeEstimator()) , m_compositingMode(Options::defaultCompositingMode()) @@ -97,6 +99,8 @@ Options::Options(QObject *parent) connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) { if (group.name() == QLatin1String("KDE") && names.contains(QByteArrayLiteral("AnimationDurationFactor"))) { Q_EMIT animationSpeedChanged(); + } else if (group.name() == QLatin1String("Xwayland")) { + workspace()->reconfigure(); } }); } @@ -146,6 +150,15 @@ void Options::setXwaylandMaxCrashCount(int maxCrashCount) Q_EMIT xwaylandMaxCrashCountChanged(); } +void Options::setXwaylandEavesdrops(XwaylandEavesdropsMode mode) +{ + if (m_xwaylandEavesdrops == mode) { + return; + } + m_xwaylandEavesdrops = mode; + Q_EMIT xwaylandEavesdropsChanged(); +} + void Options::setClickRaise(bool clickRaise) { if (m_autoRaise) { @@ -785,6 +798,7 @@ void Options::syncFromKcfgc() setActivationDesktopPolicy(m_settings->activationDesktopPolicy()); setXwaylandCrashPolicy(m_settings->xwaylandCrashPolicy()); setXwaylandMaxCrashCount(m_settings->xwaylandMaxCrashCount()); + setXwaylandEavesdrops(XwaylandEavesdropsMode(m_settings->xwaylandEavesdrops())); setPlacement(m_settings->placement()); setAutoRaise(m_settings->autoRaise()); setAutoRaiseInterval(m_settings->autoRaiseInterval()); diff --git a/src/options.h b/src/options.h index a8f4a7c804..0c1cdb6e1f 100644 --- a/src/options.h +++ b/src/options.h @@ -33,6 +33,13 @@ enum HiddenPreviews { HiddenPreviewsAlways }; +enum XwaylandEavesdropsMode { + None, + Modifiers, + Combinations, + All +}; + /** * This enum type specifies whether the Xwayland server must be restarted after a crash. */ @@ -275,6 +282,10 @@ public: { return m_xwaylandMaxCrashCount; } + XwaylandEavesdropsMode xwaylandEavesdrops() const + { + return m_xwaylandEavesdrops; + } /** * Whether clicking on a window raises it in FocusFollowsMouse @@ -711,6 +722,7 @@ public: void setFocusPolicy(FocusPolicy focusPolicy); void setXwaylandCrashPolicy(XwaylandCrashPolicy crashPolicy); void setXwaylandMaxCrashCount(int maxCrashCount); + void setXwaylandEavesdrops(XwaylandEavesdropsMode mode); void setNextFocusPrefersMouse(bool nextFocusPrefersMouse); void setClickRaise(bool clickRaise); void setAutoRaise(bool autoRaise); @@ -888,6 +900,10 @@ public: { return 3; } + static XwaylandEavesdropsMode defaultXwaylandEavesdrops() + { + return None; + } static LatencyPolicy defaultLatencyPolicy() { return LatencyMedium; @@ -917,6 +933,7 @@ Q_SIGNALS: void focusPolicyIsResonableChanged(); void xwaylandCrashPolicyChanged(); void xwaylandMaxCrashCountChanged(); + void xwaylandEavesdropsChanged(); void nextFocusPrefersMouseChanged(); void clickRaiseChanged(); void autoRaiseChanged(); @@ -1002,6 +1019,7 @@ private: bool m_hideUtilityWindowsForInactive; XwaylandCrashPolicy m_xwaylandCrashPolicy; int m_xwaylandMaxCrashCount; + XwaylandEavesdropsMode m_xwaylandEavesdrops; LatencyPolicy m_latencyPolicy; RenderTimeEstimator m_renderTimeEstimator; diff --git a/src/wayland/keyboard_interface.cpp b/src/wayland/keyboard_interface.cpp index bd0a3c8389..8c86954d65 100644 --- a/src/wayland/keyboard_interface.cpp +++ b/src/wayland/keyboard_interface.cpp @@ -169,6 +169,15 @@ QVector KeyboardInterfacePrivate::pressedKeys() const return keys; } +void KeyboardInterface::sendKey(quint32 key, KeyboardKeyState state, ClientConnection *client) +{ + const QList keyboards = d->keyboardsForClient(client); + const quint32 serial = d->seat->display()->nextSerial(); + for (KeyboardInterfacePrivate::Resource *keyboardResource : keyboards) { + d->send_key(keyboardResource->handle, serial, d->seat->timestamp(), key, quint32(state)); + } +} + void KeyboardInterface::sendKey(quint32 key, KeyboardKeyState state) { if (!d->updateKey(key, state)) { @@ -179,11 +188,7 @@ void KeyboardInterface::sendKey(quint32 key, KeyboardKeyState state) return; } - const QList keyboards = d->keyboardsForClient(d->focusedSurface->client()); - const quint32 serial = d->seat->display()->nextSerial(); - for (KeyboardInterfacePrivate::Resource *keyboardResource : keyboards) { - d->send_key(keyboardResource->handle, serial, d->seat->timestamp(), key, quint32(state)); - } + sendKey(key, state, d->focusedSurface->client()); } void KeyboardInterface::sendModifiers(quint32 depressed, quint32 latched, quint32 locked, quint32 group) diff --git a/src/wayland/keyboard_interface.h b/src/wayland/keyboard_interface.h index aaf31c9b75..e653d33874 100644 --- a/src/wayland/keyboard_interface.h +++ b/src/wayland/keyboard_interface.h @@ -11,6 +11,7 @@ namespace KWaylandServer { +class ClientConnection; class SeatInterface; class SurfaceInterface; class KeyboardInterfacePrivate; @@ -54,6 +55,7 @@ public: void setRepeatInfo(qint32 charactersPerSecond, qint32 delay); void sendKey(quint32 key, KeyboardKeyState state); + void sendKey(quint32 key, KeyboardKeyState state, ClientConnection *client); void sendModifiers(quint32 depressed, quint32 latched, quint32 locked, quint32 group); private: diff --git a/src/wayland/seat_interface.cpp b/src/wayland/seat_interface.cpp index 8143550a90..af33046cb3 100644 --- a/src/wayland/seat_interface.cpp +++ b/src/wayland/seat_interface.cpp @@ -908,6 +908,7 @@ void SeatInterface::setFocusedKeyboardSurface(SurfaceInterface *surface) return; } + Q_EMIT focusedKeyboardSurfaceAboutToChange(surface); const quint32 serial = d->display->nextSerial(); if (d->globalKeyboard.focus.surface) { diff --git a/src/wayland/seat_interface.h b/src/wayland/seat_interface.h index 042e10a842..d99d71fd87 100644 --- a/src/wayland/seat_interface.h +++ b/src/wayland/seat_interface.h @@ -720,6 +720,11 @@ Q_SIGNALS: * @see focusedTextInput */ void focusedTextInputSurfaceChanged(); + /** + * Emitted whenever the focused keyboard is about to change. + * @see focusedKeyboardSurface + */ + void focusedKeyboardSurfaceAboutToChange(SurfaceInterface *nextSurface); private: std::unique_ptr d; diff --git a/src/xwayland/xwayland.cpp b/src/xwayland/xwayland.cpp index 0fde06d01f..4f22311395 100644 --- a/src/xwayland/xwayland.cpp +++ b/src/xwayland/xwayland.cpp @@ -15,19 +15,27 @@ #include "cursor.h" #include "databridge.h" #include "dnd.h" +#include "window.h" #include "xwaylandlauncher.h" #include "xwldrophandler.h" #include "core/output.h" +#include "input_event_spy.h" +#include "keyboard_input.h" #include "main_wayland.h" #include "utils/common.h" #include "utils/xcbutils.h" #include "wayland_server.h" +#include "waylandwindow.h" #include "workspace.h" #include "x11eventfilter.h" +#include "xkb.h" #include "xwayland_logging.h" #include +#include +#include +#include #include #include @@ -41,8 +49,10 @@ #include #include +#include #include #include +#include namespace KWin { @@ -73,6 +83,116 @@ bool XrandrEventFilter::event(xcb_generic_event_t *event) return false; } +class XwaylandInputSpy : public QObject, public KWin::InputEventSpy +{ +public: + XwaylandInputSpy() + { + connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::focusedKeyboardSurfaceAboutToChange, + this, [this](KWaylandServer::SurfaceInterface *newSurface) { + auto keyboard = waylandServer()->seat()->keyboard(); + if (!newSurface) { + return; + } + + if (waylandServer()->xWaylandConnection() == newSurface->client()) { + // Since this is a spy but the keyboard interface gets its normal sendKey calls through filters, + // there can be a mismatch in both states. + // This loop makes sure all key press events are reset before we switch back to the + // Xwayland client and the state is correctly restored. + for (auto it = m_states.constBegin(); it != m_states.constEnd(); ++it) { + if (it.value() == KWaylandServer::KeyboardKeyState::Pressed) { + keyboard->sendKey(it.key(), KWaylandServer::KeyboardKeyState::Released, waylandServer()->xWaylandConnection()); + } + } + m_states.clear(); + } + }); + } + + void setMode(XwaylandEavesdropsMode mode) + { + static const QSet modifierKeys = { + Qt::Key_Control, + Qt::Key_Shift, + Qt::Key_Alt, + Qt::Key_Meta, + }; + + switch (mode) { + case None: + m_filter = {}; + break; + case Modifiers: + m_filter = [](int key, Qt::KeyboardModifiers) { + return modifierKeys.contains(key); + }; + break; + case Combinations: + m_filter = [](int key, Qt::KeyboardModifiers m) { + return m != Qt::NoModifier || modifierKeys.contains(key); + }; + break; + case All: + m_filter = [](int, Qt::KeyboardModifiers) { + return true; + }; + break; + } + } + + void keyEvent(KWin::KeyEvent *event) override + { + if (event->isAutoRepeat()) { + return; + } + + Window *window = workspace()->activeWindow(); + if (!m_filter || !m_filter(event->key(), event->modifiers()) || (window && window->isLockScreen())) { + return; + } + + auto keyboard = waylandServer()->seat()->keyboard(); + auto surface = keyboard->focusedSurface(); + if (!surface) { + return; + } + + auto client = surface->client(); + if (waylandServer()->xWaylandConnection() != client) { + KWaylandServer::KeyboardKeyState state{event->type() == QEvent::KeyPress}; + if (!updateKey(event->nativeScanCode(), state)) { + return; + } + + auto xkb = input()->keyboard()->xkb(); + keyboard->sendModifiers(xkb->modifierState().depressed, + xkb->modifierState().latched, + xkb->modifierState().locked, + xkb->currentLayout()); + + waylandServer()->seat()->keyboard()->sendKey(event->nativeScanCode(), state, waylandServer()->xWaylandConnection()); + } + } + + bool updateKey(quint32 key, KWaylandServer::KeyboardKeyState state) + { + auto it = m_states.find(key); + if (it == m_states.end()) { + m_states.insert(key, state); + return true; + } + if (it.value() == state) { + return false; + } + it.value() = state; + return true; + } + + QHash m_states; + std::function m_filter; +}; + Xwayland::Xwayland(Application *app) : m_app(app) , m_launcher(new XwaylandLauncher(this)) @@ -205,6 +325,33 @@ void Xwayland::handleXwaylandReady() delete m_xrandrEventsFilter; m_xrandrEventsFilter = new XrandrEventFilter(this); + + refreshEavesdropping(); + connect(options, &Options::xwaylandEavesdropsChanged, this, &Xwayland::refreshEavesdropping); +} + +void Xwayland::refreshEavesdropping() +{ + if (!waylandServer()->seat()->keyboard()) { + return; + } + + const bool enabled = options->xwaylandEavesdrops() != None; + if (enabled == bool(m_inputSpy)) { + if (m_inputSpy) { + m_inputSpy->setMode(options->xwaylandEavesdrops()); + } + return; + } + + if (enabled) { + m_inputSpy.reset(new XwaylandInputSpy); + input()->installInputEventSpy(m_inputSpy.get()); + m_inputSpy->setMode(options->xwaylandEavesdrops()); + } else { + input()->uninstallInputEventSpy(m_inputSpy.get()); + m_inputSpy.reset(); + } } void Xwayland::updatePrimary() diff --git a/src/xwayland/xwayland.h b/src/xwayland/xwayland.h index 67243b51a3..b9e057d7f4 100644 --- a/src/xwayland/xwayland.h +++ b/src/xwayland/xwayland.h @@ -27,6 +27,7 @@ class Application; namespace Xwl { class XrandrEventFilter; +class XwaylandInputSpy; class XwaylandLauncher; class DataBridge; @@ -69,6 +70,7 @@ private: void installSocketNotifier(); void uninstallSocketNotifier(); void updatePrimary(); + void refreshEavesdropping(); bool createX11Connection(); void destroyX11Connection(); @@ -83,6 +85,7 @@ private: XrandrEventFilter *m_xrandrEventsFilter = nullptr; XwaylandLauncher *m_launcher; + std::unique_ptr m_inputSpy; Q_DISABLE_COPY(Xwayland) }; diff --git a/src/xwayland/xwaylandlauncher.h b/src/xwayland/xwaylandlauncher.h index 34899efd35..eb1d785bb1 100644 --- a/src/xwayland/xwaylandlauncher.h +++ b/src/xwayland/xwaylandlauncher.h @@ -90,6 +90,7 @@ private Q_SLOTS: private: void maybeDestroyReadyNotifier(); + bool startInternal(); void stopInternal(); void restartInternal();