Refactor input event handling to be based on filters

The main motivation of this change is to remove the spaghetti code
in the input event handling. Each area of processing (e.g. lock screen)
is moved into a dedicated event filter. Processing the events now just
means calling a virtual method on each of the filters. As soon as the
method returns true, the processing is stopped.

This allows to have the security for the lock screen just in one place:
whenever the screen is locked the event filter can ensure that the events
are not further processed.

Currently all event filters are implemented directly in input.cpp and
are registered by InputRedirection itself. In future it would be better
to have those moved to the area they belong to and get registered from
there. E.g. the input filter for EffectsHandlerImpl should be created
by EffectsHandlerImpl. This requires an improved API to ensure that the
filters are installed in the correct sequence.
This commit is contained in:
Martin Gräßlin 2016-02-03 15:17:49 +01:00
parent 5e6f0b8b4f
commit d02c325a61
2 changed files with 657 additions and 315 deletions

897
input.cpp
View file

@ -275,6 +275,498 @@ quint32 Xkb::getGroup()
return xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE);
}
InputEventFilter::InputEventFilter() = default;
InputEventFilter::~InputEventFilter()
{
if (input()) {
input()->uninstallInputEventFilter(this);
}
}
bool InputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton)
{
Q_UNUSED(event)
Q_UNUSED(nativeButton)
return false;
}
bool InputEventFilter::wheelEvent(QWheelEvent *event)
{
Q_UNUSED(event)
return false;
}
bool InputEventFilter::keyEvent(QKeyEvent *event)
{
Q_UNUSED(event)
return false;
}
bool InputEventFilter::touchDown(quint32 id, const QPointF &point, quint32 time)
{
Q_UNUSED(id)
Q_UNUSED(point)
Q_UNUSED(time)
return false;
}
bool InputEventFilter::touchMotion(quint32 id, const QPointF &point, quint32 time)
{
Q_UNUSED(id)
Q_UNUSED(point)
Q_UNUSED(time)
return false;
}
bool InputEventFilter::touchUp(quint32 id, quint32 time)
{
Q_UNUSED(id)
Q_UNUSED(time)
return false;
}
#if HAVE_INPUT
class VirtualTerminalFilter : public InputEventFilter {
public:
bool keyEvent(QKeyEvent *event) override {
// really on press and not on release? X11 switches on press.
if (event->type() == QEvent::KeyPress) {
const xkb_keysym_t keysym = event->nativeVirtualKey();
if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) {
VirtualTerminal::self()->activate(keysym - XKB_KEY_XF86Switch_VT_1 + 1);
return true;
}
}
return false;
}
};
#endif
class LockScreenFilter : public InputEventFilter {
public:
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override {
if (!waylandServer()->isScreenLocked()) {
return false;
}
auto seat = waylandServer()->seat();
seat->setTimestamp(event->timestamp());
if (event->type() == QEvent::MouseMove) {
Toplevel *t = input()->findToplevel(event->screenPos().toPoint());
if (t && t->surface()) {
seat->setFocusedPointerSurface(t->surface(), t->inputTransformation());
} else {
seat->setFocusedPointerSurface(nullptr);
}
seat->setPointerPos(event->screenPos().toPoint());
} else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) {
if (KWayland::Server::SurfaceInterface *s = seat->focusedPointerSurface()) {
Toplevel *t = waylandServer()->findClient(s);
if (t->isLockScreen() || t->isInputMethod()) {
event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton);
}
}
}
return true;
}
bool wheelEvent(QWheelEvent *event) override {
if (!waylandServer()->isScreenLocked()) {
return false;
}
auto seat = waylandServer()->seat();
if (KWayland::Server::SurfaceInterface *s = seat->focusedPointerSurface()) {
Toplevel *t = waylandServer()->findClient(s);
if (t->isLockScreen() || t->isInputMethod()) {
seat->setTimestamp(event->timestamp());
const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal;
seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y());
}
}
return true;
}
bool keyEvent(QKeyEvent * event) override {
if (!waylandServer()->isScreenLocked()) {
return false;
}
const ToplevelList &stacking = Workspace::self()->stackingOrder();
auto seat = waylandServer()->seat();
seat->setTimestamp(event->timestamp());
Toplevel *found = nullptr;
if (!stacking.isEmpty()) {
auto it = stacking.end();
do {
--it;
Toplevel *t = (*it);
if (t->isDeleted()) {
// a deleted window doesn't get mouse events
continue;
}
if (!t->isLockScreen()) {
continue;
}
if (!t->readyForPainting()) {
continue;
}
found = t;
break;
} while (it != stacking.begin());
}
// if we reach this point it's time to unset
seat->setFocusedKeyboardSurface(found ? found->surface() : nullptr);
switch (event->type()) {
case QEvent::KeyPress:
seat->keyPressed(event->nativeScanCode());
break;
case QEvent::KeyRelease:
seat->keyReleased(event->nativeScanCode());
break;
default:
break;
}
return true;
}
};
class EffectsFilter : public InputEventFilter {
public:
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override {
Q_UNUSED(nativeButton)
if (!effects) {
return false;
}
return static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowEvent(event);
}
bool keyEvent(QKeyEvent *event) override {
if (!effects || !static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) {
return false;
}
static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(event);
return true;
}
};
class MoveResizeFilter : public InputEventFilter {
public:
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override {
Q_UNUSED(nativeButton)
AbstractClient *c = workspace()->getMovingClient();
if (!c) {
return false;
}
switch (event->type()) {
case QEvent::MouseMove:
c->updateMoveResize(event->screenPos().toPoint());
break;
case QEvent::MouseButtonRelease:
if (event->buttons() == Qt::NoButton) {
c->endMoveResize();
}
break;
default:
break;
}
return true;
}
bool wheelEvent(QWheelEvent *event) override {
Q_UNUSED(event)
// filter out while moving a window
return workspace()->getMovingClient() != nullptr;
}
bool keyEvent(QKeyEvent *event) override {
AbstractClient *c = workspace()->getMovingClient();
if (!c) {
return false;
}
if (event->type() == QEvent::KeyPress) {
c->keyPressEvent(event->key() | event->modifiers());
if (c->isMove() || c->isResize()) {
// only update if mode didn't end
c->updateMoveResize(input()->globalPointer());
}
}
return true;
}
};
class GlobalShortcutFilter : public InputEventFilter {
public:
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override {
Q_UNUSED(nativeButton);
if (event->type() == QEvent::MouseButtonPress) {
if (input()->shortcuts()->processPointerPressed(event->modifiers(), event->buttons())) {
return true;
}
}
return false;
}
bool wheelEvent(QWheelEvent *event) override {
if (event->modifiers() == Qt::NoModifier) {
return false;
}
PointerAxisDirection direction = PointerAxisUp;
if (event->angleDelta().x() < 0) {
direction = PointerAxisRight;
} else if (event->angleDelta().x() > 0) {
direction = PointerAxisLeft;
} else if (event->angleDelta().y() < 0) {
direction = PointerAxisDown;
} else if (event->angleDelta().y() > 0) {
direction = PointerAxisUp;
}
return input()->shortcuts()->processAxis(event->modifiers(), direction);
}
bool keyEvent(QKeyEvent *event) override {
if (event->type() == QEvent::KeyPress) {
return input()->shortcuts()->processKey(event->modifiers(), event->nativeVirtualKey());
}
return false;
}
};
class InternalWindowEventFilter : public InputEventFilter {
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override {
Q_UNUSED(nativeButton)
auto internal = input()->m_pointerInternalWindow;
if (!internal) {
return false;
}
QMouseEvent e(event->type(),
event->pos() - internal->position(),
event->globalPos(),
event->button(), event->buttons(), event->modifiers());
e.setAccepted(false);
QCoreApplication::sendEvent(internal.data(), &e);
return e.isAccepted();
}
bool wheelEvent(QWheelEvent *event) override {
auto internal = input()->m_pointerInternalWindow;
if (!internal) {
return false;
}
const QPointF localPos = event->globalPosF() - QPointF(internal->x(), internal->y());
const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical;
const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y();
QWheelEvent e(localPos, event->globalPosF(), QPoint(),
event->angleDelta(),
delta,
orientation,
event->buttons(),
event->modifiers());
e.setAccepted(false);
QCoreApplication::sendEvent(internal.data(), &e);
return e.isAccepted();
}
bool keyEvent(QKeyEvent *event) override {
auto internal = input()->m_pointerInternalWindow;
if (!internal) {
return false;
}
event->setAccepted(false);
QCoreApplication::sendEvent(internal.data(), event);
return true;
}
};
class DecorationEventFilter : public InputEventFilter {
public:
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override {
Q_UNUSED(nativeButton)
auto decoration = input()->m_pointerDecoration;
if (!decoration) {
return false;
}
const QPointF p = event->globalPos() - decoration->client()->pos();
switch (event->type()) {
case QEvent::MouseMove: {
if (event->buttons() == Qt::NoButton) {
return false;
}
QHoverEvent e(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(decoration->decoration(), &e);
decoration->client()->processDecorationMove();
return true;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease: {
QMouseEvent e(event->type(), p, event->globalPos(), event->button(), event->buttons(), event->modifiers());
e.setAccepted(false);
QCoreApplication::sendEvent(decoration->decoration(), &e);
if (!e.isAccepted() && event->type() == QEvent::MouseButtonPress) {
decoration->client()->processDecorationButtonPress(&e);
}
if (event->type() == QEvent::MouseButtonRelease) {
decoration->client()->processDecorationButtonRelease(&e);
}
input()->installCursorFromDecoration();
return true;
}
default:
break;
}
return false;
}
bool wheelEvent(QWheelEvent *event) override {
auto decoration = input()->m_pointerDecoration;
if (!decoration) {
return false;
}
const QPointF localPos = event->globalPosF() - decoration->client()->pos();
const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical;
const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y();
QWheelEvent e(localPos, event->globalPosF(), QPoint(),
event->angleDelta(),
delta,
orientation,
event->buttons(),
event->modifiers());
e.setAccepted(false);
QCoreApplication::sendEvent(decoration.data(), &e);
if (e.isAccepted()) {
return true;
}
if (orientation == Qt::Vertical && decoration->decoration()->titleBar().contains(localPos.toPoint())) {
decoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1),
event->globalPosF().toPoint());
}
return true;
}
};
#ifdef KWIN_BUILD_TABBOX
class TabBoxInputFilter : public InputEventFilter
{
public:
bool keyEvent(QKeyEvent *event) override {
if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) {
return false;
}
TabBox::TabBox::self()->keyPress(event->modifiers() | event->key());
return true;
}
};
#endif
/**
* The remaining default input filter which forwards events to other windows
**/
class ForwardInputFilter : public InputEventFilter
{
public:
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override {
auto seat = waylandServer()->seat();
seat->setTimestamp(event->timestamp());
switch (event->type()) {
case QEvent::MouseMove:
if (event->buttons() == Qt::NoButton) {
// update pointer window only if no button is pressed
input()->updatePointerWindow();
}
seat->setPointerPos(event->globalPos());
break;
case QEvent::MouseButtonPress: {
bool passThrough = true;
if (AbstractClient *c = dynamic_cast<AbstractClient*>(input()->m_pointerWindow.data())) {
bool wasAction = false;
const Options::MouseCommand command = c->getMouseCommand(event->button(), &wasAction);
if (wasAction) {
passThrough = c->performMouseCommand(command, event->globalPos());
}
}
if (passThrough) {
seat->pointerButtonPressed(nativeButton);
}
break;
}
case QEvent::MouseButtonRelease:
seat->pointerButtonReleased(nativeButton);
if (event->buttons() == Qt::NoButton) {
input()->updatePointerWindow();
}
break;
default:
break;
}
return true;
}
bool wheelEvent(QWheelEvent *event) override {
auto seat = waylandServer()->seat();
seat->setTimestamp(event->timestamp());
const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal;
seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y());
return true;
}
bool keyEvent(QKeyEvent *event) override {
if (!workspace()) {
return false;
}
auto seat = waylandServer()->seat();
if (workspace()->activeClient() &&
(seat->focusedKeyboardSurface() != workspace()->activeClient()->surface())) {
seat->setFocusedKeyboardSurface(workspace()->activeClient()->surface());
}
seat->setTimestamp(event->timestamp());
switch (event->type()) {
case QEvent::KeyPress:
seat->keyPressed(event->nativeScanCode());
break;
case QEvent::KeyRelease:
seat->keyReleased(event->nativeScanCode());
break;
default:
break;
}
return true;
}
bool touchDown(quint32 id, const QPointF &pos, quint32 time) override {
if (!workspace()) {
return false;
}
auto seat = waylandServer()->seat();
seat->setTimestamp(time);
if (!seat->isTouchSequence()) {
input()->updateTouchWindow(pos);
if (AbstractClient *c = dynamic_cast<AbstractClient*>(input()->m_touchWindow.data())) {
// perform same handling as if it were a left click
bool wasAction = false;
const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction);
if (wasAction) {
// if no replay we filter out this touch point
if (!c->performMouseCommand(command, pos.toPoint())) {
return true;
}
}
}
}
input()->insertTouchId(id, seat->touchDown(pos));
return true;
}
bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override {
if (!workspace()) {
return false;
}
auto seat = waylandServer()->seat();
seat->setTimestamp(time);
const qint32 kwaylandId = input()->touchId(id);
if (kwaylandId != -1) {
seat->touchMove(kwaylandId, pos);
}
return true;
}
bool touchUp(quint32 id, quint32 time) override {
if (!workspace()) {
return false;
}
auto seat = waylandServer()->seat();
seat->setTimestamp(time);
const qint32 kwaylandId = input()->touchId(id);
if (kwaylandId != -1) {
seat->touchUp(kwaylandId);
input()->removeTouchId(id);
}
return true;
}
};
KWIN_SINGLETON_FACTORY(InputRedirection)
InputRedirection::InputRedirection(QObject *parent)
@ -302,6 +794,17 @@ InputRedirection::InputRedirection(QObject *parent)
InputRedirection::~InputRedirection()
{
s_self = NULL;
qDeleteAll(m_filters);
}
void InputRedirection::installInputEventFilter(InputEventFilter *filter)
{
m_filters << filter;
}
void InputRedirection::uninstallInputEventFilter(InputEventFilter *filter)
{
m_filters.removeAll(filter);
}
void InputRedirection::init()
@ -376,6 +879,30 @@ void InputRedirection::setupWorkspace()
);
connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure);
}
setupInputFilters();
}
void InputRedirection::setupInputFilters()
{
#if HAVE_INPUT
if (VirtualTerminal::self()) {
installInputEventFilter(new VirtualTerminalFilter);
}
#endif
if (waylandServer()) {
installInputEventFilter(new LockScreenFilter);
}
installInputEventFilter(new EffectsFilter);
installInputEventFilter(new MoveResizeFilter);
installInputEventFilter(new GlobalShortcutFilter);
#ifdef KWIN_BUILD_TABBOX
installInputEventFilter(new TabBoxInputFilter);
#endif
installInputEventFilter(new InternalWindowEventFilter);
installInputEventFilter(new DecorationEventFilter);
if (waylandServer()) {
installInputEventFilter(new ForwardInputFilter);
}
}
void InputRedirection::reconfigure()
@ -639,14 +1166,6 @@ void InputRedirection::updatePointerInternalWindow()
return;
}
}
if (m_pointerInternalWindow) {
// send mouse move
QMouseEvent event(QEvent::MouseMove,
m_globalPointer.toPoint() - m_pointerInternalWindow->position(),
m_globalPointer.toPoint(),
Qt::NoButton, qtButtonStates(), keyboardModifiers());
QCoreApplication::sendEvent(m_pointerInternalWindow.data(), &event);
}
}
void InputRedirection::pointerInternalWindowVisibilityChanged(bool visible)
@ -706,50 +1225,16 @@ void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time)
// const QPointF oldPos = m_globalPointer;
updatePointerPosition(pos);
if (waylandServer()->isScreenLocked()) {
Toplevel *t = findToplevel(m_globalPointer.toPoint());
if (t && t->surface()) {
if (auto seat = findSeat()) {
seat->setFocusedPointerSurface(t->surface(), t->inputTransformation());
seat->setTimestamp(time);
seat->setPointerPos(m_globalPointer);
}
} else {
if (auto seat = findSeat()) {
seat->setFocusedPointerSurface(nullptr);
seat->setTimestamp(time);
seat->setPointerPos(m_globalPointer);
}
}
return;
}
// TODO: check which part of KWin would like to intercept the event
QMouseEvent event(QEvent::MouseMove, m_globalPointer.toPoint(), m_globalPointer.toPoint(),
Qt::NoButton, qtButtonStates(), keyboardModifiers());
// check whether an effect has a mouse grab
if (effects && static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowEvent(&event)) {
// an effect grabbed the pointer, we do not forward the event to surfaces
return;
}
if (AbstractClient *c = workspace()->getMovingClient()) {
c->updateMoveResize(m_globalPointer);
} else {
QWeakPointer<Toplevel> old = m_pointerWindow;
if (!areButtonsPressed()) {
// update pointer window only if no button is pressed
updatePointerWindow();
} else if (m_pointerDecoration) {
const QPointF p = m_globalPointer - m_pointerDecoration->client()->pos();
QHoverEvent event(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(m_pointerDecoration->decoration(), &event);
m_pointerDecoration->client()->processDecorationMove();
event.setTimestamp(time);
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->pointerEvent(&event, 0)) {
return;
}
}
if (auto seat = findSeat()) {
seat->setTimestamp(time);
seat->setPointerPos(m_globalPointer);
}
}
void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time)
@ -762,86 +1247,13 @@ void InputRedirection::processPointerButton(uint32_t button, InputRedirection::P
QMouseEvent event(buttonStateToEvent(state), m_globalPointer.toPoint(), m_globalPointer.toPoint(),
buttonToQtMouseButton(button), qtButtonStates(), keyboardModifiers());
event.setTimestamp(time);
if (waylandServer()->isScreenLocked()) {
if (auto seat = findSeat()) {
KWayland::Server::SurfaceInterface *s = seat->focusedPointerSurface();
if (s) {
Toplevel *t = waylandServer()->findClient(s);
if (t->isLockScreen() || t->isInputMethod()) {
seat->setTimestamp(time);
state == PointerButtonPressed ? seat->pointerButtonPressed(button) : seat->pointerButtonReleased(button);
}
}
}
return;
}
// check whether an effect has a mouse grab
if (effects && static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowEvent(&event)) {
// an effect grabbed the pointer, we do not forward the event to surfaces
return;
}
if (AbstractClient *c = workspace()->getMovingClient()) {
if (state == KWin::InputRedirection::PointerButtonReleased) {
if (!areButtonsPressed()) {
c->endMoveResize();
}
}
return;
}
if (state == KWin::InputRedirection::PointerButtonPressed) {
if (m_shortcuts->processPointerPressed(m_xkb->modifiers(), qtButtonStates())) {
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->pointerEvent(&event, button)) {
return;
}
}
if (m_pointerInternalWindow) {
// send mouse move
QMouseEvent event(buttonStateToEvent(state),
m_globalPointer.toPoint() - m_pointerInternalWindow->position(),
m_globalPointer.toPoint(),
buttonToQtMouseButton(button), qtButtonStates(), keyboardModifiers());
event.setAccepted(false);
QCoreApplication::sendEvent(m_pointerInternalWindow.data(), &event);
}
if (m_pointerDecoration) {
const QPoint localPos = m_globalPointer.toPoint() - m_pointerDecoration->client()->pos();
QMouseEvent event(buttonStateToEvent(state),
localPos,
m_globalPointer.toPoint(),
buttonToQtMouseButton(button), qtButtonStates(), keyboardModifiers());
event.setAccepted(false);
QCoreApplication::sendEvent(m_pointerDecoration->decoration(), &event);
if (!event.isAccepted()) {
if (state == PointerButtonPressed) {
m_pointerDecoration->client()->processDecorationButtonPress(&event);
}
}
if (state == PointerButtonReleased) {
m_pointerDecoration->client()->processDecorationButtonRelease(&event);
}
installCursorFromDecoration();
}
// TODO: check which part of KWin would like to intercept the event
if (auto seat = findSeat()) {
seat->setTimestamp(time);
bool passThrough = true;
if (state == PointerButtonPressed) {
if (AbstractClient *c = dynamic_cast<AbstractClient*>(m_pointerWindow.data())) {
bool wasAction = false;
const Options::MouseCommand command = c->getMouseCommand(buttonToQtMouseButton(button), &wasAction);
if (wasAction) {
passThrough = c->performMouseCommand(command, m_globalPointer.toPoint());
}
}
}
if (passThrough) {
state == PointerButtonPressed ? seat->pointerButtonPressed(button) : seat->pointerButtonReleased(button);
}
}
if (state == PointerButtonReleased && !areButtonsPressed()) {
updatePointerWindow();
}
}
void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time)
@ -852,72 +1264,19 @@ void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qr
emit pointerAxisChanged(axis, delta);
if (waylandServer()->isScreenLocked()) {
if (auto seat = findSeat()) {
KWayland::Server::SurfaceInterface *s = seat->focusedPointerSurface();
if (s) {
Toplevel *t = waylandServer()->findClient(s);
if (t->isLockScreen() || t->isInputMethod()) {
seat->setTimestamp(time);
seat->pointerAxis(axis == InputRedirection::PointerAxisHorizontal ? Qt::Horizontal : Qt::Vertical, delta);
}
}
}
return;
}
QWheelEvent wheelEvent(m_globalPointer, m_globalPointer, QPoint(),
(axis == PointerAxisHorizontal) ? QPoint(delta, 0) : QPoint(0, delta),
delta,
(axis == PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical,
qtButtonStates(),
m_xkb->modifiers());
wheelEvent.setTimestamp(time);
if (m_xkb->modifiers() != Qt::NoModifier) {
PointerAxisDirection direction = PointerAxisUp;
if (axis == PointerAxisVertical) {
if (delta > 0) {
direction = PointerAxisUp;
} else {
direction = PointerAxisDown;
}
} else {
if (delta > 0) {
direction = PointerAxisLeft;
} else {
direction = PointerAxisRight;
}
}
if (m_shortcuts->processAxis(m_xkb->modifiers(), direction)) {
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->wheelEvent(&wheelEvent)) {
return;
}
}
auto sendWheelEvent = [this, delta, axis] (const QPoint targetPos, QObject *target) -> bool {
const QPointF localPos = m_globalPointer - targetPos;
// TODO: add modifiers and buttons
QWheelEvent event(localPos, m_globalPointer, QPoint(),
(axis == PointerAxisHorizontal) ? QPoint(delta, 0) : QPoint(0, delta),
delta,
(axis == PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical,
Qt::NoButton,
Qt::NoModifier);
event.setAccepted(false);
QCoreApplication::sendEvent(target, &event);
return event.isAccepted();
};
if (m_pointerDecoration) {
if (!sendWheelEvent(m_pointerDecoration->client()->pos(), m_pointerDecoration.data())
&& axis == PointerAxisVertical) {
if (m_pointerDecoration->decoration()->titleBar().contains(m_globalPointer.toPoint() - m_pointerDecoration->client()->pos())) {
m_pointerDecoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1),
m_globalPointer.toPoint());
}
}
}
if (m_pointerInternalWindow) {
sendWheelEvent(m_pointerInternalWindow->position(), m_pointerInternalWindow.data());
}
// TODO: check which part of KWin would like to intercept the event
// TODO: Axis support for effect redirection
if (auto seat = findSeat()) {
seat->setTimestamp(time);
seat->pointerAxis(axis == InputRedirection::PointerAxisHorizontal ? Qt::Horizontal : Qt::Vertical, delta);
}
}
void InputRedirection::updateKeyboardWindow()
@ -944,108 +1303,20 @@ void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::Keyboa
if (oldMods != keyboardModifiers()) {
emit keyboardModifiersChanged(keyboardModifiers(), oldMods);
}
// check for vt-switch
#if HAVE_INPUT
if (VirtualTerminal::self()) {
const xkb_keysym_t keysym = m_xkb->toKeysym(key);
if (state == KWin::InputRedirection::KeyboardKeyPressed &&
(keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12)) {
VirtualTerminal::self()->activate(keysym - XKB_KEY_XF86Switch_VT_1 + 1);
return;
}
}
#endif
const xkb_keysym_t keySym = m_xkb->toKeysym(key);
QKeyEvent event((state == KeyboardKeyPressed) ? QEvent::KeyPress : QEvent::KeyRelease,
m_xkb->toQtKey(keySym),
m_xkb->modifiers(),
key,
keySym,
0,
m_xkb->toString(m_xkb->toKeysym(key)));
event.setTimestamp(time);
if (waylandServer()->isScreenLocked()) {
const ToplevelList &stacking = Workspace::self()->stackingOrder();
if (stacking.isEmpty()) {
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->keyEvent(&event)) {
return;
}
auto it = stacking.end();
do {
--it;
Toplevel *t = (*it);
if (t->isDeleted()) {
// a deleted window doesn't get mouse events
continue;
}
if (!t->isLockScreen()) {
continue;
}
if (!t->readyForPainting()) {
continue;
}
if (auto seat = findSeat()) {
seat->setFocusedKeyboardSurface(t->surface());
seat->setTimestamp(time);
state == InputRedirection::KeyboardKeyPressed ? seat->keyPressed(key) : seat->keyReleased(key);
}
return;
} while (it != stacking.begin());
if (auto seat = findSeat()) {
seat->setFocusedKeyboardSurface(nullptr);
seat->setTimestamp(time);
state == InputRedirection::KeyboardKeyPressed ? seat->keyPressed(key) : seat->keyReleased(key);
}
return;
}
// TODO: pass to internal parts of KWin
#ifdef KWIN_BUILD_TABBOX
if (TabBox::TabBox::self() && TabBox::TabBox::self()->isGrabbed()) {
if (state == KWin::InputRedirection::KeyboardKeyPressed) {
TabBox::TabBox::self()->keyPress(m_xkb->modifiers() | m_xkb->toQtKey(m_xkb->toKeysym(key)));
}
return;
}
#endif
auto toKeyEvent = [&] {
const xkb_keysym_t keysym = m_xkb->toKeysym(key);
// TODO: start auto-repeat
// TODO: add modifiers to the event
const QEvent::Type type = (state == KeyboardKeyPressed) ? QEvent::KeyPress : QEvent::KeyRelease;
QKeyEvent event(type, m_xkb->toQtKey(keysym), m_xkb->modifiers(), m_xkb->toString(keysym));
return event;
};
if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) {
QKeyEvent event = toKeyEvent();
static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(&event);
return;
}
if (workspace()) {
if (AbstractClient *c = workspace()->getMovingClient()) {
// TODO: handle key repeat
if (state == KeyboardKeyPressed) {
c->keyPressEvent(m_xkb->toQtKey(m_xkb->toKeysym(key)) | m_xkb->modifiers());
if (c->isMove() || c->isResize()) {
// only update if mode didn't end
c->updateMoveResize(m_globalPointer);
}
}
return;
}
// TODO: Maybe it's better to select the top most visible internal window?
if (m_pointerInternalWindow) {
QKeyEvent event = toKeyEvent();
event.setAccepted(false);
QCoreApplication::sendEvent(m_pointerInternalWindow.data(), &event);
return;
}
}
// process global shortcuts
if (state == KeyboardKeyPressed) {
if (m_shortcuts->processKey(m_xkb->modifiers(), m_xkb->toKeysym(key))) {
return;
}
}
if (auto seat = findSeat()) {
if (workspace()->activeClient() &&
(seat->focusedKeyboardSurface() != workspace()->activeClient()->surface())) {
seat->setFocusedKeyboardSurface(workspace()->activeClient()->surface());
}
seat->setTimestamp(time);
state == InputRedirection::KeyboardKeyPressed ? seat->keyPressed(key) : seat->keyReleased(key);
}
}
@ -1067,24 +1338,10 @@ void InputRedirection::processKeymapChange(int fd, uint32_t size)
void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time)
{
// TODO: internal handling?
if (auto seat = findSeat()) {
seat->setTimestamp(time);
if (!seat->isTouchSequence()) {
updateTouchWindow(pos);
if (AbstractClient *c = dynamic_cast<AbstractClient*>(m_touchWindow.data())) {
// perform same handling as if it were a left click
bool wasAction = false;
const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction);
if (wasAction) {
// if no replay we filter out this touch point
if (!c->performMouseCommand(command, pos.toPoint())) {
return;
}
}
}
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->touchDown(id, pos, time)) {
return;
}
m_touchIdMapper.insert(id, seat->touchDown(pos));
}
}
@ -1119,25 +1376,18 @@ void InputRedirection::updateTouchWindow(const QPointF &pos)
void InputRedirection::processTouchUp(qint32 id, quint32 time)
{
// TODO: internal handling?
if (auto seat = findSeat()) {
auto it = m_touchIdMapper.constFind(id);
if (it != m_touchIdMapper.constEnd()) {
seat->setTimestamp(time);
seat->touchUp(it.value());
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->touchUp(id, time)) {
return;
}
}
}
void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time)
{
// TODO: internal handling?
if (auto seat = findSeat()) {
seat->setTimestamp(time);
auto it = m_touchIdMapper.constFind(id);
if (it != m_touchIdMapper.constEnd()) {
seat->setTimestamp(time);
seat->touchMove(it.value(), pos);
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->touchMotion(id, pos, time)) {
return;
}
}
}
@ -1156,6 +1406,25 @@ void InputRedirection::touchFrame()
}
}
void InputRedirection::insertTouchId(quint32 internalId, qint32 kwaylandId)
{
m_touchIdMapper.insert(internalId, kwaylandId);
}
qint32 InputRedirection::touchId(quint32 internalId)
{
auto it = input()->m_touchIdMapper.constFind(internalId);
if (it != input()->m_touchIdMapper.constEnd()) {
return it.value();
}
return -1;
}
void InputRedirection::removeTouchId(quint32 internalId)
{
m_touchIdMapper.remove(internalId);
}
QEvent::Type InputRedirection::buttonStateToEvent(InputRedirection::PointerButtonState state)
{
switch (state) {

75
input.h
View file

@ -43,6 +43,7 @@ namespace KWin
class GlobalShortcutsManager;
class Toplevel;
class Xkb;
class InputEventFilter;
namespace Decoration
{
@ -145,6 +146,15 @@ public:
bool supportsPointerWarping() const;
void warpPointer(const QPointF &pos);
void uninstallInputEventFilter(InputEventFilter *filter);
Toplevel *findToplevel(const QPoint &pos);
GlobalShortcutsManager *shortcuts() const {
return m_shortcuts;
}
void insertTouchId(quint32 internalId, qint32 kwaylandId);
qint32 touchId(quint32 internalId);
void removeTouchId(quint32 internalId);
public Q_SLOTS:
void updatePointerWindow();
@ -190,7 +200,6 @@ Q_SIGNALS:
private:
static QEvent::Type buttonStateToEvent(PointerButtonState state);
static Qt::MouseButton buttonToQtMouseButton(uint32_t button);
Toplevel *findToplevel(const QPoint &pos);
void setupLibInput();
void setupLibInputWithScreens();
void updatePointerPosition(const QPointF &pos);
@ -207,6 +216,8 @@ private:
void updateKeyboardWindow();
void setupWorkspace();
void reconfigure();
void setupInputFilters();
void installInputEventFilter(InputEventFilter *filter);
QPointF m_globalPointer;
QHash<uint32_t, PointerButtonState> m_pointerButtons;
QScopedPointer<Xkb> m_xkb;
@ -235,8 +246,70 @@ private:
bool m_pointerWarping = false;
QVector<InputEventFilter*> m_filters;
KWIN_SINGLETON(InputRedirection)
friend InputRedirection *input();
friend class DecorationEventFilter;
friend class InternalWindowEventFilter;
friend class ForwardInputFilter;
};
/**
* Base class for filtering input events inside InputRedirection.
*
* The idea behind the InputEventFilter is to have task oriented
* filters. E.g. there is one filter taking care of a locked screen,
* one to take care of interacting with window decorations, etc.
*
* A concrete subclass can reimplement the virtual methods and decide
* whether an event should be filtered out or not by returning either
* @c true or @c false. E.g. the lock screen filter can easily ensure
* that all events are filtered out.
*
* As soon as a filter returns @c true the processing is stopped. If
* a filter returns @c false the next one is invoked. This means a filter
* installed early gets to see more events than a filter installed later on.
*
* Deleting an instance of InputEventFilter automatically uninstalls it from
* InputRedirection.
**/
class InputEventFilter
{
public:
InputEventFilter();
virtual ~InputEventFilter();
/**
* Event filter for pointer events which can be described by a QMouseEvent.
*
* Please note that the button translation in QMouseEvent cannot cover all
* possible buttons. Because of that also the @p nativeButton code is passed
* through the filter. For internal areas it's fine to use @p event, but for
* passing to client windows the @p nativeButton should be used.
*
* @param event The event information about the move or button press/release
* @param nativeButton The native key code of the button, for move events 0
* @return @c true to stop further event processing, @c false to pass to next filter
**/
virtual bool pointerEvent(QMouseEvent *event, quint32 nativeButton);
/**
* Event filter for pointer axis events.
*
* @param event The event information about the axis event
* @return @c true to stop further event processing, @c false to pass to next filter
**/
virtual bool wheelEvent(QWheelEvent *event);
/**
* Event filter for keyboard events.
*
* @param event The event information about the key event
* @return @c tru to stop further event processing, @c false to pass to next filter.
**/
virtual bool keyEvent(QKeyEvent *event);
virtual bool touchDown(quint32 id, const QPointF &pos, quint32 time);
virtual bool touchMotion(quint32 id, const QPointF &pos, quint32 time);
virtual bool touchUp(quint32 id, quint32 time);
};
class Xkb