kwin/input.cpp

2263 lines
79 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#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 "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 <KWayland/Server/display.h>
#include <KWayland/Server/fakeinput_interface.h>
#include <KWayland/Server/seat_interface.h>
#include <KWayland/Server/relativepointer_interface.h>
#include <decorations/decoratedclient.h>
#include <KDecoration2/Decoration>
#include <KGlobalAccel>
//screenlocker
#include <KScreenLocker/KsldApp>
// Qt
#include <QKeyEvent>
#include <xkbcommon/xkbcommon.h>
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) {
input()->pointer()->update();
if (pointerSurfaceAllowed()) {
seat->setPointerPos(event->screenPos().toPoint());
}
} else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) {
if (pointerSurfaceAllowed()) {
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 (!seat->isTouchSequence()) {
input()->touch()->update(pos);
}
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 PointerConstraintsFilter : public InputEventFilter {
public:
explicit PointerConstraintsFilter()
: InputEventFilter()
, m_timer(new QTimer)
{
QObject::connect(m_timer.data(), &QTimer::timeout,
[this] {
if (waylandServer()) {
// break keyboard focus, this cancels the pressed ESC
waylandServer()->seat()->setFocusedKeyboardSurface(nullptr);
}
input()->pointer()->breakPointerConstraints();
input()->pointer()->blockPointerConstraints();
// TODO: show notification
waylandServer()->seat()->keyReleased(m_keyCode);
cancel();
}
);
}
bool keyEvent(QKeyEvent *event) override {
if (isActive()) {
if (event->type() == QEvent::KeyPress) {
// is that another key that gets pressed?
if (!event->isAutoRepeat() && event->key() != Qt::Key_Escape) {
cancel();
return false;
}
if (event->isAutoRepeat() && event->key() == Qt::Key_Escape) {
// filter out
return true;
}
} else {
cancel();
return false;
}
} else if (input()->pointer()->isConstrained()) {
if (event->type() == QEvent::KeyPress &&
event->key() == Qt::Key_Escape &&
static_cast<KeyEvent*>(event)->modifiersRelevantForGlobalShortcuts() == Qt::KeyboardModifiers()) {
// TODO: don't hard code
m_timer->start(3000);
m_keyCode = event->nativeScanCode();
return false;
}
}
return false;
}
void cancel() {
if (!isActive()) {
return;
}
m_timer->stop();
input()->keyboard()->update();
}
bool isActive() const {
return m_timer->isActive();
}
private:
QScopedPointer<QTimer> m_timer;
int m_keyCode = 0;
};
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;
}
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()->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 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<void(KWin::Toplevel*)> callback) {
Q_ASSERT(!m_active);
m_active = true;
m_callback = callback;
input()->keyboard()->update();
input()->cancelTouch();
}
void start(std::function<void(const QPoint &)> 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<void(KWin::Toplevel*)>();
m_pointSelectionFallback = std::function<void(const QPoint &)>();
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<void(KWin::Toplevel*)> m_callback;
std::function<void(const QPoint &)> m_pointSelectionFallback;
QMap<quint32, QPointF> 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<KeyEvent*>(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<bool, bool> performClientMouseAction(QMouseEvent *event, AbstractClient *client, MouseAction action = MouseAction::ModifierOnly)
{
Options::MouseCommand command = Options::MouseNothing;
bool wasAction = false;
if (static_cast<MouseEvent*>(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) {
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<bool, bool> performClientWheelAction(QWheelEvent *event, AbstractClient *c, MouseAction action = MouseAction::ModifierOnly)
{
bool wasAction = false;
Options::MouseCommand command = Options::MouseNothing;
if (static_cast<WheelEvent*>(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) {
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;
}
if (event->buttons() == Qt::NoButton) {
// update pointer window only if no button is pressed
input()->pointer()->update();
}
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 a decoration, ignore further touch points, but filter out
return true;
}
// a new touch point
seat->setTimestamp(time);
touch->update(pos);
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());
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);
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: {
if (event->buttons() == Qt::NoButton) {
return false;
}
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);
input()->touch()->update(pos);
auto decoration = input()->touch()->decoration();
if (!decoration) {
return false;
}
input()->touch()->setDecorationPressId(id);
m_lastGlobalTouchPos = pos;
m_lastLocalTouchPos = pos - decoration->client()->pos();
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();
if (auto c = workspace()->getMovingClient()) {
c->updateMoveResize(pos);
} else {
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
if (auto c = workspace()->getMovingClient()) {
c->endMoveResize();
} else {
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);
if (input()->pointer()->decoration() == decoration) {
// send motion to current pointer position
const QPointF p = input()->pointer()->pos() - decoration->client()->pos();
QHoverEvent event(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(decoration->decoration(), &event);
} else {
// send leave
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(decoration->decoration(), &event);
}
}
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<KeyEvent*>(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<AbstractClient*>(input()->pointer()->window().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<AbstractClient*>(input()->pointer()->window().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;
}
input()->touch()->update(pos);
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->touch()->window().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: {
if (event->buttons() == Qt::NoButton) {
// update pointer window only if no button is pressed
input()->pointer()->update();
input()->pointer()->updatePointerConstraints();
}
seat->setPointerPos(event->globalPos());
MouseEvent *e = static_cast<MouseEvent*>(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);
if (event->buttons() == Qt::NoButton) {
input()->pointer()->update();
}
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;
}
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);
if (!seat->isTouchSequence()) {
input()->touch()->update(pos);
}
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;
}
seat->setTimestamp(event->timestamp());
switch (event->type()) {
case QEvent::MouseMove: {
if (Toplevel *t = input()->findToplevel(event->globalPos())) {
// TODO: consider decorations
if (t->surface() != seat->dragSurface()) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(t)) {
workspace()->raiseClient(c);
}
seat->setPointerPos(event->globalPos());
seat->setDragTarget(t->surface(), event->globalPos(), t->inputTransformation());
}
} else {
// no window at that place, if we have a surface we need to reset
seat->setDragTarget(nullptr);
}
seat->setPointerPos(event->globalPos());
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;
}
};
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<KWin::InputRedirection::KeyboardKeyState>();
qRegisterMetaType<KWin::InputRedirection::PointerButtonState>();
qRegisterMetaType<KWin::InputRedirection::PointerAxis>();
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::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);
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()
{
if (LogindIntegration::self()->hasSessionControl()) {
installInputEventFilter(new VirtualTerminalFilter);
}
if (waylandServer()) {
installInputEventFilter(new TerminateServerFilter);
installInputEventFilter(new DragAndDropInputFilter);
installInputEventFilter(new LockScreenFilter);
installInputEventFilter(new PopupInputFilter);
m_pointerConstraintsFilter = new PointerConstraintsFilter;
installInputEventFilter(m_pointerConstraintsFilter);
m_windowSelector = new WindowSelectorFilter;
installInputEventFilter(m_windowSelector);
}
installInputEventFilter(new ScreenEdgeInputFilter);
installInputEventFilter(new EffectsFilter);
installInputEventFilter(new MoveResizeFilter);
#ifdef KWIN_BUILD_TABBOX
installInputEventFilter(new TabBoxInputFilter);
#endif
installInputEventFilter(new GlobalShortcutFilter);
installInputEventFilter(new InternalWindowEventFilter);
installInputEventFilter(new DecorationEventFilter);
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<QKeySequence>{Qt::Key_TouchpadToggle});
KGlobalAccel::self()->setShortcut(touchpadToggleAction, QList<QKeySequence>{Qt::Key_TouchpadToggle});
KGlobalAccel::self()->setDefaultShortcut(touchpadOnAction, QList<QKeySequence>{Qt::Key_TouchpadOn});
KGlobalAccel::self()->setShortcut(touchpadOnAction, QList<QKeySequence>{Qt::Key_TouchpadOn});
KGlobalAccel::self()->setDefaultShortcut(touchpadOffAction, QList<QKeySequence>{Qt::Key_TouchpadOff});
KGlobalAccel::self()->setShortcut(touchpadOffAction, QList<QKeySequence>{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, uint32_t time)
{
m_pointer->processAxis(axis, delta, 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<EffectsHandlerImpl*>(effects)->isMouseInterception()) {
return nullptr;
}
const UnmanagedList &unmanaged = Workspace::self()->unmanagedList();
foreach (Unmanaged *u, unmanaged) {
if (u->geometry().contains(pos) && acceptsInput(u, pos)) {
return u;
}
}
}
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<AbstractClient*>(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<void(KWin::Toplevel*)> 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<void(const QPoint &)> 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;
}
bool InputRedirection::isBreakingPointerConstraints() const
{
return m_pointerConstraintsFilter ? m_pointerConstraintsFilter->isActive() : false;
}
InputDeviceHandler::InputDeviceHandler(InputRedirection *input)
: QObject(input)
, m_input(input)
{
}
InputDeviceHandler::~InputDeviceHandler() = default;
void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos)
{
const auto oldDeco = m_decoration;
bool needsReset = waylandServer()->isScreenLocked();
if (AbstractClient *c = dynamic_cast<AbstractClient*>(t)) {
// check whether it's on a Decoration
if (c->decoratedClient()) {
const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos());
if (!clientRect.contains(pos.toPoint())) {
m_decoration = c->decoratedClient();
} else {
needsReset = true;
}
} else {
needsReset = true;
}
} else {
needsReset = true;
}
if (needsReset) {
m_decoration.clear();
}
bool leftSend = false;
auto oldWindow = qobject_cast<AbstractClient*>(m_window.data());
if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) {
leftSend = true;
oldWindow->leaveEvent();
}
if (oldDeco && oldDeco != m_decoration) {
if (oldDeco->client() != t && !leftSend) {
leftSend = true;
oldDeco->client()->leaveEvent();
}
// send leave
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event);
}
if (m_decoration) {
if (m_decoration->client() != oldWindow) {
m_decoration->client()->enterEvent(pos.toPoint());
workspace()->updateFocusMousePosition(pos.toPoint());
}
const QPointF p = pos - t->pos();
QHoverEvent event(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event);
m_decoration->client()->processDecorationMove(p.toPoint(), pos.toPoint());
}
}
void InputDeviceHandler::updateInternalWindow(const QPointF &pos)
{
const auto oldInternalWindow = m_internalWindow;
bool found = false;
// TODO: screen locked check without going through wayland server
bool needsReset = waylandServer()->isScreenLocked();
const auto &internalClients = waylandServer()->internalClients();
const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible());
if (!internalClients.isEmpty() && change) {
auto it = internalClients.end();
do {
it--;
if (QWindow *w = (*it)->internalWindow()) {
if (!w->isVisible()) {
continue;
}
if ((*it)->geometry().contains(pos.toPoint())) {
// check input mask
const QRegion mask = w->mask().translated(w->geometry().topLeft());
if (!mask.isEmpty() && !mask.contains(pos.toPoint())) {
continue;
}
if (w->property("outputOnly").toBool()) {
continue;
}
m_internalWindow = QPointer<QWindow>(w);
found = true;
break;
}
}
} while (it != internalClients.begin());
if (!found) {
needsReset = true;
}
}
if (needsReset) {
m_internalWindow.clear();
}
if (oldInternalWindow != m_internalWindow) {
// changed
if (oldInternalWindow) {
QEvent event(QEvent::Leave);
QCoreApplication::sendEvent(oldInternalWindow.data(), &event);
}
if (m_internalWindow) {
QEnterEvent event(pos - m_internalWindow->position(),
pos - m_internalWindow->position(),
pos);
QCoreApplication::sendEvent(m_internalWindow.data(), &event);
}
emit internalWindowChanged();
}
}
} // namespace