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.
master
Vlad Zahorodnii 2 years ago
parent 7aee88581f
commit bcd43ff44d

@ -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)

@ -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()

@ -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 <QDesktopWidget>
#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<X11StandalonePlatform *>(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<EffectsKeyboardInterceptionX11Filter>(this, keyboard);
return ret;
}
void EffectsHandlerImplX11::doUngrabKeyboard()
{
ungrabXKeyboard();
m_x11KeyboardInterception.reset();
}
void EffectsHandlerImplX11::doStartMouseInterception(Qt::CursorShape shape)

@ -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<EffectsMouseInterceptionX11Filter> m_x11MouseInterception;
std::unique_ptr<EffectsKeyboardInterceptionX11Filter> m_x11KeyboardInterception;
};
}

@ -0,0 +1,61 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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 <QDebug>
#include <QKeyEvent>
#include <QtXkbCommonSupport/private/qxkbcommon_p.h>
namespace KWin
{
EffectsKeyboardInterceptionX11Filter::EffectsKeyboardInterceptionX11Filter(EffectsHandlerImpl *effects, X11Keyboard *keyboard)
: X11EventFilter(QVector<int>{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<xcb_key_press_event_t *>(event);
processKey(true, keyEvent->detail, keyEvent->time);
return true;
}
case XCB_KEY_RELEASE: {
const auto keyEvent = reinterpret_cast<xcb_key_release_event_t *>(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

@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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

@ -0,0 +1,224 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "x11_standalone_keyboard.h"
#include "main.h"
#include <QDebug>
#define explicit dont_use_cxx_explicit
#include <xcb/xkb.h>
#undef explicit
#include <xkbcommon/xkbcommon-x11.h>
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<X11KeyboardFilter>(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<union xkb_event *>(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

@ -0,0 +1,43 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "x11eventfilter.h"
#include <xkbcommon/xkbcommon.h>
#include <memory>
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<X11KeyboardFilter> m_filter;
};
} // namespace KWin

@ -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<X11Keyboard>();
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();

@ -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<WindowSelector> m_windowSelector;
std::unique_ptr<X11EventFilter> m_screenEdgesFilter;
std::unique_ptr<X11EventFilter> m_randrEventFilter;
std::unique_ptr<X11Keyboard> m_keyboard;
std::unique_ptr<RenderLoop> m_renderLoop;
QVector<Output *> m_outputs;
};

@ -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<EffectsHandlerImpl *>(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<EffectsHandlerImpl *>(effects)->hasKeyboardGrab()) {
static_cast<EffectsHandlerImpl *>(effects)->grabbedKeyboardEvent(static_cast<QKeyEvent *>(e));
return true;
}
return false;
}
// ****************************************
// Client
// ****************************************

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

@ -26,7 +26,6 @@ public:
protected:
void performStartup() override;
bool notify(QObject *o, QEvent *e) override;
private Q_SLOTS:
void lostSelection();

@ -93,7 +93,6 @@ public:
}
bool workspaceEvent(xcb_generic_event_t *);
bool workspaceEvent(QEvent *);
bool hasWindow(const Window *);

Loading…
Cancel
Save