diff --git a/input.cpp b/input.cpp index 5013cb286..253200795 100644 --- a/input.cpp +++ b/input.cpp @@ -1,2409 +1,2424 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin Copyright (C) 2018 Roman Gilg Copyright (C) 2019 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "input.h" #include "input_event.h" #include "input_event_spy.h" #include "keyboard_input.h" #include "pointer_input.h" #include "touch_input.h" #include "touch_hide_cursor_spy.h" #include "client.h" #include "effects.h" #include "gestures.h" #include "globalshortcuts.h" #include "logind.h" #include "main.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox/tabbox.h" #endif #include "unmanaged.h" #include "screenedge.h" #include "screens.h" #include "workspace.h" #include "libinput/connection.h" #include "libinput/device.h" #include "platform.h" #include "popup_input_filter.h" #include "shell_client.h" #include "wayland_server.h" #include "xwl/xwayland_interface.h" #include #include #include #include #include #include #include //screenlocker #include // Qt #include #include namespace KWin { 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; } bool InputEventFilter::pinchGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureUpdate(const QSizeF &delta, quint32 time) { Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::switchEvent(SwitchEvent *event) { Q_UNUSED(event) return false; } void InputEventFilter::passToWaylandServer(QKeyEvent *event) { Q_ASSERT(waylandServer()); if (event->isAutoRepeat()) { return; } switch (event->type()) { case QEvent::KeyPress: waylandServer()->seat()->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: waylandServer()->seat()->keyReleased(event->nativeScanCode()); break; default: break; } } 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 && !event->isAutoRepeat()) { const xkb_keysym_t keysym = event->nativeVirtualKey(); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { LogindIntegration::self()->switchVirtualTerminal(keysym - XKB_KEY_XF86Switch_VT_1 + 1); return true; } } return false; } }; class TerminateServerFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { if (event->nativeVirtualKey() == XKB_KEY_Terminate_Server) { qCWarning(KWIN_CORE) << "Request to terminate server"; QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection); return true; } } return false; } }; 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) { if (pointerSurfaceAllowed()) { // TODO: should the pointer position always stay in sync, i.e. not do the check? seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { // TODO: can we leak presses/releases here when we move the mouse in between from an allowed surface to // disallowed one or vice versa? 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 (pointerSurfaceAllowed()) { 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; } if (event->isAutoRepeat()) { // wayland client takes care of it return true; } // send event to KSldApp for global accel // if event is set to accepted it means a whitelisted shortcut was triggered // in that case we filter it out and don't process it further event->setAccepted(false); QCoreApplication::sendEvent(ScreenLocker::KSldApp::self(), event); if (event->isAccepted()) { return true; } // continue normal processing input()->keyboard()->update(); auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (!keyboardSurfaceAllowed()) { // don't pass event to seat return true; } 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 (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { input()->touch()->insertId(id, seat->touchDown(pos)); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } } return true; } bool touchUp(quint32 id, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } } return true; } bool pinchGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(fingerCount) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureEnd(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureCancelled(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(fingerCount) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { Q_UNUSED(delta) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureEnd(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureCancelled(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } private: bool surfaceAllowed(KWayland::Server::SurfaceInterface *(KWayland::Server::SeatInterface::*method)() const) const { if (KWayland::Server::SurfaceInterface *s = (waylandServer()->seat()->*method)()) { if (Toplevel *t = waylandServer()->findClient(s)) { return t->isLockScreen() || t->isInputMethod(); } return false; } return true; } bool pointerSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedPointerSurface); } bool keyboardSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedKeyboardSurface); } bool touchSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedTouchSurface); } }; class EffectsFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!effects) { return false; } return static_cast(effects)->checkInputWindowEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!effects || !static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { return false; } waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(event); return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchDown(id, pos, time); } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchMotion(id, pos, time); } bool touchUp(quint32 id, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchUp(id, time); } }; class MoveResizeFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) AbstractClient *c = workspace()->moveResizeClient(); 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()->moveResizeClient() != nullptr; } bool keyEvent(QKeyEvent *event) override { AbstractClient *c = workspace()->moveResizeClient(); 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; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } if (!m_set) { m_id = id; m_set = true; } if (m_id == id) { c->updateMoveResize(pos.toPoint()); } return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } if (m_id == id || !m_set) { c->endMoveResize(); m_set = false; // pass through to update decoration filter later on return false; } m_set = false; return true; } private: quint32 m_id = 0; bool m_set = false; }; class WindowSelectorFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!m_active) { return false; } switch (event->type()) { case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { if (event->button() == Qt::RightButton) { cancel(); } else { accept(event->globalPos()); } } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while selecting a window return m_active; } bool keyEvent(QKeyEvent *event) override { Q_UNUSED(event) if (!m_active) { return false; } waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { // x11 variant does this on key press, so do the same if (event->key() == Qt::Key_Escape) { cancel(); } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Space) { accept(input()->globalPointer()); } if (input()->supportsPointerWarping()) { int mx = 0; int my = 0; if (event->key() == Qt::Key_Left) { mx = -10; } if (event->key() == Qt::Key_Right) { mx = 10; } if (event->key() == Qt::Key_Up) { my = -10; } if (event->key() == Qt::Key_Down) { my = 10; } if (event->modifiers() & Qt::ControlModifier) { mx /= 10; my /= 10; } input()->warpPointer(input()->globalPointer() + QPointF(mx, my)); } } // filter out while selecting a window return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } m_touchPoints.insert(id, pos); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } auto it = m_touchPoints.find(id); if (it != m_touchPoints.end()) { *it = pos; } return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } auto it = m_touchPoints.find(id); if (it != m_touchPoints.end()) { const auto pos = it.value(); m_touchPoints.erase(it); if (m_touchPoints.isEmpty()) { accept(pos); } } return true; } bool isActive() const { return m_active; } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_callback = callback; input()->keyboard()->update(); input()->cancelTouch(); } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_pointSelectionFallback = callback; input()->keyboard()->update(); input()->cancelTouch(); } private: void deactivate() { m_active = false; m_callback = std::function(); m_pointSelectionFallback = std::function(); input()->pointer()->removeWindowSelectionCursor(); input()->keyboard()->update(); m_touchPoints.clear(); } void cancel() { if (m_callback) { m_callback(nullptr); } if (m_pointSelectionFallback) { m_pointSelectionFallback(QPoint(-1, -1)); } deactivate(); } void accept(const QPoint &pos) { if (m_callback) { // TODO: this ignores shaped windows m_callback(input()->findToplevel(pos)); } if (m_pointSelectionFallback) { m_pointSelectionFallback(pos); } deactivate(); } void accept(const QPointF &pos) { accept(pos.toPoint()); } bool m_active = false; std::function m_callback; std::function m_pointSelectionFallback; QMap m_touchPoints; }; 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(static_cast(event)->modifiersRelevantForGlobalShortcuts(), event->key()); } return false; } bool swipeGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeStart(fingerCount); return false; } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeUpdate(delta); return false; } bool swipeGestureCancelled(quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeCancel(); return false; } bool swipeGestureEnd(quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeEnd(); return false; } }; namespace { enum class MouseAction { ModifierOnly, ModifierAndWindow }; std::pair performClientMouseAction(QMouseEvent *event, AbstractClient *client, MouseAction action = MouseAction::ModifierOnly) { Options::MouseCommand command = Options::MouseNothing; bool wasAction = false; if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { if (!input()->pointer()->isConstrained() && !workspace()->globalShortcutsDisabled()) { wasAction = true; switch (event->button()) { case Qt::LeftButton: command = options->commandAll1(); break; case Qt::MiddleButton: command = options->commandAll2(); break; case Qt::RightButton: command = options->commandAll3(); break; default: // nothing break; } } } else { if (action == MouseAction::ModifierAndWindow) { command = client->getMouseCommand(event->button(), &wasAction); } } if (wasAction) { return std::make_pair(wasAction, !client->performMouseCommand(command, event->globalPos())); } return std::make_pair(wasAction, false); } std::pair performClientWheelAction(QWheelEvent *event, AbstractClient *c, MouseAction action = MouseAction::ModifierOnly) { bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { if (!input()->pointer()->isConstrained() && !workspace()->globalShortcutsDisabled()) { wasAction = true; command = options->operationWindowMouseWheel(-1 * event->angleDelta().y()); } } else { if (action == MouseAction::ModifierAndWindow) { command = c->getWheelCommand(Qt::Vertical, &wasAction); } } if (wasAction) { return std::make_pair(wasAction, !c->performMouseCommand(command, event->globalPos())); } return std::make_pair(wasAction, false); } } class InternalWindowEventFilter : public InputEventFilter { bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } // find client switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { auto s = waylandServer()->findClient(internal); if (s && s->isDecorated()) { // only perform mouse commands on decorated internal windows const auto actionResult = performClientMouseAction(event, s); if (actionResult.first) { return actionResult.second; } } break; } default: break; } 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()->pointer()->internalWindow(); if (!internal) { return false; } if (event->angleDelta().y() != 0) { auto s = waylandServer()->findClient(internal); if (s && s->isDecorated()) { // client window action only on vertical scrolling const auto actionResult = performClientWheelAction(event, s); if (actionResult.first) { return actionResult.second; } } } 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() * -1, delta * -1, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool keyEvent(QKeyEvent *event) override { const auto &internalClients = waylandServer()->internalClients(); if (internalClients.isEmpty()) { return false; } QWindow *found = nullptr; auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if (!screens()->geometry().contains(w->geometry())) { continue; } if (w->property("_q_showWithoutActivating").toBool()) { continue; } if (w->property("outputOnly").toBool()) { continue; } if (w->flags().testFlag(Qt::ToolTip)) { continue; } found = w; break; } } while (it != internalClients.begin()); if (!found) { return false; } auto xkb = input()->keyboard()->xkb(); Qt::Key key = xkb->toQtKey(xkb->toKeysym(event->nativeScanCode())); if (key == Qt::Key_Super_L || key == Qt::Key_Super_R) { // workaround for QTBUG-62102 key = Qt::Key_Meta; } QKeyEvent internalEvent(event->type(), key, event->modifiers(), event->nativeScanCode(), event->nativeVirtualKey(), event->nativeModifiers(), event->text()); internalEvent.setAccepted(false); if (QCoreApplication::sendEvent(found, &internalEvent)) { waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); return true; } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { // something else is getting the events return false; } auto touch = input()->touch(); if (touch->internalPressId() != -1) { // already on internal window, ignore further touch points, but filter out return true; } // a new touch point seat->setTimestamp(time); auto internal = touch->internalWindow(); if (!internal) { return false; } touch->setInternalPressId(id); // Qt's touch event API is rather complex, let's do fake mouse events instead m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QEnterEvent enterEvent(m_lastLocalTouchPos, m_lastLocalTouchPos, pos); QCoreApplication::sendEvent(internal.data(), &enterEvent); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); QCoreApplication::instance()->sendEvent(internal.data(), &e); return true; } bool touchUp(quint32 id, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); QEvent leaveEvent(QEvent::Leave); QCoreApplication::sendEvent(internal.data(), &leaveEvent); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setInternalPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; class DecorationEventFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF p = event->globalPos() - decoration->client()->pos(); switch (event->type()) { case QEvent::MouseMove: { QHoverEvent e(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); return true; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { const auto actionResult = performClientMouseAction(event, decoration->client()); if (actionResult.first) { return actionResult.second; } 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); } return true; } default: break; } return false; } bool wheelEvent(QWheelEvent *event) override { auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } if (event->angleDelta().y() != 0) { // client window action only on vertical scrolling const auto actionResult = performClientWheelAction(event, decoration->client()); if (actionResult.first) { return actionResult.second; } } 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->client()->titlebarPositionUnderMouse()) { decoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1), event->globalPosF().toPoint()); } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } if (input()->touch()->decorationPressId() != -1) { // already on a decoration, ignore further touch points, but filter out return true; } seat->setTimestamp(time); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } input()->touch()->setDecorationPressId(id); m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QHoverEvent hoverEvent(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QCoreApplication::sendEvent(decoration->decoration(), &hoverEvent); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted()) { decoration->client()->processDecorationButtonPress(&e); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint()); return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationButtonRelease(&e); QHoverEvent leaveEvent(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::sendEvent(decoration->decoration(), &leaveEvent); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setDecorationPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; #ifdef KWIN_BUILD_TABBOX class TabBoxInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 button) override { Q_UNUSED(button) if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleMouseEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } auto seat = waylandServer()->seat(); seat->setFocusedKeyboardSurface(nullptr); input()->pointer()->setEnableConstraints(false); // pass the key event to the seat, so that it has a proper model of the currently hold keys // this is important for combinations like alt+shift to ensure that shift is not considered pressed passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { TabBox::TabBox::self()->keyPress(event->modifiers() | event->key()); } else if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == Qt::NoModifier) { TabBox::TabBox::self()->modifiersReleased(); } return true; } bool wheelEvent(QWheelEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleWheelEvent(event); } }; #endif class ScreenEdgeInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) ScreenEdges::self()->isEntered(event); // always forward return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) // TODO: better check whether a touch sequence is in progress if (m_touchInProgress || waylandServer()->seat()->isTouchSequence()) { // cancel existing touch ScreenEdges::self()->gestureRecognizer()->cancelSwipeGesture(); m_touchInProgress = false; m_id = 0; return false; } if (ScreenEdges::self()->gestureRecognizer()->startSwipeGesture(pos) > 0) { m_touchInProgress = true; m_id = id; m_lastPos = pos; return true; } return false; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (m_touchInProgress && m_id == id) { ScreenEdges::self()->gestureRecognizer()->updateSwipeGesture(QSizeF(pos.x() - m_lastPos.x(), pos.y() - m_lastPos.y())); m_lastPos = pos; return true; } return false; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time) if (m_touchInProgress && m_id == id) { ScreenEdges::self()->gestureRecognizer()->endSwipeGesture(); m_touchInProgress = false; return true; } return false; } private: bool m_touchInProgress = false; quint32 m_id = 0; QPointF m_lastPos; }; /** * This filter implements window actions. If the event should not be passed to the * current pointer window it will filter out the event **/ class WindowActionInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (event->type() != QEvent::MouseButtonPress) { return false; } AbstractClient *c = dynamic_cast(input()->pointer()->focus().data()); if (!c) { return false; } const auto actionResult = performClientMouseAction(event, c, MouseAction::ModifierAndWindow); if (actionResult.first) { return actionResult.second; } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->angleDelta().y() == 0) { // only actions on vertical scroll return false; } AbstractClient *c = dynamic_cast(input()->pointer()->focus().data()); if (!c) { return false; } const auto actionResult = performClientWheelAction(event, c, MouseAction::ModifierAndWindow); if (actionResult.first) { return actionResult.second; } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(time) auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } AbstractClient *c = dynamic_cast(input()->touch()->focus().data()); if (!c) { return false; } bool wasAction = false; const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction); if (wasAction) { return !c->performMouseCommand(command, pos.toPoint()); } return false; } }; /** * 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: { seat->setPointerPos(event->globalPos()); MouseEvent *e = static_cast(event); if (e->delta() != QSizeF()) { seat->relativePointerMotion(e->delta(), e->deltaUnaccelerated(), e->timestampMicroseconds()); } break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); auto _event = static_cast(event); KWayland::Server::PointerAxisSource source; switch (_event->axisSource()) { case KWin::InputRedirection::PointerAxisSourceWheel: source = KWayland::Server::PointerAxisSource::Wheel; break; case KWin::InputRedirection::PointerAxisSourceFinger: source = KWayland::Server::PointerAxisSource::Finger; break; case KWin::InputRedirection::PointerAxisSourceContinuous: source = KWayland::Server::PointerAxisSource::Continuous; break; case KWin::InputRedirection::PointerAxisSourceWheelTilt: source = KWayland::Server::PointerAxisSource::WheelTilt; break; case KWin::InputRedirection::PointerAxisSourceUnknown: default: source = KWayland::Server::PointerAxisSource::Unknown; break; } seat->pointerAxisV5(_event->orientation(), _event->delta(), _event->discreteDelta(), source); return true; } bool keyEvent(QKeyEvent *event) override { if (!workspace()) { return false; } if (event->isAutoRepeat()) { // handled by Wayland client return false; } auto seat = waylandServer()->seat(); input()->keyboard()->update(); seat->setTimestamp(event->timestamp()); passToWaylandServer(event); return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); input()->touch()->insertId(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()->touch()->mappedId(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()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } return true; } bool pinchGestureBegin(int fingerCount, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->startPointerPinchGesture(fingerCount); return true; } bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->updatePointerPinchGesture(delta, scale, angleDelta); return true; } bool pinchGestureEnd(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->endPointerPinchGesture(); return true; } bool pinchGestureCancelled(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->cancelPointerPinchGesture(); return true; } bool swipeGestureBegin(int fingerCount, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->startPointerSwipeGesture(fingerCount); return true; } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->updatePointerSwipeGesture(delta); return true; } bool swipeGestureEnd(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->endPointerSwipeGesture(); return true; } bool swipeGestureCancelled(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->cancelPointerSwipeGesture(); return true; } }; class DragAndDropInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); if (!seat->isDragPointer()) { return false; } if (seat->isDragTouch()) { return true; } seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { const auto pos = input()->globalPointer(); seat->setPointerPos(pos); const auto eventPos = event->globalPos(); // TODO: use InputDeviceHandler::at() here and check isClient()? Toplevel *t = input()->findManagedToplevel(eventPos); if (auto *xwl = xwayland()) { const auto ret = xwl->dragMoveFilter(t, eventPos); if (ret == Xwl::DragEventReply::Ignore) { return false; } else if (ret == Xwl::DragEventReply::Take) { break; } } if (t) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->activateClient(c); } seat->setDragTarget(t->surface(), t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); break; default: break; } // TODO: should we pass through effects? return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isDragPointer()) { return true; } if (!seat->isDragTouch()) { return false; } if (m_touchId != id) { return true; } seat->setTimestamp(time); input()->touch()->insertId(id, seat->touchDown(pos)); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isDragPointer()) { return true; } if (!seat->isDragTouch()) { return false; } if (m_touchId < 0) { // We take for now the first id appearing as a move after a drag // started. We can optimize by specifying the id the drag is // associated with by implementing a key-value getter in KWayland. m_touchId = id; } if (m_touchId != id) { return true; } seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId == -1) { return true; } seat->touchMove(kwaylandId, pos); if (Toplevel *t = input()->findToplevel(pos.toPoint())) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->activateClient(c); } seat->setDragTarget(t->surface(), pos, t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } return true; } bool touchUp(quint32 id, quint32 time) override { auto seat = waylandServer()->seat(); if (!seat->isDragTouch()) { return false; } seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } if (m_touchId == id) { m_touchId = -1; } return true; } private: qint32 m_touchId = -1; }; KWIN_SINGLETON_FACTORY(InputRedirection) static const QString s_touchpadComponent = QStringLiteral("kcm_touchpad"); InputRedirection::InputRedirection(QObject *parent) : QObject(parent) , m_keyboard(new KeyboardInputRedirection(this)) , m_pointer(new PointerInputRedirection(this)) , m_touch(new TouchInputRedirection(this)) , m_shortcuts(new GlobalShortcutsManager(this)) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); if (Application::usesLibinput()) { if (LogindIntegration::self()->hasSessionControl()) { setupLibInput(); } else { LibInput::Connection::createThread(); if (LogindIntegration::self()->isConnected()) { LogindIntegration::self()->takeControl(); } else { connect(LogindIntegration::self(), &LogindIntegration::connectedChanged, LogindIntegration::self(), &LogindIntegration::takeControl); } connect(LogindIntegration::self(), &LogindIntegration::hasSessionControlChanged, this, [this] (bool sessionControl) { if (sessionControl) { setupLibInput(); } } ); } } connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace); reconfigure(); } InputRedirection::~InputRedirection() { s_self = NULL; qDeleteAll(m_filters); qDeleteAll(m_spies); } void InputRedirection::installInputEventFilter(InputEventFilter *filter) { Q_ASSERT(!m_filters.contains(filter)); m_filters << filter; } void InputRedirection::prependInputEventFilter(InputEventFilter *filter) { Q_ASSERT(!m_filters.contains(filter)); m_filters.prepend(filter); } void InputRedirection::uninstallInputEventFilter(InputEventFilter *filter) { m_filters.removeOne(filter); } void InputRedirection::installInputEventSpy(InputEventSpy *spy) { m_spies << spy; } void InputRedirection::uninstallInputEventSpy(InputEventSpy *spy) { m_spies.removeOne(spy); } void InputRedirection::init() { m_shortcuts->init(); } void InputRedirection::setupWorkspace() { if (waylandServer()) { using namespace KWayland::Server; FakeInputInterface *fakeInput = waylandServer()->display()->createFakeInput(this); fakeInput->create(); connect(fakeInput, &FakeInputInterface::deviceCreated, this, [this] (FakeInputDevice *device) { connect(device, &FakeInputDevice::authenticationRequested, this, [this, device] (const QString &application, const QString &reason) { Q_UNUSED(application) Q_UNUSED(reason) // TODO: make secure device->setAuthentication(true); } ); connect(device, &FakeInputDevice::pointerMotionRequested, this, [this] (const QSizeF &delta) { // TODO: Fix time m_pointer->processMotion(globalPointer() + QPointF(delta.width(), delta.height()), 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerMotionAbsoluteRequested, this, [this] (const QPointF &pos) { // TODO: Fix time m_pointer->processMotion(pos, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonPressed, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonReleased, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerAxisRequested, this, [this] (Qt::Orientation orientation, qreal delta) { // TODO: Fix time InputRedirection::PointerAxis axis; switch (orientation) { case Qt::Horizontal: axis = InputRedirection::PointerAxisHorizontal; break; case Qt::Vertical: axis = InputRedirection::PointerAxisVertical; break; default: Q_UNREACHABLE(); break; } // TODO: Fix time m_pointer->processAxis(axis, delta, 0, InputRedirection::PointerAxisSourceUnknown, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchDownRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processDown(id, pos, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchMotionRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processMotion(id, pos, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchUpRequested, this, [this] (quint32 id) { // TODO: Fix time m_touch->processUp(id, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchCancelRequested, this, [this] () { m_touch->cancel(); } ); connect(device, &FakeInputDevice::touchFrameRequested, this, [this] () { m_touch->frame(); } ); } ); connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure); m_keyboard->init(); m_pointer->init(); m_touch->init(); } setupInputFilters(); } void InputRedirection::setupInputFilters() { const bool hasGlobalShortcutSupport = !waylandServer() || waylandServer()->hasGlobalShortcutSupport(); if (LogindIntegration::self()->hasSessionControl() && hasGlobalShortcutSupport) { installInputEventFilter(new VirtualTerminalFilter); } if (waylandServer()) { installInputEventSpy(new TouchHideCursorSpy); if (hasGlobalShortcutSupport) { installInputEventFilter(new TerminateServerFilter); } installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); installInputEventFilter(new PopupInputFilter); m_windowSelector = new WindowSelectorFilter; installInputEventFilter(m_windowSelector); } if (hasGlobalShortcutSupport) { installInputEventFilter(new ScreenEdgeInputFilter); } installInputEventFilter(new EffectsFilter); installInputEventFilter(new MoveResizeFilter); #ifdef KWIN_BUILD_TABBOX installInputEventFilter(new TabBoxInputFilter); #endif if (hasGlobalShortcutSupport) { installInputEventFilter(new GlobalShortcutFilter); } installInputEventFilter(new DecorationEventFilter); installInputEventFilter(new InternalWindowEventFilter); if (waylandServer()) { installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new ForwardInputFilter); } } void InputRedirection::reconfigure() { if (Application::usesLibinput()) { auto inputConfig = kwinApp()->inputConfig(); inputConfig->reparseConfiguration(); const auto config = inputConfig->group(QStringLiteral("keyboard")); const int delay = config.readEntry("RepeatDelay", 660); const int rate = config.readEntry("RepeatRate", 25); const bool enabled = config.readEntry("KeyboardRepeating", 0) == 0; waylandServer()->seat()->setKeyRepeatInfo(enabled ? rate : 0, delay); } } static KWayland::Server::SeatInterface *findSeat() { auto server = waylandServer(); if (!server) { return nullptr; } return server->seat(); } void InputRedirection::setupLibInput() { if (!Application::usesLibinput()) { return; } if (m_libInput) { return; } LibInput::Connection *conn = LibInput::Connection::create(this); m_libInput = conn; if (conn) { if (waylandServer()) { // create relative pointer manager waylandServer()->display()->createRelativePointerManager(KWayland::Server::RelativePointerInterfaceVersion::UnstableV1, waylandServer()->display())->create(); } conn->setInputConfig(kwinApp()->inputConfig()); conn->updateLEDs(m_keyboard->xkb()->leds()); connect(m_keyboard, &KeyboardInputRedirection::ledsChanged, conn, &LibInput::Connection::updateLEDs); connect(conn, &LibInput::Connection::eventsRead, this, [this] { m_libInput->processEvents(); }, Qt::QueuedConnection ); conn->setup(); connect(conn, &LibInput::Connection::pointerButtonChanged, m_pointer, &PointerInputRedirection::processButton); connect(conn, &LibInput::Connection::pointerAxisChanged, m_pointer, &PointerInputRedirection::processAxis); connect(conn, &LibInput::Connection::pinchGestureBegin, m_pointer, &PointerInputRedirection::processPinchGestureBegin); connect(conn, &LibInput::Connection::pinchGestureUpdate, m_pointer, &PointerInputRedirection::processPinchGestureUpdate); connect(conn, &LibInput::Connection::pinchGestureEnd, m_pointer, &PointerInputRedirection::processPinchGestureEnd); connect(conn, &LibInput::Connection::pinchGestureCancelled, m_pointer, &PointerInputRedirection::processPinchGestureCancelled); connect(conn, &LibInput::Connection::swipeGestureBegin, m_pointer, &PointerInputRedirection::processSwipeGestureBegin); connect(conn, &LibInput::Connection::swipeGestureUpdate, m_pointer, &PointerInputRedirection::processSwipeGestureUpdate); connect(conn, &LibInput::Connection::swipeGestureEnd, m_pointer, &PointerInputRedirection::processSwipeGestureEnd); connect(conn, &LibInput::Connection::swipeGestureCancelled, m_pointer, &PointerInputRedirection::processSwipeGestureCancelled); connect(conn, &LibInput::Connection::keyChanged, m_keyboard, &KeyboardInputRedirection::processKey); connect(conn, &LibInput::Connection::pointerMotion, this, [this] (const QSizeF &delta, const QSizeF &deltaNonAccel, uint32_t time, quint64 timeMicroseconds, LibInput::Device *device) { m_pointer->processMotion(m_pointer->pos() + QPointF(delta.width(), delta.height()), delta, deltaNonAccel, time, timeMicroseconds, device); } ); connect(conn, &LibInput::Connection::pointerMotionAbsolute, this, [this] (QPointF orig, QPointF screen, uint32_t time, LibInput::Device *device) { Q_UNUSED(orig) m_pointer->processMotion(screen, time, device); } ); connect(conn, &LibInput::Connection::touchDown, m_touch, &TouchInputRedirection::processDown); connect(conn, &LibInput::Connection::touchUp, m_touch, &TouchInputRedirection::processUp); connect(conn, &LibInput::Connection::touchMotion, m_touch, &TouchInputRedirection::processMotion); connect(conn, &LibInput::Connection::touchCanceled, m_touch, &TouchInputRedirection::cancel); connect(conn, &LibInput::Connection::touchFrame, m_touch, &TouchInputRedirection::frame); auto handleSwitchEvent = [this] (SwitchEvent::State state, quint32 time, quint64 timeMicroseconds, LibInput::Device *device) { SwitchEvent event(state, time, timeMicroseconds, device); processSpies(std::bind(&InputEventSpy::switchEvent, std::placeholders::_1, &event)); processFilters(std::bind(&InputEventFilter::switchEvent, std::placeholders::_1, &event)); }; connect(conn, &LibInput::Connection::switchToggledOn, this, std::bind(handleSwitchEvent, SwitchEvent::State::On, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); connect(conn, &LibInput::Connection::switchToggledOff, this, std::bind(handleSwitchEvent, SwitchEvent::State::Off, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); if (screens()) { setupLibInputWithScreens(); } else { connect(kwinApp(), &Application::screensCreated, this, &InputRedirection::setupLibInputWithScreens); } if (auto s = findSeat()) { // Workaround for QTBUG-54371: if there is no real keyboard Qt doesn't request virtual keyboard s->setHasKeyboard(true); s->setHasPointer(conn->hasPointer()); s->setHasTouch(conn->hasTouch()); connect(conn, &LibInput::Connection::hasAlphaNumericKeyboardChanged, this, [this] (bool set) { if (m_libInput->isSuspended()) { return; } // TODO: this should update the seat, only workaround for QTBUG-54371 emit hasAlphaNumericKeyboardChanged(set); } ); connect(conn, &LibInput::Connection::hasTabletModeSwitchChanged, this, [this] (bool set) { if (m_libInput->isSuspended()) { return; } emit hasTabletModeSwitchChanged(set); } ); connect(conn, &LibInput::Connection::hasPointerChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasPointer(set); } ); connect(conn, &LibInput::Connection::hasTouchChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasTouch(set); } ); } connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, m_libInput, [this] (bool active) { if (!active) { m_libInput->deactivate(); } } ); } setupTouchpadShortcuts(); } void InputRedirection::setupTouchpadShortcuts() { if (!m_libInput) { return; } QAction *touchpadToggleAction = new QAction(this); QAction *touchpadOnAction = new QAction(this); QAction *touchpadOffAction = new QAction(this); touchpadToggleAction->setObjectName(QStringLiteral("Toggle Touchpad")); touchpadToggleAction->setProperty("componentName", s_touchpadComponent); touchpadOnAction->setObjectName(QStringLiteral("Enable Touchpad")); touchpadOnAction->setProperty("componentName", s_touchpadComponent); touchpadOffAction->setObjectName(QStringLiteral("Disable Touchpad")); touchpadOffAction->setProperty("componentName", s_touchpadComponent); KGlobalAccel::self()->setDefaultShortcut(touchpadToggleAction, QList{Qt::Key_TouchpadToggle}); KGlobalAccel::self()->setShortcut(touchpadToggleAction, QList{Qt::Key_TouchpadToggle}); KGlobalAccel::self()->setDefaultShortcut(touchpadOnAction, QList{Qt::Key_TouchpadOn}); KGlobalAccel::self()->setShortcut(touchpadOnAction, QList{Qt::Key_TouchpadOn}); KGlobalAccel::self()->setDefaultShortcut(touchpadOffAction, QList{Qt::Key_TouchpadOff}); KGlobalAccel::self()->setShortcut(touchpadOffAction, QList{Qt::Key_TouchpadOff}); #ifndef KWIN_BUILD_TESTING registerShortcut(Qt::Key_TouchpadToggle, touchpadToggleAction); registerShortcut(Qt::Key_TouchpadOn, touchpadOnAction); registerShortcut(Qt::Key_TouchpadOff, touchpadOffAction); #endif connect(touchpadToggleAction, &QAction::triggered, m_libInput, &LibInput::Connection::toggleTouchpads); connect(touchpadOnAction, &QAction::triggered, m_libInput, &LibInput::Connection::enableTouchpads); connect(touchpadOffAction, &QAction::triggered, m_libInput, &LibInput::Connection::disableTouchpads); } bool InputRedirection::hasAlphaNumericKeyboard() { if (m_libInput) { return m_libInput->hasAlphaNumericKeyboard(); } return true; } bool InputRedirection::hasTabletModeSwitch() { if (m_libInput) { return m_libInput->hasTabletModeSwitch(); } return false; } void InputRedirection::setupLibInputWithScreens() { if (!screens() || !m_libInput) { return; } m_libInput->setScreenSize(screens()->size()); m_libInput->updateScreens(); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); connect(screens(), &Screens::changed, m_libInput, &LibInput::Connection::updateScreens); } void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time) { m_pointer->processMotion(pos, time); } void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { m_pointer->processButton(button, state, time); } void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, qint32 discreteDelta, PointerAxisSource source, uint32_t time) { m_pointer->processAxis(axis, delta, discreteDelta, source, time); } void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { m_keyboard->processKey(key, state, time); } void InputRedirection::processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { m_keyboard->processModifiers(modsDepressed, modsLatched, modsLocked, group); } void InputRedirection::processKeymapChange(int fd, uint32_t size) { m_keyboard->processKeymapChange(fd, size); } void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time) { m_touch->processDown(id, pos, time); } void InputRedirection::processTouchUp(qint32 id, quint32 time) { m_touch->processUp(id, time); } void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time) { m_touch->processMotion(id, pos, time); } void InputRedirection::cancelTouch() { m_touch->cancel(); } void InputRedirection::touchFrame() { m_touch->frame(); } Qt::MouseButtons InputRedirection::qtButtonStates() const { return m_pointer->buttons(); } static bool acceptsInput(Toplevel *t, const QPoint &pos) { const QRegion input = t->inputShape(); if (input.isEmpty()) { return true; } return input.translated(t->pos()).contains(pos); } Toplevel *InputRedirection::findToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); // TODO: check whether the unmanaged wants input events at all if (!isScreenLocked) { // if an effect overrides the cursor we don't have a window to focus if (effects && static_cast(effects)->isMouseInterception()) { return nullptr; } const UnmanagedList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { if (u->geometry().contains(pos) && acceptsInput(u, pos)) { return u; } } } return findManagedToplevel(pos); } Toplevel *InputRedirection::findManagedToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); const ToplevelList &stacking = Workspace::self()->stackingOrder(); if (stacking.isEmpty()) { return NULL; } auto it = stacking.end(); do { --it; Toplevel *t = (*it); if (t->isDeleted()) { // a deleted window doesn't get mouse events continue; } if (AbstractClient *c = dynamic_cast(t)) { if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || !c->isCurrentTab() || c->isHiddenInternal()) { continue; } } if (!t->readyForPainting()) { continue; } if (isScreenLocked) { if (!t->isLockScreen() && !t->isInputMethod()) { continue; } } if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); return NULL; } Qt::KeyboardModifiers InputRedirection::keyboardModifiers() const { return m_keyboard->modifiers(); } Qt::KeyboardModifiers InputRedirection::modifiersRelevantForGlobalShortcuts() const { return m_keyboard->modifiersRelevantForGlobalShortcuts(); } void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { Q_UNUSED(shortcut) kwinApp()->platform()->setupActionForGlobalAccel(action); } void InputRedirection::registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) { m_shortcuts->registerPointerShortcut(action, modifiers, pointerButtons); } void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { m_shortcuts->registerAxisShortcut(action, modifiers, axis); } void InputRedirection::registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action) { m_shortcuts->registerTouchpadSwipe(action, direction); } void InputRedirection::registerGlobalAccel(KGlobalAccelInterface *interface) { m_shortcuts->setKGlobalAccelInterface(interface); } void InputRedirection::warpPointer(const QPointF &pos) { m_pointer->warp(pos); } bool InputRedirection::supportsPointerWarping() const { return m_pointer->supportsWarping(); } QPointF InputRedirection::globalPointer() const { return m_pointer->pos(); } void InputRedirection::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (!m_windowSelector || m_windowSelector->isActive()) { callback(nullptr); return; } m_windowSelector->start(callback); m_pointer->setWindowSelectionCursor(cursorName); } void InputRedirection::startInteractivePositionSelection(std::function callback) { if (!m_windowSelector || m_windowSelector->isActive()) { callback(QPoint(-1, -1)); return; } m_windowSelector->start(callback); m_pointer->setWindowSelectionCursor(QByteArray()); } bool InputRedirection::isSelectingWindow() const { return m_windowSelector ? m_windowSelector->isActive() : false; } InputDeviceHandler::InputDeviceHandler(InputRedirection *input) : QObject(input) { } InputDeviceHandler::~InputDeviceHandler() = default; void InputDeviceHandler::init() { connect(workspace(), &Workspace::stackingOrderChanged, this, &InputDeviceHandler::update); connect(workspace(), &Workspace::clientMinimizedChanged, this, &InputDeviceHandler::update); connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &InputDeviceHandler::update); } bool InputDeviceHandler::setAt(Toplevel *toplevel) { - if (m_at == toplevel) { + if (m_at.at == toplevel) { return false; } - auto old = m_at; - m_at = toplevel; + auto old = m_at.at; + disconnect(m_at.surfaceCreatedConnection); + m_at.surfaceCreatedConnection = QMetaObject::Connection(); + + m_at.at = toplevel; emit atChanged(old, toplevel); return true; } void InputDeviceHandler::setFocus(Toplevel *toplevel) { m_focus.focus = toplevel; //TODO: call focusUpdate? } void InputDeviceHandler::setDecoration(QPointer decoration) { auto oldDeco = m_focus.decoration; m_focus.decoration = decoration; cleanupDecoration(oldDeco.data(), m_focus.decoration.data()); emit decorationChanged(); } void InputDeviceHandler::setInternalWindow(QWindow *window) { m_focus.internalWindow = window; //TODO: call internalWindowUpdate? } void InputDeviceHandler::updateFocus() { auto oldFocus = m_focus.focus; - m_focus.focus = m_at; + + if (m_at.at && !m_at.at->surface()) { + // The surface has not yet been created (special XWayland case). + // Therefore listen for its creation. + if (!m_at.surfaceCreatedConnection) { + m_at.surfaceCreatedConnection = connect(m_at.at, &Toplevel::surfaceChanged, + this, &InputDeviceHandler::update); + } + m_focus.focus = nullptr; + } else { + m_focus.focus = m_at.at; + } + focusUpdate(oldFocus, m_focus.focus); } bool InputDeviceHandler::updateDecoration() { const auto oldDeco = m_focus.decoration; m_focus.decoration = nullptr; - auto *ac = qobject_cast(m_at); + auto *ac = qobject_cast(m_at.at); if (ac && ac->decoratedClient()) { const QRect clientRect = QRect(ac->clientPos(), ac->clientSize()).translated(ac->pos()); if (!clientRect.contains(position().toPoint())) { // input device above decoration m_focus.decoration = ac->decoratedClient(); } } if (m_focus.decoration == oldDeco) { // no change to decoration return false; } cleanupDecoration(oldDeco.data(), m_focus.decoration.data()); emit decorationChanged(); return true; } void InputDeviceHandler::updateInternalWindow(QWindow *window) { if (m_focus.internalWindow == window) { // no change return; } const auto oldInternal = m_focus.internalWindow; m_focus.internalWindow = window; cleanupInternalWindow(oldInternal, window); } void InputDeviceHandler::update() { if (!m_inited) { return; } Toplevel *toplevel = nullptr; QWindow *internalWindow = nullptr; if (!positionValid()) { const auto pos = position().toPoint(); internalWindow = findInternalWindow(pos); if (internalWindow) { toplevel = waylandServer()->findClient(internalWindow); } else { toplevel = input()->findToplevel(pos); } } // Always set the toplevel at the position of the input device. setAt(toplevel); if (focusUpdatesBlocked()) { return; } if (internalWindow) { if (m_focus.internalWindow != internalWindow) { // changed internal window updateDecoration(); updateInternalWindow(internalWindow); updateFocus(); } else if (updateDecoration()) { // went onto or off from decoration, update focus updateFocus(); } return; } updateInternalWindow(nullptr); - if (m_focus.focus != m_at) { + if (m_focus.focus != m_at.at) { // focus change updateDecoration(); updateFocus(); return; } // check if switched to/from decoration while staying on the same Toplevel if (updateDecoration()) { // went onto or off from decoration, update focus updateFocus(); } } QWindow* InputDeviceHandler::findInternalWindow(const QPoint &pos) const { if (waylandServer()->isScreenLocked()) { return nullptr; } const auto &internalClients = waylandServer()->internalClients(); if (internalClients.isEmpty()) { return nullptr; } auto it = internalClients.end(); do { --it; QWindow *w = (*it)->internalWindow(); if (!w || !w->isVisible()) { continue; } if (!(*it)->geometry().contains(pos)) { continue; } // check input mask const QRegion mask = w->mask().translated(w->geometry().topLeft()); if (!mask.isEmpty() && !mask.contains(pos)) { continue; } if (w->property("outputOnly").toBool()) { continue; } return w; } while (it != internalClients.begin()); return nullptr; } } // namespace diff --git a/input.h b/input.h index 588674d79..fd1fbdfc2 100644 --- a/input.h +++ b/input.h @@ -1,492 +1,496 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin Copyright (C) 2018 Roman Gilg Copyright (C) 2019 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_INPUT_H #define KWIN_INPUT_H #include #include #include #include #include #include #include #include class KGlobalAccelInterface; class QKeySequence; class QMouseEvent; class QKeyEvent; class QWheelEvent; namespace KWin { class GlobalShortcutsManager; class Toplevel; class InputEventFilter; class InputEventSpy; class KeyboardInputRedirection; class PointerInputRedirection; class TouchInputRedirection; class WindowSelectorFilter; class SwitchEvent; namespace Decoration { class DecoratedClientImpl; } namespace LibInput { class Connection; } /** * @brief This class is responsible for redirecting incoming input to the surface which currently * has input or send enter/leave events. * * In addition input is intercepted before passed to the surfaces to have KWin internal areas * getting input first (e.g. screen edges) and filter the input event out if we currently have * a full input grab. **/ class KWIN_EXPORT InputRedirection : public QObject { Q_OBJECT public: enum PointerButtonState { PointerButtonReleased, PointerButtonPressed }; enum PointerAxis { PointerAxisVertical, PointerAxisHorizontal }; enum PointerAxisSource { PointerAxisSourceUnknown, PointerAxisSourceWheel, PointerAxisSourceFinger, PointerAxisSourceContinuous, PointerAxisSourceWheelTilt }; enum KeyboardKeyState { KeyboardKeyReleased, KeyboardKeyPressed, KeyboardKeyAutoRepeat }; virtual ~InputRedirection(); void init(); /** * @return const QPointF& The current global pointer position **/ QPointF globalPointer() const; Qt::MouseButtons qtButtonStates() const; Qt::KeyboardModifiers keyboardModifiers() const; Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const; void registerShortcut(const QKeySequence &shortcut, QAction *action); /** * @overload * * Like registerShortcut, but also connects QAction::triggered to the @p slot on @p receiver. * It's recommended to use this method as it ensures that the X11 timestamp is updated prior * to the @p slot being invoked. If not using this overload it's required to ensure that * registerShortcut is called before connecting to QAction's triggered signal. **/ template void registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, Slot slot); void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action); void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action); void registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action); void registerGlobalAccel(KGlobalAccelInterface *interface); /** * @internal **/ void processPointerMotion(const QPointF &pos, uint32_t time); /** * @internal **/ void processPointerButton(uint32_t button, PointerButtonState state, uint32_t time); /** * @internal **/ void processPointerAxis(PointerAxis axis, qreal delta, qint32 discreteDelta, PointerAxisSource source, uint32_t time); /** * @internal **/ void processKeyboardKey(uint32_t key, KeyboardKeyState state, uint32_t time); /** * @internal **/ void processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); /** * @internal **/ void processKeymapChange(int fd, uint32_t size); void processTouchDown(qint32 id, const QPointF &pos, quint32 time); void processTouchUp(qint32 id, quint32 time); void processTouchMotion(qint32 id, const QPointF &pos, quint32 time); void cancelTouch(); void touchFrame(); bool supportsPointerWarping() const; void warpPointer(const QPointF &pos); /** * Adds the @p filter to the list of event filters and makes it the first * event filter in processing. * * Note: the event filter will get events before the lock screen can get them, thus * this is a security relevant method. **/ void prependInputEventFilter(InputEventFilter *filter); void uninstallInputEventFilter(InputEventFilter *filter); /** * Installs the @p spy for spying on events. **/ void installInputEventSpy(InputEventSpy *spy); /** * Uninstalls the @p spy. This happens automatically when deleting an InputEventSpy. **/ void uninstallInputEventSpy(InputEventSpy *spy); Toplevel *findToplevel(const QPoint &pos); Toplevel *findManagedToplevel(const QPoint &pos); GlobalShortcutsManager *shortcuts() const { return m_shortcuts; } /** * Sends an event through all InputFilters. * The method @p function is invoked on each input filter. Processing is stopped if * a filter returns @c true for @p function. * * The UnaryPredicate is defined like the UnaryPredicate of std::any_of. * The signature of the function should be equivalent to the following: * @code * bool function(const InputEventFilter *spy); * @endcode * * The intended usage is to std::bind the method to invoke on the filter with all arguments * bind. **/ template void processFilters(UnaryPredicate function) { std::any_of(m_filters.constBegin(), m_filters.constEnd(), function); } /** * Sends an event through all input event spies. * The @p function is invoked on each InputEventSpy. * * The UnaryFunction is defined like the UnaryFunction of std::for_each. * The signature of the function should be equivalent to the following: * @code * void function(const InputEventSpy *spy); * @endcode * * The intended usage is to std::bind the method to invoke on the spies with all arguments * bind. **/ template void processSpies(UnaryFunction function) { std::for_each(m_spies.constBegin(), m_spies.constEnd(), function); } KeyboardInputRedirection *keyboard() const { return m_keyboard; } PointerInputRedirection *pointer() const { return m_pointer; } TouchInputRedirection *touch() const { return m_touch; } bool hasAlphaNumericKeyboard(); bool hasTabletModeSwitch(); void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName); void startInteractivePositionSelection(std::function callback); bool isSelectingWindow() const; Q_SIGNALS: /** * @brief Emitted when the global pointer position changed * * @param pos The new global pointer position. **/ void globalPointerChanged(const QPointF &pos); /** * @brief Emitted when the state of a pointer button changed. * * @param button The button which changed * @param state The new button state **/ void pointerButtonStateChanged(uint32_t button, InputRedirection::PointerButtonState state); /** * @brief Emitted when a pointer axis changed * * @param axis The axis on which the even occurred * @param delta The delta of the event. **/ void pointerAxisChanged(InputRedirection::PointerAxis axis, qreal delta); /** * @brief Emitted when the modifiers changes. * * Only emitted for the mask which is provided by Qt::KeyboardModifiers, if other modifiers * change signal is not emitted * * @param newMods The new modifiers state * @param oldMods The previous modifiers state **/ void keyboardModifiersChanged(Qt::KeyboardModifiers newMods, Qt::KeyboardModifiers oldMods); /** * @brief Emitted when the state of a key changed. * * @param keyCode The keycode of the key which changed * @param state The new key state **/ void keyStateChanged(quint32 keyCode, InputRedirection::KeyboardKeyState state); void hasAlphaNumericKeyboardChanged(bool set); void hasTabletModeSwitchChanged(bool set); private: void setupLibInput(); void setupTouchpadShortcuts(); void setupLibInputWithScreens(); void setupWorkspace(); void reconfigure(); void setupInputFilters(); void installInputEventFilter(InputEventFilter *filter); KeyboardInputRedirection *m_keyboard; PointerInputRedirection *m_pointer; TouchInputRedirection *m_touch; GlobalShortcutsManager *m_shortcuts; LibInput::Connection *m_libInput = nullptr; WindowSelectorFilter *m_windowSelector = nullptr; QVector m_filters; QVector m_spies; 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 KWIN_EXPORT 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); virtual bool pinchGestureBegin(int fingerCount, quint32 time); virtual bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time); virtual bool pinchGestureEnd(quint32 time); virtual bool pinchGestureCancelled(quint32 time); virtual bool swipeGestureBegin(int fingerCount, quint32 time); virtual bool swipeGestureUpdate(const QSizeF &delta, quint32 time); virtual bool swipeGestureEnd(quint32 time); virtual bool swipeGestureCancelled(quint32 time); virtual bool switchEvent(SwitchEvent *event); protected: void passToWaylandServer(QKeyEvent *event); }; class KWIN_EXPORT InputDeviceHandler : public QObject { Q_OBJECT public: virtual ~InputDeviceHandler(); virtual void init(); void update(); /** * @brief First Toplevel currently at the position of the input device * according to the stacking order. * @return Toplevel* at device position. **/ QPointer at() const { - return m_at; + return m_at.at; } /** * @brief Toplevel currently having pointer input focus (this might * be different from the Toplevel at the position of the pointer). * @return Toplevel* with pointer focus. **/ QPointer focus() const { return m_focus.focus; } /** * @brief The Decoration currently receiving events. * @return decoration with pointer focus. **/ QPointer decoration() const { return m_focus.decoration; } /** * @brief The internal window currently receiving events. * @return QWindow with pointer focus. **/ QPointer internalWindow() const { return m_focus.internalWindow; } virtual QPointF position() const = 0; void setFocus(Toplevel *toplevel); void setDecoration(QPointer decoration); void setInternalWindow(QWindow *window); Q_SIGNALS: void atChanged(Toplevel *old, Toplevel *now); void decorationChanged(); protected: explicit InputDeviceHandler(InputRedirection *parent); virtual void cleanupInternalWindow(QWindow *old, QWindow *now) = 0; virtual void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) = 0; virtual void focusUpdate(Toplevel *old, Toplevel *now) = 0; /** * Certain input devices can be in a state of having no valid * position. An example are touch screens when no finger/pen * is resting on the surface (no touch point). **/ virtual bool positionValid() const { return false; } virtual bool focusUpdatesBlocked() { return false; } inline bool inited() const { return m_inited; } inline void setInited(bool set) { m_inited = set; } private: bool setAt(Toplevel *toplevel); void updateFocus(); bool updateDecoration(); void updateInternalWindow(QWindow *window); QWindow* findInternalWindow(const QPoint &pos) const; - QPointer m_at; + struct { + QPointer at; + QMetaObject::Connection surfaceCreatedConnection; + } m_at; + struct { QPointer focus; QPointer decoration; QPointer internalWindow; } m_focus; bool m_inited = false; }; inline InputRedirection *input() { return InputRedirection::s_self; } template inline void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, Slot slot) { registerShortcut(shortcut, action); connect(action, &QAction::triggered, receiver, slot); } } // namespace KWin Q_DECLARE_METATYPE(KWin::InputRedirection::KeyboardKeyState) Q_DECLARE_METATYPE(KWin::InputRedirection::PointerButtonState) Q_DECLARE_METATYPE(KWin::InputRedirection::PointerAxis) Q_DECLARE_METATYPE(KWin::InputRedirection::PointerAxisSource) #endif // KWIN_INPUT_H