From bcd43ff44d323044cc130aac4d9423f4d0411122 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Thu, 8 Sep 2022 13:54:47 +0300 Subject: [PATCH] backends/x11: Implement own keyboard interception At the moment, the keyboard interception code in the effects system relies on Qt code processing key events. However, since QDesktopWidget is removed in Qt 6, this is a blocker for Qt 6 port. This change ports the X11 backend to private xkb keymap as indicates in the todo comment. It allows us to drop the last QDesktopWidget usage. --- CMakeLists.txt | 4 + src/backends/x11/standalone/CMakeLists.txt | 4 +- .../x11/standalone/x11_standalone_effects.cpp | 16 +- .../x11/standalone/x11_standalone_effects.h | 2 + ...e_effects_keyboard_interception_filter.cpp | 61 +++++ ...one_effects_keyboard_interception_filter.h | 31 +++ .../standalone/x11_standalone_keyboard.cpp | 224 ++++++++++++++++++ .../x11/standalone/x11_standalone_keyboard.h | 43 ++++ .../standalone/x11_standalone_platform.cpp | 8 + .../x11/standalone/x11_standalone_platform.h | 3 + src/events.cpp | 17 -- src/main_x11.cpp | 8 - src/main_x11.h | 1 - src/workspace.h | 1 - 14 files changed, 388 insertions(+), 35 deletions(-) create mode 100644 src/backends/x11/standalone/x11_standalone_effects_keyboard_interception_filter.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_effects_keyboard_interception_filter.h create mode 100644 src/backends/x11/standalone/x11_standalone_keyboard.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_keyboard.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8805407139..8798a4a88b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,6 +236,9 @@ set_package_properties(XKB PROPERTIES PURPOSE "Required for building KWin with Wayland support" ) +pkg_check_modules(XKBX11 IMPORTED_TARGET xkbcommon-x11 REQUIRED) +add_feature_info(XKBX11 XKBX11_FOUND "Required for handling keyboard events in X11 backend") + find_package(Libinput 1.19) set_package_properties(Libinput PROPERTIES TYPE REQUIRED PURPOSE "Required for input handling on Wayland.") @@ -294,6 +297,7 @@ find_package(XCB 1.10 REQUIRED COMPONENTS SYNC XCB XFIXES + XKB XINERAMA ) set_package_properties(XCB PROPERTIES TYPE REQUIRED) diff --git a/src/backends/x11/standalone/CMakeLists.txt b/src/backends/x11/standalone/CMakeLists.txt index a4595e7887..d4a05644e5 100644 --- a/src/backends/x11/standalone/CMakeLists.txt +++ b/src/backends/x11/standalone/CMakeLists.txt @@ -2,8 +2,10 @@ set(X11PLATFORM_SOURCES x11_standalone_cursor.cpp x11_standalone_edge.cpp x11_standalone_effects.cpp + x11_standalone_effects_keyboard_interception_filter.cpp x11_standalone_effects_mouse_interception_filter.cpp x11_standalone_egl_backend.cpp + x11_standalone_keyboard.cpp x11_standalone_logging.cpp x11_standalone_non_composited_outline.cpp x11_standalone_output.cpp @@ -16,7 +18,7 @@ set(X11PLATFORM_SOURCES ) add_library(KWinX11Platform OBJECT ${X11PLATFORM_SOURCES}) -target_link_libraries(KWinX11Platform kwin KF5::Crash X11::X11) +target_link_libraries(KWinX11Platform kwin KF5::Crash X11::X11 XCB::XKB PkgConfig::XKBX11) if (QT_MAJOR_VERSION EQUAL "5") target_link_libraries(KWinX11Platform Qt::X11Extras) endif() diff --git a/src/backends/x11/standalone/x11_standalone_effects.cpp b/src/backends/x11/standalone/x11_standalone_effects.cpp index 35230a2e45..170141909e 100644 --- a/src/backends/x11/standalone/x11_standalone_effects.cpp +++ b/src/backends/x11/standalone/x11_standalone_effects.cpp @@ -12,9 +12,10 @@ #include "screenedge.h" #include "utils/common.h" #include "workspace.h" +#include "x11_standalone_effects_keyboard_interception_filter.h" #include "x11_standalone_effects_mouse_interception_filter.h" - -#include +#include "x11_standalone_keyboard.h" +#include "x11_standalone_platform.h" namespace KWin { @@ -43,21 +44,22 @@ EffectsHandlerImplX11::~EffectsHandlerImplX11() bool EffectsHandlerImplX11::doGrabKeyboard() { + auto keyboard = static_cast(kwinApp()->platform())->keyboard(); + if (!keyboard->xkbKeymap()) { + return false; + } bool ret = grabXKeyboard(); if (!ret) { return false; } - // Workaround for Qt 5.9 regression introduced with 2b34aefcf02f09253473b096eb4faffd3e62b5f4 - // we no longer get any events for the root window, one needs to call winId() on the desktop window - // TODO: change effects event handling to create the appropriate QKeyEvent without relying on Qt - // as it's done already in the Wayland case. - qApp->desktop()->winId(); + m_x11KeyboardInterception = std::make_unique(this, keyboard); return ret; } void EffectsHandlerImplX11::doUngrabKeyboard() { ungrabXKeyboard(); + m_x11KeyboardInterception.reset(); } void EffectsHandlerImplX11::doStartMouseInterception(Qt::CursorShape shape) diff --git a/src/backends/x11/standalone/x11_standalone_effects.h b/src/backends/x11/standalone/x11_standalone_effects.h index 8ed50bd95c..957e70bb10 100644 --- a/src/backends/x11/standalone/x11_standalone_effects.h +++ b/src/backends/x11/standalone/x11_standalone_effects.h @@ -18,6 +18,7 @@ namespace KWin { class EffectsMouseInterceptionX11Filter; +class EffectsKeyboardInterceptionX11Filter; class EffectsHandlerImplX11 : public EffectsHandlerImpl { @@ -40,6 +41,7 @@ protected: private: Xcb::Window m_mouseInterceptionWindow; std::unique_ptr m_x11MouseInterception; + std::unique_ptr m_x11KeyboardInterception; }; } diff --git a/src/backends/x11/standalone/x11_standalone_effects_keyboard_interception_filter.cpp b/src/backends/x11/standalone/x11_standalone_effects_keyboard_interception_filter.cpp new file mode 100644 index 0000000000..9e11c1a7fb --- /dev/null +++ b/src/backends/x11/standalone/x11_standalone_effects_keyboard_interception_filter.cpp @@ -0,0 +1,61 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "x11_standalone_effects_keyboard_interception_filter.h" +#include "x11_standalone_effects.h" +#include "x11_standalone_keyboard.h" + +#include +#include +#include + +namespace KWin +{ + +EffectsKeyboardInterceptionX11Filter::EffectsKeyboardInterceptionX11Filter(EffectsHandlerImpl *effects, X11Keyboard *keyboard) + : X11EventFilter(QVector{XCB_KEY_PRESS, XCB_KEY_RELEASE}) + , m_effects(effects) + , m_keyboard(keyboard) +{ +} + +bool EffectsKeyboardInterceptionX11Filter::event(xcb_generic_event_t *event) +{ + switch (event->response_type & ~0x80) { + case XCB_KEY_PRESS: { + const auto keyEvent = reinterpret_cast(event); + processKey(true, keyEvent->detail, keyEvent->time); + return true; + } + case XCB_KEY_RELEASE: { + const auto keyEvent = reinterpret_cast(event); + processKey(false, keyEvent->detail, keyEvent->time); + return true; + } + default: + return false; + } +} + +void EffectsKeyboardInterceptionX11Filter::processKey(bool press, xcb_keycode_t keycode, xcb_timestamp_t timestamp) +{ + const xkb_keysym_t keysym = xkb_state_key_get_one_sym(m_keyboard->xkbState(), keycode); + + Qt::KeyboardModifiers modifiers = m_keyboard->modifiers(); + if (QXkbCommon::isKeypad(keysym)) { + modifiers |= Qt::KeypadModifier; + } + + const int qtKey = QXkbCommon::keysymToQtKey(keysym, modifiers, m_keyboard->xkbState(), keycode); + const QString text = QXkbCommon::lookupString(m_keyboard->xkbState(), keycode); + + QKeyEvent keyEvent(press ? QEvent::KeyPress : QEvent::KeyRelease, qtKey, modifiers, text); + keyEvent.setTimestamp(timestamp); + + m_effects->grabbedKeyboardEvent(&keyEvent); +} + +} // namespace KWin diff --git a/src/backends/x11/standalone/x11_standalone_effects_keyboard_interception_filter.h b/src/backends/x11/standalone/x11_standalone_effects_keyboard_interception_filter.h new file mode 100644 index 0000000000..a80545242e --- /dev/null +++ b/src/backends/x11/standalone/x11_standalone_effects_keyboard_interception_filter.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "x11eventfilter.h" + +namespace KWin +{ + +class EffectsHandlerImpl; +class X11Keyboard; + +class EffectsKeyboardInterceptionX11Filter : public X11EventFilter +{ +public: + explicit EffectsKeyboardInterceptionX11Filter(EffectsHandlerImpl *effects, X11Keyboard *keyboard); + + bool event(xcb_generic_event_t *event) override; + +private: + void processKey(bool press, xcb_keycode_t keycode, xcb_timestamp_t timestamp); + + EffectsHandlerImpl *m_effects; + X11Keyboard *m_keyboard; +}; + +} // namespace KWin diff --git a/src/backends/x11/standalone/x11_standalone_keyboard.cpp b/src/backends/x11/standalone/x11_standalone_keyboard.cpp new file mode 100644 index 0000000000..7e0b224b8e --- /dev/null +++ b/src/backends/x11/standalone/x11_standalone_keyboard.cpp @@ -0,0 +1,224 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "x11_standalone_keyboard.h" + +#include "main.h" + +#include + +#define explicit dont_use_cxx_explicit +#include +#undef explicit +#include + +namespace KWin +{ + +class X11KeyboardFilter : public X11EventFilter +{ +public: + X11KeyboardFilter(X11Keyboard *kbd, int eventType) + : X11EventFilter(eventType) + , m_kbd(kbd) + { + } + + bool event(xcb_generic_event_t *event) override + { + return m_kbd->event(event); + } + +private: + X11Keyboard *m_kbd; +}; + +X11Keyboard::X11Keyboard() + : m_xkbContext(xkb_context_new(XKB_CONTEXT_NO_FLAGS)) +{ + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(kwinApp()->x11Connection(), &xcb_xkb_id); + if (!reply || !reply->present) { + qWarning() << "XKeyboard extension is unavailable"; + return; + } + + m_deviceId = xkb_x11_get_core_keyboard_device_id(kwinApp()->x11Connection()); + if (m_deviceId == -1) { + qWarning() << "xkb_x11_get_core_keyboard_device_id() failed"; + return; + } + + enum { + requiredEvents = + (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY + | XCB_XKB_EVENT_TYPE_MAP_NOTIFY + | XCB_XKB_EVENT_TYPE_STATE_NOTIFY), + + requiredNknDetails = + (XCB_XKB_NKN_DETAIL_KEYCODES), + + requiredMapParts = + (XCB_XKB_MAP_PART_KEY_TYPES + | XCB_XKB_MAP_PART_KEY_SYMS + | XCB_XKB_MAP_PART_MODIFIER_MAP + | XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS + | XCB_XKB_MAP_PART_KEY_ACTIONS + | XCB_XKB_MAP_PART_VIRTUAL_MODS + | XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP), + + requiredStateDetails = + (XCB_XKB_STATE_PART_MODIFIER_BASE + | XCB_XKB_STATE_PART_MODIFIER_LATCH + | XCB_XKB_STATE_PART_MODIFIER_LOCK + | XCB_XKB_STATE_PART_GROUP_BASE + | XCB_XKB_STATE_PART_GROUP_LATCH + | XCB_XKB_STATE_PART_GROUP_LOCK), + }; + + static const xcb_xkb_select_events_details_t details = { + .affectNewKeyboard = requiredNknDetails, + .newKeyboardDetails = requiredNknDetails, + .affectState = requiredStateDetails, + .stateDetails = requiredStateDetails, + }; + + xcb_void_cookie_t cookie = + xcb_xkb_select_events_aux_checked(kwinApp()->x11Connection(), + m_deviceId, + requiredEvents, + 0, + 0, + requiredMapParts, + requiredMapParts, + &details); + + xcb_generic_error_t *error = xcb_request_check(kwinApp()->x11Connection(), cookie); + if (error) { + free(error); + return; + } + + updateKeymap(); + + m_filter = std::make_unique(this, reply->first_event); +} + +X11Keyboard::~X11Keyboard() +{ + if (m_xkbState) { + xkb_state_unref(m_xkbState); + m_xkbState = nullptr; + } + if (m_xkbKeymap) { + xkb_keymap_unref(m_xkbKeymap); + m_xkbKeymap = nullptr; + } + if (m_xkbContext) { + xkb_context_unref(m_xkbContext); + m_xkbContext = nullptr; + } +} + +bool X11Keyboard::event(xcb_generic_event_t *gevent) +{ + union xkb_event { + struct + { + uint8_t response_type; + uint8_t xkbType; + uint16_t sequence; + xcb_timestamp_t time; + uint8_t deviceID; + } any; + xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify; + xcb_xkb_map_notify_event_t map_notify; + xcb_xkb_state_notify_event_t state_notify; + } *event = reinterpret_cast(gevent); + + if (event->any.deviceID == m_deviceId) { + switch (event->any.xkbType) { + case XCB_XKB_NEW_KEYBOARD_NOTIFY: + if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES) { + updateKeymap(); + } + break; + + case XCB_XKB_MAP_NOTIFY: + updateKeymap(); + break; + + case XCB_XKB_STATE_NOTIFY: + xkb_state_update_mask(m_xkbState, + event->state_notify.baseMods, + event->state_notify.latchedMods, + event->state_notify.lockedMods, + event->state_notify.baseGroup, + event->state_notify.latchedGroup, + event->state_notify.lockedGroup); + break; + } + } + + return false; +} + +void X11Keyboard::updateKeymap() +{ + xkb_keymap *keymap = xkb_x11_keymap_new_from_device(m_xkbContext, kwinApp()->x11Connection(), m_deviceId, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + qWarning() << "xkb_x11_keymap_new_from_device() failed"; + return; + } + + xkb_state *state = xkb_x11_state_new_from_device(keymap, kwinApp()->x11Connection(), m_deviceId); + if (!state) { + xkb_keymap_unref(keymap); + qWarning() << "xkb_x11_state_new_from_device() failed"; + return; + } + + if (m_xkbState) { + xkb_state_unref(m_xkbState); + } + if (m_xkbKeymap) { + xkb_keymap_unref(m_xkbKeymap); + } + + m_xkbState = state; + m_xkbKeymap = keymap; +} + +xkb_keymap *X11Keyboard::xkbKeymap() const +{ + return m_xkbKeymap; +} + +xkb_state *X11Keyboard::xkbState() const +{ + return m_xkbState; +} + +Qt::KeyboardModifiers X11Keyboard::modifiers() const +{ + Qt::KeyboardModifiers mods; + + if (xkb_state_mod_name_is_active(m_xkbState, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) == 1 || xkb_state_mod_name_is_active(m_xkbState, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_EFFECTIVE) == 1) { + mods |= Qt::ShiftModifier; + } + if (xkb_state_mod_name_is_active(m_xkbState, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) == 1) { + mods |= Qt::AltModifier; + } + if (xkb_state_mod_name_is_active(m_xkbState, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) == 1) { + mods |= Qt::ControlModifier; + } + if (xkb_state_mod_name_is_active(m_xkbState, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) == 1) { + mods |= Qt::MetaModifier; + } + + return mods; +} + +} // namespace KWin diff --git a/src/backends/x11/standalone/x11_standalone_keyboard.h b/src/backends/x11/standalone/x11_standalone_keyboard.h new file mode 100644 index 0000000000..9040d5609a --- /dev/null +++ b/src/backends/x11/standalone/x11_standalone_keyboard.h @@ -0,0 +1,43 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "x11eventfilter.h" + +#include + +#include + +namespace KWin +{ + +class X11KeyboardFilter; + +class X11Keyboard +{ +public: + X11Keyboard(); + ~X11Keyboard(); + + xkb_keymap *xkbKeymap() const; + xkb_state *xkbState() const; + Qt::KeyboardModifiers modifiers() const; + + bool event(xcb_generic_event_t *event); + +private: + void updateKeymap(); + + xkb_context *m_xkbContext = nullptr; + xkb_keymap *m_xkbKeymap = nullptr; + xkb_state *m_xkbState = nullptr; + int32_t m_deviceId = 0; + + std::unique_ptr m_filter; +}; + +} // namespace KWin diff --git a/src/backends/x11/standalone/x11_standalone_platform.cpp b/src/backends/x11/standalone/x11_standalone_platform.cpp index 9bc241563e..0a1a2235fa 100644 --- a/src/backends/x11/standalone/x11_standalone_platform.cpp +++ b/src/backends/x11/standalone/x11_standalone_platform.cpp @@ -33,6 +33,7 @@ #include "workspace.h" #include "x11_standalone_effects.h" #include "x11_standalone_egl_backend.h" +#include "x11_standalone_keyboard.h" #include "x11_standalone_logging.h" #include "x11_standalone_non_composited_outline.h" #include "x11_standalone_output.h" @@ -121,6 +122,8 @@ X11StandalonePlatform::X11StandalonePlatform(QObject *parent) m_updateOutputsTimer->setSingleShot(true); connect(m_updateOutputsTimer.get(), &QTimer::timeout, this, &X11StandalonePlatform::updateOutputs); + m_keyboard = std::make_unique(); + setSupportsGammaControl(true); } @@ -646,6 +649,11 @@ Outputs X11StandalonePlatform::outputs() const return m_outputs; } +X11Keyboard *X11StandalonePlatform::keyboard() const +{ + return m_keyboard.get(); +} + RenderLoop *X11StandalonePlatform::renderLoop() const { return m_renderLoop.get(); diff --git a/src/backends/x11/standalone/x11_standalone_platform.h b/src/backends/x11/standalone/x11_standalone_platform.h index 7696360b14..866e949ec7 100644 --- a/src/backends/x11/standalone/x11_standalone_platform.h +++ b/src/backends/x11/standalone/x11_standalone_platform.h @@ -26,6 +26,7 @@ class XInputIntegration; class WindowSelector; class X11EventFilter; class X11Output; +class X11Keyboard; class KWIN_EXPORT X11StandalonePlatform : public Platform { @@ -63,6 +64,7 @@ public: void scheduleUpdateOutputs(); void updateOutputs(); + X11Keyboard *keyboard() const; RenderLoop *renderLoop() const; Outputs outputs() const override; @@ -92,6 +94,7 @@ private: std::unique_ptr m_windowSelector; std::unique_ptr m_screenEdgesFilter; std::unique_ptr m_randrEventFilter; + std::unique_ptr m_keyboard; std::unique_ptr m_renderLoop; QVector m_outputs; }; diff --git a/src/events.cpp b/src/events.cpp index 495193329d..1a7b79ffa0 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -148,10 +148,6 @@ static xcb_window_t findEventWindow(xcb_generic_event_t *event) bool Workspace::workspaceEvent(xcb_generic_event_t *e) { const uint8_t eventType = e->response_type & ~0x80; - if (effects && static_cast(effects)->hasKeyboardGrab() - && (eventType == XCB_KEY_PRESS || eventType == XCB_KEY_RELEASE)) { - return false; // let Qt process it, it'll be intercepted again in eventFilter() - } const xcb_window_t eventWindow = findEventWindow(e); if (eventWindow != XCB_WINDOW_NONE) { @@ -311,19 +307,6 @@ bool Workspace::workspaceEvent(xcb_generic_event_t *e) return false; } -// Used only to filter events that need to be processed by Qt first -// (e.g. keyboard input to be composed), otherwise events are -// handle by the XEvent filter above -bool Workspace::workspaceEvent(QEvent *e) -{ - if ((e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride) - && effects && static_cast(effects)->hasKeyboardGrab()) { - static_cast(effects)->grabbedKeyboardEvent(static_cast(e)); - return true; - } - return false; -} - // **************************************** // Client // **************************************** diff --git a/src/main_x11.cpp b/src/main_x11.cpp index 086ba79dba..ddbd2d60e4 100644 --- a/src/main_x11.cpp +++ b/src/main_x11.cpp @@ -259,14 +259,6 @@ void ApplicationX11::performStartup() createTabletModeManager(); } -bool ApplicationX11::notify(QObject *o, QEvent *e) -{ - if (e->spontaneous() && Workspace::self()->workspaceEvent(e)) { - return true; - } - return QApplication::notify(o, e); -} - void ApplicationX11::setupCrashHandler() { KCrash::setEmergencySaveFunction(ApplicationX11::crashHandler); diff --git a/src/main_x11.h b/src/main_x11.h index ad9709bb83..54f8a86e2e 100644 --- a/src/main_x11.h +++ b/src/main_x11.h @@ -26,7 +26,6 @@ public: protected: void performStartup() override; - bool notify(QObject *o, QEvent *e) override; private Q_SLOTS: void lostSelection(); diff --git a/src/workspace.h b/src/workspace.h index 7d58b6f427..b4637fce09 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -93,7 +93,6 @@ public: } bool workspaceEvent(xcb_generic_event_t *); - bool workspaceEvent(QEvent *); bool hasWindow(const Window *);