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 *);