wayland: Move XWayland key forwarding into a filter
We optionally send some keys to xwayland through the filter when no x11 client has focus. This allows shortcut handling in X11 apps to work. When kwin is grabbing keys we don't necessarily want X11 to sniff these keys as things can get out of sync. A key place is the tabbox. The X11 client sill has focus, but xwayland is not active. This means we pass tab keys to X which then go to application incorrectly. Part of this patch changes the tabbox filter to not intercept the alt key release event. This ensures xwayland's concept of pressed modifiers stays in sync. BUG: 484992
This commit is contained in:
parent
15538c303b
commit
8fd4476ff1
5 changed files with 90 additions and 51 deletions
|
@ -56,6 +56,7 @@ private Q_SLOTS:
|
||||||
void onlyModifier();
|
void onlyModifier();
|
||||||
void letterWithModifier();
|
void letterWithModifier();
|
||||||
void testWaylandWindowHasFocus();
|
void testWaylandWindowHasFocus();
|
||||||
|
void tabBox();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<KeyAction> recievedX11EventsForInput(const QList<KeyAction> &keyEventsIn);
|
QList<KeyAction> recievedX11EventsForInput(const QList<KeyAction> &keyEventsIn);
|
||||||
|
@ -232,6 +233,41 @@ void X11KeyReadTest::testWaylandWindowHasFocus()
|
||||||
QCOMPARE(modifierSpy.last()[3], 0);
|
QCOMPARE(modifierSpy.last()[3], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void X11KeyReadTest::tabBox()
|
||||||
|
{
|
||||||
|
// Note this test will fail if you forget to use dbus-run-session!
|
||||||
|
#if KWIN_BUILD_TABBOX
|
||||||
|
QList<KeyAction> keyEvents = {
|
||||||
|
{State::Press, KEY_LEFTALT},
|
||||||
|
{State::Press, KEY_TAB},
|
||||||
|
{State::Release, KEY_TAB},
|
||||||
|
{State::Press, KEY_TAB},
|
||||||
|
{State::Release, KEY_TAB},
|
||||||
|
{State::Release, KEY_LEFTALT},
|
||||||
|
};
|
||||||
|
auto received = recievedX11EventsForInput(keyEvents);
|
||||||
|
|
||||||
|
QList<KeyAction> expected;
|
||||||
|
QFETCH_GLOBAL(XwaylandEavesdropsMode, operatingMode);
|
||||||
|
switch (operatingMode) {
|
||||||
|
case XwaylandEavesdropsMode::None:
|
||||||
|
expected = {};
|
||||||
|
break;
|
||||||
|
// even though tab is a regular key whilst holding alt, the tab switcher should be grabbing
|
||||||
|
case XwaylandEavesdropsMode::AllKeysWithModifier:
|
||||||
|
case XwaylandEavesdropsMode::NonCharacterKeys:
|
||||||
|
case XwaylandEavesdropsMode::All:
|
||||||
|
expected = {
|
||||||
|
{State::Press, KEY_LEFTALT},
|
||||||
|
{State::Release, KEY_LEFTALT},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(received, expected);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
class X11EventRecorder :
|
class X11EventRecorder :
|
||||||
public
|
public
|
||||||
QObject
|
QObject
|
||||||
|
|
|
@ -1666,6 +1666,7 @@ public:
|
||||||
workspace()->tabbox()->keyPress(event->modifiers() | event->key());
|
workspace()->tabbox()->keyPress(event->modifiers() | event->key());
|
||||||
} else if (static_cast<KeyEvent *>(event)->modifiersRelevantForGlobalShortcuts() == Qt::NoModifier) {
|
} else if (static_cast<KeyEvent *>(event)->modifiersRelevantForGlobalShortcuts() == Qt::NoModifier) {
|
||||||
workspace()->tabbox()->modifiersReleased();
|
workspace()->tabbox()->modifiersReleased();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -381,6 +381,7 @@ enum Order {
|
||||||
Popup,
|
Popup,
|
||||||
Decoration,
|
Decoration,
|
||||||
WindowAction,
|
WindowAction,
|
||||||
|
XWayland,
|
||||||
InternalWindow,
|
InternalWindow,
|
||||||
InputMethod,
|
InputMethod,
|
||||||
Forward,
|
Forward,
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
#include "xwldrophandler.h"
|
#include "xwldrophandler.h"
|
||||||
|
|
||||||
#include "core/output.h"
|
#include "core/output.h"
|
||||||
#include "input_event_spy.h"
|
|
||||||
#include "keyboard_input.h"
|
#include "keyboard_input.h"
|
||||||
#include "main_wayland.h"
|
#include "main_wayland.h"
|
||||||
#include "utils/common.h"
|
#include "utils/common.h"
|
||||||
|
@ -82,32 +81,32 @@ bool XrandrEventFilter::event(xcb_generic_event_t *event)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
class XwaylandInputSpy : public QObject, public KWin::InputEventSpy
|
class XwaylandInputFilter : public QObject, public KWin::InputEventFilter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
XwaylandInputSpy()
|
XwaylandInputFilter()
|
||||||
|
: KWin::InputEventFilter(InputFilterOrder::XWayland)
|
||||||
{
|
{
|
||||||
connect(waylandServer()->seat(), &SeatInterface::focusedKeyboardSurfaceAboutToChange,
|
connect(waylandServer()->seat(), &SeatInterface::focusedKeyboardSurfaceAboutToChange,
|
||||||
this, [this](SurfaceInterface *newSurface) {
|
this, [this](SurfaceInterface *newSurface) {
|
||||||
auto keyboard = waylandServer()->seat()->keyboard();
|
auto keyboard = waylandServer()->seat()->keyboard();
|
||||||
if (!newSurface) {
|
if (!newSurface) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waylandServer()->xWaylandConnection() == newSurface->client()) {
|
if (waylandServer()->xWaylandConnection() == newSurface->client()) {
|
||||||
// Since this is a spy but the keyboard interface gets its normal sendKey calls through filters,
|
// Since this is in the filter chain some key events may have been filtered out
|
||||||
// there can be a mismatch in both states.
|
// This loop makes sure all key press events are reset before we switch back to the
|
||||||
// This loop makes sure all key press events are reset before we switch back to the
|
// Xwayland client and the state is correctly restored.
|
||||||
// Xwayland client and the state is correctly restored.
|
for (auto it = m_states.constBegin(); it != m_states.constEnd(); ++it) {
|
||||||
for (auto it = m_states.constBegin(); it != m_states.constEnd(); ++it) {
|
if (it.value() == KeyboardKeyState::Pressed) {
|
||||||
if (it.value() == KeyboardKeyState::Pressed) {
|
keyboard->sendKey(it.key(), KeyboardKeyState::Released, waylandServer()->xWaylandConnection());
|
||||||
keyboard->sendKey(it.key(), KeyboardKeyState::Released, waylandServer()->xWaylandConnection());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_modifiers = {};
|
|
||||||
m_states.clear();
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
m_modifiers = {};
|
||||||
|
m_states.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMode(XwaylandEavesdropsMode mode, bool eavesdropsMouse)
|
void setMode(XwaylandEavesdropsMode mode, bool eavesdropsMouse)
|
||||||
|
@ -368,35 +367,33 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void keyEvent(KWin::KeyEvent *event) override
|
bool keyEvent(KWin::KeyEvent *event) override
|
||||||
{
|
{
|
||||||
|
ClientConnection *xwaylandClient = waylandServer()->xWaylandConnection();
|
||||||
|
if (!xwaylandClient) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (event->isAutoRepeat()) {
|
if (event->isAutoRepeat()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Window *window = workspace()->activeWindow();
|
if (!m_filterKey || !m_filterKey(event->key(), event->modifiers())) {
|
||||||
if (!m_filterKey || !m_filterKey(event->key(), event->modifiers()) || (window && window->isLockScreen())) {
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto keyboard = waylandServer()->seat()->keyboard();
|
auto keyboard = waylandServer()->seat()->keyboard();
|
||||||
auto surface = keyboard->focusedSurface();
|
auto surface = keyboard->focusedSurface();
|
||||||
ClientConnection *xwaylandClient = waylandServer()->xWaylandConnection();
|
|
||||||
|
|
||||||
if (!xwaylandClient) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (surface) {
|
if (surface) {
|
||||||
ClientConnection *client = surface->client();
|
ClientConnection *client = surface->client();
|
||||||
if (xwaylandClient == client) {
|
if (xwaylandClient == client) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardKeyState state{event->type() == QEvent::KeyPress};
|
KeyboardKeyState state{event->type() == QEvent::KeyPress};
|
||||||
if (!updateKey(event->nativeScanCode(), state)) {
|
if (!updateKey(event->nativeScanCode(), state)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto xkb = input()->keyboard()->xkb();
|
auto xkb = input()->keyboard()->xkb();
|
||||||
|
@ -421,7 +418,7 @@ public:
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboard->sendModifiers(xkb->modifierState().depressed,
|
keyboard->sendModifiers(xkb->modifierState().depressed,
|
||||||
|
@ -429,31 +426,36 @@ public:
|
||||||
xkb->modifierState().locked,
|
xkb->modifierState().locked,
|
||||||
xkb->currentLayout(),
|
xkb->currentLayout(),
|
||||||
xwaylandClient);
|
xwaylandClient);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pointerEvent(KWin::MouseEvent *event) override
|
bool pointerEvent(KWin::MouseEvent *event, quint32 nativeButton) override
|
||||||
{
|
{
|
||||||
Window *window = workspace()->activeWindow();
|
|
||||||
if (!m_filterMouse || (window && window->isLockScreen())) {
|
ClientConnection *xwaylandClient = waylandServer()->xWaylandConnection();
|
||||||
return;
|
if (!xwaylandClient) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!m_filterMouse) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease) {
|
if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pointer = waylandServer()->seat()->pointer();
|
auto pointer = waylandServer()->seat()->pointer();
|
||||||
auto surface = pointer->focusedSurface();
|
auto surface = pointer->focusedSurface();
|
||||||
ClientConnection *xwaylandClient = waylandServer()->xWaylandConnection();
|
|
||||||
|
|
||||||
if (surface) {
|
if (surface) {
|
||||||
ClientConnection *client = surface->client();
|
ClientConnection *client = surface->client();
|
||||||
if (xwaylandClient && xwaylandClient == client) {
|
if (xwaylandClient && xwaylandClient == client) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PointerButtonState state{event->type() == QEvent::MouseButtonPress};
|
PointerButtonState state{event->type() == QEvent::MouseButtonPress};
|
||||||
pointer->sendButton(event->nativeButton(), state, xwaylandClient);
|
pointer->sendButton(event->nativeButton(), state, xwaylandClient);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool updateKey(quint32 key, KeyboardKeyState state)
|
bool updateKey(quint32 key, KeyboardKeyState state)
|
||||||
|
@ -583,7 +585,7 @@ void Xwayland::handleXwaylandFinished()
|
||||||
m_compositingManagerSelectionOwner.reset();
|
m_compositingManagerSelectionOwner.reset();
|
||||||
m_windowManagerSelectionOwner.reset();
|
m_windowManagerSelectionOwner.reset();
|
||||||
|
|
||||||
m_inputSpy.reset();
|
m_inputFilter.reset();
|
||||||
disconnect(options, &Options::xwaylandEavesdropsChanged, this, &Xwayland::refreshEavesdropping);
|
disconnect(options, &Options::xwaylandEavesdropsChanged, this, &Xwayland::refreshEavesdropping);
|
||||||
disconnect(options, &Options::xwaylandEavesdropsMouseChanged, this, &Xwayland::refreshEavesdropping);
|
disconnect(options, &Options::xwaylandEavesdropsMouseChanged, this, &Xwayland::refreshEavesdropping);
|
||||||
|
|
||||||
|
@ -632,20 +634,19 @@ void Xwayland::refreshEavesdropping()
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool enabled = options->xwaylandEavesdrops() != None;
|
const bool enabled = options->xwaylandEavesdrops() != None;
|
||||||
if (enabled == bool(m_inputSpy)) {
|
if (enabled == bool(m_inputFilter)) {
|
||||||
if (m_inputSpy) {
|
if (m_inputFilter) {
|
||||||
m_inputSpy->setMode(options->xwaylandEavesdrops(), options->xwaylandEavesdropsMouse());
|
m_inputFilter->setMode(options->xwaylandEavesdrops(), options->xwaylandEavesdropsMouse());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
m_inputSpy = std::make_unique<XwaylandInputSpy>();
|
m_inputFilter = std::make_unique<XwaylandInputFilter>();
|
||||||
input()->installInputEventSpy(m_inputSpy.get());
|
m_inputFilter->setMode(options->xwaylandEavesdrops(), options->xwaylandEavesdropsMouse());
|
||||||
m_inputSpy->setMode(options->xwaylandEavesdrops(), options->xwaylandEavesdropsMouse());
|
input()->installInputEventFilter(m_inputFilter.get());
|
||||||
} else {
|
} else {
|
||||||
input()->uninstallInputEventSpy(m_inputSpy.get());
|
m_inputFilter.reset();
|
||||||
m_inputSpy.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ class Application;
|
||||||
namespace Xwl
|
namespace Xwl
|
||||||
{
|
{
|
||||||
class XrandrEventFilter;
|
class XrandrEventFilter;
|
||||||
class XwaylandInputSpy;
|
class XwaylandInputFilter;
|
||||||
class XwaylandLauncher;
|
class XwaylandLauncher;
|
||||||
class DataBridge;
|
class DataBridge;
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ private:
|
||||||
|
|
||||||
XrandrEventFilter *m_xrandrEventsFilter = nullptr;
|
XrandrEventFilter *m_xrandrEventsFilter = nullptr;
|
||||||
XwaylandLauncher *m_launcher;
|
XwaylandLauncher *m_launcher;
|
||||||
std::unique_ptr<XwaylandInputSpy> m_inputSpy;
|
std::unique_ptr<XwaylandInputFilter> m_inputFilter;
|
||||||
|
|
||||||
Q_DISABLE_COPY(Xwayland)
|
Q_DISABLE_COPY(Xwayland)
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue