Rework InputDeviceHandler focus tracking

Summary:
This patch aims at improving the Toplevel, internal window and decoration
focus tracking.

In detail the goals are:
* Clean tracking of beneath and focus Toplevel as well as decoration and
internal windows. Splitting this up in well defined sub routines.
* Minimal find Toplevel operations on window stack.
* Reduce code duplication in pointer and touch child classes.
* Reuse tracking in drag operations.
* Allow direct usage of Wayland input interfaces for decoration and internal
windows in the future.
* Update touch focus on external events like VD switches correctly.

Test Plan: Manually and existing autotests.

Reviewers: #kwin

Subscribers: kwin, zzag

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D15595
This commit is contained in:
Roman Gilg 2018-09-15 02:00:24 +02:00
parent eab71a8a19
commit 2e29711323
11 changed files with 639 additions and 431 deletions

View file

@ -836,8 +836,8 @@ void DecorationInputTest::testTouchEvents()
QCOMPARE(hoverMoveSpy.count(), 3); QCOMPARE(hoverMoveSpy.count(), 3);
QCOMPARE(hoverLeaveSpy.count(), 1); QCOMPARE(hoverLeaveSpy.count(), 1);
kwinApp()->platform()->touchUp(0, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++);
QCOMPARE(hoverMoveSpy.count(), 4); QCOMPARE(hoverMoveSpy.count(), 3);
QCOMPARE(hoverLeaveSpy.count(), 1); QCOMPARE(hoverLeaveSpy.count(), 2);
} }
void DecorationInputTest::testTooltipDoesntEatKeyEvents_data() void DecorationInputTest::testTooltipDoesntEatKeyEvents_data()

View file

@ -230,11 +230,11 @@ void InternalWindowTest::testEnterLeave()
quint32 timestamp = 1; quint32 timestamp = 1;
kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++);
QTRY_COMPARE(enterSpy.count(), 1); QTRY_COMPARE(moveSpy.count(), 1);
kwinApp()->platform()->pointerMotion(QPoint(60, 50), timestamp++); kwinApp()->platform()->pointerMotion(QPoint(60, 50), timestamp++);
QTRY_COMPARE(moveSpy.count(), 1); QTRY_COMPARE(moveSpy.count(), 2);
QCOMPARE(moveSpy.first().first().toPoint(), QPoint(60, 50)); QCOMPARE(moveSpy[1].first().toPoint(), QPoint(60, 50));
kwinApp()->platform()->pointerMotion(QPoint(101, 50), timestamp++); kwinApp()->platform()->pointerMotion(QPoint(101, 50), timestamp++);
QTRY_COMPARE(leaveSpy.count(), 1); QTRY_COMPARE(leaveSpy.count(), 1);

View file

@ -232,7 +232,7 @@ void TestPointerConstraints::testConfinedPointer()
QVERIFY(unconfinedSpy2.isValid()); QVERIFY(unconfinedSpy2.isValid());
// activate it again, this confines again // activate it again, this confines again
workspace()->activateClient(static_cast<AbstractClient*>(input()->pointer()->window().data())); workspace()->activateClient(static_cast<AbstractClient*>(input()->pointer()->focus().data()));
QVERIFY(confinedSpy2.wait()); QVERIFY(confinedSpy2.wait());
QCOMPARE(input()->pointer()->isConstrained(), true); QCOMPARE(input()->pointer()->isConstrained(), true);
@ -241,7 +241,7 @@ void TestPointerConstraints::testConfinedPointer()
QVERIFY(unconfinedSpy2.wait()); QVERIFY(unconfinedSpy2.wait());
QCOMPARE(input()->pointer()->isConstrained(), false); QCOMPARE(input()->pointer()->isConstrained(), false);
// activate it again, this confines again // activate it again, this confines again
workspace()->activateClient(static_cast<AbstractClient*>(input()->pointer()->window().data())); workspace()->activateClient(static_cast<AbstractClient*>(input()->pointer()->focus().data()));
QVERIFY(confinedSpy2.wait()); QVERIFY(confinedSpy2.wait());
QCOMPARE(input()->pointer()->isConstrained(), true); QCOMPARE(input()->pointer()->isConstrained(), true);
@ -272,7 +272,7 @@ void TestPointerConstraints::testConfinedPointer()
confinedPointer.reset(nullptr); confinedPointer.reset(nullptr);
Test::flushWaylandConnection(); Test::flushWaylandConnection();
QSignalSpy constraintsChangedSpy(input()->pointer()->window()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged); QSignalSpy constraintsChangedSpy(input()->pointer()->focus()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged);
QVERIFY(constraintsChangedSpy.isValid()); QVERIFY(constraintsChangedSpy.isValid());
QVERIFY(constraintsChangedSpy.wait()); QVERIFY(constraintsChangedSpy.wait());
@ -348,7 +348,7 @@ void TestPointerConstraints::testLockedPointer()
QVERIFY(lockedSpy2.isValid()); QVERIFY(lockedSpy2.isValid());
// activate the client again, this should lock again // activate the client again, this should lock again
workspace()->activateClient(static_cast<AbstractClient*>(input()->pointer()->window().data())); workspace()->activateClient(static_cast<AbstractClient*>(input()->pointer()->focus().data()));
QVERIFY(lockedSpy2.wait()); QVERIFY(lockedSpy2.wait());
QCOMPARE(input()->pointer()->isConstrained(), true); QCOMPARE(input()->pointer()->isConstrained(), true);
@ -361,7 +361,7 @@ void TestPointerConstraints::testLockedPointer()
lockedPointer.reset(nullptr); lockedPointer.reset(nullptr);
Test::flushWaylandConnection(); Test::flushWaylandConnection();
QSignalSpy constraintsChangedSpy(input()->pointer()->window()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged); QSignalSpy constraintsChangedSpy(input()->pointer()->focus()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged);
QVERIFY(constraintsChangedSpy.isValid()); QVERIFY(constraintsChangedSpy.isValid());
QVERIFY(constraintsChangedSpy.wait()); QVERIFY(constraintsChangedSpy.wait());

View file

@ -963,7 +963,7 @@ void PointerInputTest::testCursorImage()
// move cursor to center of window, this should first set a null pointer, so we still show old cursor // move cursor to center of window, this should first set a null pointer, so we still show old cursor
Cursor::setPos(window->geometry().center()); Cursor::setPos(window->geometry().center());
QCOMPARE(p->window().data(), window); QCOMPARE(p->focus().data(), window);
QCOMPARE(p->cursorImage(), fallbackCursor); QCOMPARE(p->cursorImage(), fallbackCursor);
QVERIFY(enteredSpy.wait()); QVERIFY(enteredSpy.wait());
@ -1018,7 +1018,7 @@ void PointerInputTest::testCursorImage()
// move cursor somewhere else, should reset to fallback cursor // move cursor somewhere else, should reset to fallback cursor
Cursor::setPos(window->geometry().bottomLeft() + QPoint(20, 20)); Cursor::setPos(window->geometry().bottomLeft() + QPoint(20, 20));
QVERIFY(p->window().isNull()); QVERIFY(p->focus().isNull());
QVERIFY(!p->cursorImage().isNull()); QVERIFY(!p->cursorImage().isNull());
QCOMPARE(p->cursorImage(), fallbackCursor); QCOMPARE(p->cursorImage(), fallbackCursor);
} }

View file

@ -115,7 +115,7 @@ void TestWindowSelection::testSelectOnWindowPointer()
QVERIFY(client); QVERIFY(client);
QVERIFY(keyboardEnteredSpy.wait()); QVERIFY(keyboardEnteredSpy.wait());
KWin::Cursor::setPos(client->geometry().center()); KWin::Cursor::setPos(client->geometry().center());
QCOMPARE(input()->pointer()->window().data(), client); QCOMPARE(input()->pointer()->focus().data(), client);
QVERIFY(pointerEnteredSpy.wait()); QVERIFY(pointerEnteredSpy.wait());
Toplevel *selectedWindow = nullptr; Toplevel *selectedWindow = nullptr;
@ -142,11 +142,11 @@ void TestWindowSelection::testSelectOnWindowPointer()
// should not have ended the mode // should not have ended the mode
QCOMPARE(input()->isSelectingWindow(), true); QCOMPARE(input()->isSelectingWindow(), true);
QVERIFY(!selectedWindow); QVERIFY(!selectedWindow);
QVERIFY(input()->pointer()->window().isNull()); QVERIFY(input()->pointer()->focus().isNull());
// updating the pointer should not change anything // updating the pointer should not change anything
input()->pointer()->update(); input()->pointer()->update();
QVERIFY(input()->pointer()->window().isNull()); QVERIFY(input()->pointer()->focus().isNull());
// updating keyboard should also not change // updating keyboard should also not change
input()->keyboard()->update(); input()->keyboard()->update();
@ -160,7 +160,7 @@ void TestWindowSelection::testSelectOnWindowPointer()
kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++);
QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(input()->isSelectingWindow(), false);
QCOMPARE(selectedWindow, client); QCOMPARE(selectedWindow, client);
QCOMPARE(input()->pointer()->window().data(), client); QCOMPARE(input()->pointer()->focus().data(), client);
// should give back keyboard and pointer // should give back keyboard and pointer
QVERIFY(pointerEnteredSpy.wait()); QVERIFY(pointerEnteredSpy.wait());
if (keyboardEnteredSpy.count() != 2) { if (keyboardEnteredSpy.count() != 2) {
@ -240,7 +240,7 @@ void TestWindowSelection::testSelectOnWindowKeyboard()
kwinApp()->platform()->keyboardKeyPressed(key, timestamp++); kwinApp()->platform()->keyboardKeyPressed(key, timestamp++);
QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(input()->isSelectingWindow(), false);
QCOMPARE(selectedWindow, client); QCOMPARE(selectedWindow, client);
QCOMPARE(input()->pointer()->window().data(), client); QCOMPARE(input()->pointer()->focus().data(), client);
// should give back keyboard and pointer // should give back keyboard and pointer
QVERIFY(pointerEnteredSpy.wait()); QVERIFY(pointerEnteredSpy.wait());
if (keyboardEnteredSpy.count() != 2) { if (keyboardEnteredSpy.count() != 2) {
@ -336,7 +336,7 @@ void TestWindowSelection::testCancelOnWindowPointer()
QVERIFY(client); QVERIFY(client);
QVERIFY(keyboardEnteredSpy.wait()); QVERIFY(keyboardEnteredSpy.wait());
KWin::Cursor::setPos(client->geometry().center()); KWin::Cursor::setPos(client->geometry().center());
QCOMPARE(input()->pointer()->window().data(), client); QCOMPARE(input()->pointer()->focus().data(), client);
QVERIFY(pointerEnteredSpy.wait()); QVERIFY(pointerEnteredSpy.wait());
Toplevel *selectedWindow = nullptr; Toplevel *selectedWindow = nullptr;
@ -363,7 +363,7 @@ void TestWindowSelection::testCancelOnWindowPointer()
kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, timestamp++);
QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(input()->isSelectingWindow(), false);
QVERIFY(!selectedWindow); QVERIFY(!selectedWindow);
QCOMPARE(input()->pointer()->window().data(), client); QCOMPARE(input()->pointer()->focus().data(), client);
// should give back keyboard and pointer // should give back keyboard and pointer
QVERIFY(pointerEnteredSpy.wait()); QVERIFY(pointerEnteredSpy.wait());
if (keyboardEnteredSpy.count() != 2) { if (keyboardEnteredSpy.count() != 2) {
@ -395,7 +395,7 @@ void TestWindowSelection::testCancelOnWindowKeyboard()
QVERIFY(client); QVERIFY(client);
QVERIFY(keyboardEnteredSpy.wait()); QVERIFY(keyboardEnteredSpy.wait());
KWin::Cursor::setPos(client->geometry().center()); KWin::Cursor::setPos(client->geometry().center());
QCOMPARE(input()->pointer()->window().data(), client); QCOMPARE(input()->pointer()->focus().data(), client);
QVERIFY(pointerEnteredSpy.wait()); QVERIFY(pointerEnteredSpy.wait());
Toplevel *selectedWindow = nullptr; Toplevel *selectedWindow = nullptr;
@ -421,7 +421,7 @@ void TestWindowSelection::testCancelOnWindowKeyboard()
kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++);
QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(input()->isSelectingWindow(), false);
QVERIFY(!selectedWindow); QVERIFY(!selectedWindow);
QCOMPARE(input()->pointer()->window().data(), client); QCOMPARE(input()->pointer()->focus().data(), client);
// should give back keyboard and pointer // should give back keyboard and pointer
QVERIFY(pointerEnteredSpy.wait()); QVERIFY(pointerEnteredSpy.wait());
if (keyboardEnteredSpy.count() != 2) { if (keyboardEnteredSpy.count() != 2) {
@ -454,7 +454,7 @@ void TestWindowSelection::testSelectPointPointer()
QVERIFY(client); QVERIFY(client);
QVERIFY(keyboardEnteredSpy.wait()); QVERIFY(keyboardEnteredSpy.wait());
KWin::Cursor::setPos(client->geometry().center()); KWin::Cursor::setPos(client->geometry().center());
QCOMPARE(input()->pointer()->window().data(), client); QCOMPARE(input()->pointer()->focus().data(), client);
QVERIFY(pointerEnteredSpy.wait()); QVERIFY(pointerEnteredSpy.wait());
QPoint point; QPoint point;
@ -488,11 +488,11 @@ void TestWindowSelection::testSelectPointPointer()
// should not have ended the mode // should not have ended the mode
QCOMPARE(input()->isSelectingWindow(), true); QCOMPARE(input()->isSelectingWindow(), true);
QCOMPARE(point, QPoint()); QCOMPARE(point, QPoint());
QVERIFY(input()->pointer()->window().isNull()); QVERIFY(input()->pointer()->focus().isNull());
// updating the pointer should not change anything // updating the pointer should not change anything
input()->pointer()->update(); input()->pointer()->update();
QVERIFY(input()->pointer()->window().isNull()); QVERIFY(input()->pointer()->focus().isNull());
// updating keyboard should also not change // updating keyboard should also not change
input()->keyboard()->update(); input()->keyboard()->update();
@ -506,7 +506,7 @@ void TestWindowSelection::testSelectPointPointer()
kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++);
QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(input()->isSelectingWindow(), false);
QCOMPARE(point, input()->globalPointer().toPoint()); QCOMPARE(point, input()->globalPointer().toPoint());
QCOMPARE(input()->pointer()->window().data(), client); QCOMPARE(input()->pointer()->focus().data(), client);
// should give back keyboard and pointer // should give back keyboard and pointer
QVERIFY(pointerEnteredSpy.wait()); QVERIFY(pointerEnteredSpy.wait());
if (keyboardEnteredSpy.count() != 2) { if (keyboardEnteredSpy.count() != 2) {

356
input.cpp
View file

@ -3,6 +3,7 @@
This file is part of the KDE project. This file is part of the KDE project.
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org> Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2018 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -227,12 +228,14 @@ public:
auto seat = waylandServer()->seat(); auto seat = waylandServer()->seat();
seat->setTimestamp(event->timestamp()); seat->setTimestamp(event->timestamp());
if (event->type() == QEvent::MouseMove) { if (event->type() == QEvent::MouseMove) {
input()->pointer()->update();
if (pointerSurfaceAllowed()) { if (pointerSurfaceAllowed()) {
// TODO: should the pointer position always stay in sync, i.e. not do the check?
seat->setPointerPos(event->screenPos().toPoint()); seat->setPointerPos(event->screenPos().toPoint());
} }
} else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) {
if (pointerSurfaceAllowed()) { 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); event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton);
} }
} }
@ -293,9 +296,6 @@ public:
} }
auto seat = waylandServer()->seat(); auto seat = waylandServer()->seat();
seat->setTimestamp(time); seat->setTimestamp(time);
if (!seat->isTouchSequence()) {
input()->touch()->update(pos);
}
if (touchSurfaceAllowed()) { if (touchSurfaceAllowed()) {
input()->touch()->insertId(id, seat->touchDown(pos)); input()->touch()->insertId(id, seat->touchDown(pos));
} }
@ -477,6 +477,52 @@ public:
} }
return true; return true;
} }
bool touchDown(quint32 id, const QPointF &pos, quint32 time) override {
Q_UNUSED(id)
Q_UNUSED(pos)
Q_UNUSED(time)
AbstractClient *c = workspace()->getMovingClient();
if (!c) {
return false;
}
return true;
}
bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override {
Q_UNUSED(time)
AbstractClient *c = workspace()->getMovingClient();
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()->getMovingClient();
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 { class WindowSelectorFilter : public InputEventFilter {
@ -764,13 +810,6 @@ class InternalWindowEventFilter : public InputEventFilter {
if (!internal) { if (!internal) {
return false; 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 // find client
switch (event->type()) switch (event->type())
{ {
@ -883,12 +922,11 @@ class InternalWindowEventFilter : public InputEventFilter {
} }
auto touch = input()->touch(); auto touch = input()->touch();
if (touch->internalPressId() != -1) { if (touch->internalPressId() != -1) {
// already on a decoration, ignore further touch points, but filter out // already on internal window, ignore further touch points, but filter out
return true; return true;
} }
// a new touch point // a new touch point
seat->setTimestamp(time); seat->setTimestamp(time);
touch->update(pos);
auto internal = touch->internalWindow(); auto internal = touch->internalWindow();
if (!internal) { if (!internal) {
return false; return false;
@ -897,6 +935,10 @@ class InternalWindowEventFilter : public InputEventFilter {
// Qt's touch event API is rather complex, let's do fake mouse events instead // Qt's touch event API is rather complex, let's do fake mouse events instead
m_lastGlobalTouchPos = pos; m_lastGlobalTouchPos = pos;
m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); 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()); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers());
e.setAccepted(false); e.setAccepted(false);
QCoreApplication::sendEvent(internal.data(), &e); QCoreApplication::sendEvent(internal.data(), &e);
@ -918,6 +960,7 @@ class InternalWindowEventFilter : public InputEventFilter {
} }
m_lastGlobalTouchPos = pos; m_lastGlobalTouchPos = pos;
m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y());
QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers());
QCoreApplication::instance()->sendEvent(internal.data(), &e); QCoreApplication::instance()->sendEvent(internal.data(), &e);
return true; return true;
@ -941,6 +984,9 @@ class InternalWindowEventFilter : public InputEventFilter {
e.setAccepted(false); e.setAccepted(false);
QCoreApplication::sendEvent(internal.data(), &e); QCoreApplication::sendEvent(internal.data(), &e);
QEvent leaveEvent(QEvent::Leave);
QCoreApplication::sendEvent(internal.data(), &leaveEvent);
m_lastGlobalTouchPos = QPointF(); m_lastGlobalTouchPos = QPointF();
m_lastLocalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF();
input()->touch()->setInternalPressId(-1); input()->touch()->setInternalPressId(-1);
@ -962,9 +1008,6 @@ public:
const QPointF p = event->globalPos() - decoration->client()->pos(); const QPointF p = event->globalPos() - decoration->client()->pos();
switch (event->type()) { switch (event->type()) {
case QEvent::MouseMove: { case QEvent::MouseMove: {
if (event->buttons() == Qt::NoButton) {
return false;
}
QHoverEvent e(QEvent::HoverMove, p, p); QHoverEvent e(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e);
decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos());
@ -1034,14 +1077,18 @@ public:
return true; return true;
} }
seat->setTimestamp(time); seat->setTimestamp(time);
input()->touch()->update(pos);
auto decoration = input()->touch()->decoration(); auto decoration = input()->touch()->decoration();
if (!decoration) { if (!decoration) {
return false; return false;
} }
input()->touch()->setDecorationPressId(id); input()->touch()->setDecorationPressId(id);
m_lastGlobalTouchPos = pos; m_lastGlobalTouchPos = pos;
m_lastLocalTouchPos = pos - decoration->client()->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()); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers());
e.setAccepted(false); e.setAccepted(false);
QCoreApplication::sendEvent(decoration->decoration(), &e); QCoreApplication::sendEvent(decoration->decoration(), &e);
@ -1065,13 +1112,10 @@ public:
} }
m_lastGlobalTouchPos = pos; m_lastGlobalTouchPos = pos;
m_lastLocalTouchPos = pos - decoration->client()->pos(); m_lastLocalTouchPos = pos - decoration->client()->pos();
if (auto c = workspace()->getMovingClient()) {
c->updateMoveResize(pos);
} else {
QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos);
QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e);
decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint()); decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint());
}
return true; return true;
} }
bool touchUp(quint32 id, quint32 time) override { bool touchUp(quint32 id, quint32 time) override {
@ -1087,25 +1131,15 @@ public:
// ignore, but filter out // ignore, but filter out
return true; return true;
} }
// send mouse up // 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()); QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers());
e.setAccepted(false); e.setAccepted(false);
QCoreApplication::sendEvent(decoration->decoration(), &e); QCoreApplication::sendEvent(decoration->decoration(), &e);
decoration->client()->processDecorationButtonRelease(&e); decoration->client()->processDecorationButtonRelease(&e);
if (input()->pointer()->decoration() == decoration) {
// send motion to current pointer position QHoverEvent leaveEvent(QEvent::HoverLeave, QPointF(), QPointF());
const QPointF p = input()->pointer()->pos() - decoration->client()->pos(); QCoreApplication::sendEvent(decoration->decoration(), &leaveEvent);
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_lastGlobalTouchPos = QPointF();
m_lastLocalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF();
@ -1218,7 +1252,7 @@ public:
if (event->type() != QEvent::MouseButtonPress) { if (event->type() != QEvent::MouseButtonPress) {
return false; return false;
} }
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->pointer()->window().data()); AbstractClient *c = dynamic_cast<AbstractClient*>(input()->pointer()->focus().data());
if (!c) { if (!c) {
return false; return false;
} }
@ -1233,7 +1267,7 @@ public:
// only actions on vertical scroll // only actions on vertical scroll
return false; return false;
} }
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->pointer()->window().data()); AbstractClient *c = dynamic_cast<AbstractClient*>(input()->pointer()->focus().data());
if (!c) { if (!c) {
return false; return false;
} }
@ -1250,8 +1284,7 @@ public:
if (seat->isTouchSequence()) { if (seat->isTouchSequence()) {
return false; return false;
} }
input()->touch()->update(pos); AbstractClient *c = dynamic_cast<AbstractClient*>(input()->touch()->focus().data());
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->touch()->window().data());
if (!c) { if (!c) {
return false; return false;
} }
@ -1275,11 +1308,6 @@ public:
seat->setTimestamp(event->timestamp()); seat->setTimestamp(event->timestamp());
switch (event->type()) { switch (event->type()) {
case QEvent::MouseMove: { 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()); seat->setPointerPos(event->globalPos());
MouseEvent *e = static_cast<MouseEvent*>(event); MouseEvent *e = static_cast<MouseEvent*>(event);
if (e->delta() != QSizeF()) { if (e->delta() != QSizeF()) {
@ -1292,9 +1320,6 @@ public:
break; break;
case QEvent::MouseButtonRelease: case QEvent::MouseButtonRelease:
seat->pointerButtonReleased(nativeButton); seat->pointerButtonReleased(nativeButton);
if (event->buttons() == Qt::NoButton) {
input()->pointer()->update();
}
break; break;
default: default:
break; break;
@ -1328,9 +1353,6 @@ public:
} }
auto seat = waylandServer()->seat(); auto seat = waylandServer()->seat();
seat->setTimestamp(time); seat->setTimestamp(time);
if (!seat->isTouchSequence()) {
input()->touch()->update(pos);
}
input()->touch()->insertId(id, seat->touchDown(pos)); input()->touch()->insertId(id, seat->touchDown(pos));
return true; return true;
} }
@ -1447,7 +1469,7 @@ public:
case QEvent::MouseMove: { case QEvent::MouseMove: {
const auto pos = input()->globalPointer(); const auto pos = input()->globalPointer();
seat->setPointerPos(pos); seat->setPointerPos(pos);
if (Toplevel *t = input()->findToplevel(pos.toPoint())) { if (Toplevel *t = input()->pointer()->at()) {
// TODO: consider decorations // TODO: consider decorations
if (t->surface() != seat->dragSurface()) { if (t->surface() != seat->dragSurface()) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(t)) { if (AbstractClient *c = qobject_cast<AbstractClient*>(t)) {
@ -1670,8 +1692,8 @@ void InputRedirection::setupInputFilters()
installInputEventFilter(new TabBoxInputFilter); installInputEventFilter(new TabBoxInputFilter);
#endif #endif
installInputEventFilter(new GlobalShortcutFilter); installInputEventFilter(new GlobalShortcutFilter);
installInputEventFilter(new InternalWindowEventFilter);
installInputEventFilter(new DecorationEventFilter); installInputEventFilter(new DecorationEventFilter);
installInputEventFilter(new InternalWindowEventFilter);
if (waylandServer()) { if (waylandServer()) {
installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new WindowActionInputFilter);
installInputEventFilter(new ForwardInputFilter); installInputEventFilter(new ForwardInputFilter);
@ -2083,109 +2105,167 @@ InputDeviceHandler::InputDeviceHandler(InputRedirection *input)
InputDeviceHandler::~InputDeviceHandler() = default; InputDeviceHandler::~InputDeviceHandler() = default;
void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos) void InputDeviceHandler::init()
{ {
const auto oldDeco = m_decoration; connect(workspace(), &Workspace::stackingOrderChanged, this, &InputDeviceHandler::update);
bool needsReset = waylandServer()->isScreenLocked(); connect(workspace(), &Workspace::clientMinimizedChanged, this, &InputDeviceHandler::update);
if (AbstractClient *c = dynamic_cast<AbstractClient*>(t)) { connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &InputDeviceHandler::update);
// 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; bool InputDeviceHandler::setAt(Toplevel *toplevel)
auto oldWindow = qobject_cast<AbstractClient*>(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; if (m_at == toplevel) {
bool found = false; return false;
// TODO: screen locked check without going through wayland server }
bool needsReset = waylandServer()->isScreenLocked(); auto old = m_at;
m_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::DecoratedClientImpl> 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;
focusUpdate(oldFocus, m_focus.focus);
}
bool InputDeviceHandler::updateDecoration()
{
const auto oldDeco = m_focus.decoration;
m_focus.decoration = nullptr;
auto *ac = qobject_cast<AbstractClient*>(m_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;
}
const auto pos = position().toPoint();
auto internalWindow = findInternalWindow(pos);
Toplevel *toplevel;
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) {
// 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(); const auto &internalClients = waylandServer()->internalClients();
const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible()); if (internalClients.isEmpty()) {
if (!internalClients.isEmpty() && change) { return nullptr;
}
auto it = internalClients.end(); auto it = internalClients.end();
do { do {
it--; --it;
if (QWindow *w = (*it)->internalWindow()) { QWindow *w = (*it)->internalWindow();
if (!w->isVisible()) { if (!w || !w->isVisible()) {
continue;
}
if (!(*it)->geometry().contains(pos)) {
continue; continue;
} }
if ((*it)->geometry().contains(pos.toPoint())) {
// check input mask // check input mask
const QRegion mask = w->mask().translated(w->geometry().topLeft()); const QRegion mask = w->mask().translated(w->geometry().topLeft());
if (!mask.isEmpty() && !mask.contains(pos.toPoint())) { if (!mask.isEmpty() && !mask.contains(pos)) {
continue; continue;
} }
if (w->property("outputOnly").toBool()) { if (w->property("outputOnly").toBool()) {
continue; continue;
} }
m_internalWindow = QPointer<QWindow>(w); return w;
found = true;
break;
}
}
} while (it != internalClients.begin()); } while (it != internalClients.begin());
if (!found) {
needsReset = true; return nullptr;
}
}
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 } // namespace

88
input.h
View file

@ -3,6 +3,7 @@
This file is part of the KDE project. This file is part of the KDE project.
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org> Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2018 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -365,50 +366,91 @@ protected:
void passToWaylandServer(QKeyEvent *event); void passToWaylandServer(QKeyEvent *event);
}; };
class InputDeviceHandler : public QObject class KWIN_EXPORT InputDeviceHandler : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
virtual ~InputDeviceHandler(); virtual ~InputDeviceHandler();
virtual void init();
QPointer<Toplevel> window() const { void update();
return m_window;
/**
* @brief First Toplevel currently at the position of the input device
* according to the stacking order.
* @return Toplevel* at device position.
*/
QPointer<Toplevel> at() const {
return m_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<Toplevel> focus() const {
return m_focus.focus;
}
/**
* @brief The Decoration currently receiving events.
* @return decoration with pointer focus.
**/
QPointer<Decoration::DecoratedClientImpl> decoration() const { QPointer<Decoration::DecoratedClientImpl> decoration() const {
return m_decoration; return m_focus.decoration;
} }
/**
* @brief The internal window currently receiving events.
* @return QWindow with pointer focus.
**/
QPointer<QWindow> internalWindow() const { QPointer<QWindow> internalWindow() const {
return m_internalWindow; return m_focus.internalWindow;
} }
virtual QPointF position() const = 0;
void setFocus(Toplevel *toplevel);
void setDecoration(QPointer<Decoration::DecoratedClientImpl> decoration);
void setInternalWindow(QWindow *window);
Q_SIGNALS: Q_SIGNALS:
void atChanged(Toplevel *old, Toplevel *now);
void decorationChanged(); void decorationChanged();
void internalWindowChanged();
protected: protected:
explicit InputDeviceHandler(InputRedirection *parent); explicit InputDeviceHandler(InputRedirection *parent);
void updateDecoration(Toplevel *t, const QPointF &pos);
void updateInternalWindow(const QPointF &pos); virtual void cleanupInternalWindow(QWindow *old, QWindow *now) = 0;
void setWindow(QPointer<Toplevel> window = QPointer<Toplevel>()) { virtual void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) = 0;
m_window = window;
virtual void focusUpdate(Toplevel *old, Toplevel *now) = 0;
virtual bool focusUpdatesBlocked() {
return false;
} }
void clearDecoration() {
m_decoration.clear(); inline bool inited() const {
return m_inited;
} }
void clearInternalWindow() { inline void setInited(bool set) {
m_internalWindow.clear(); m_inited = set;
} }
private: private:
/** bool setAt(Toplevel *toplevel);
* @brief The Toplevel which currently receives events void updateFocus();
*/ bool updateDecoration();
QPointer<Toplevel> m_window; void updateInternalWindow(QWindow *window);
/**
* @brief The Decoration which currently receives events. QWindow* findInternalWindow(const QPoint &pos) const;
**/
QPointer<Decoration::DecoratedClientImpl> m_decoration; QPointer<Toplevel> m_at;
QPointer<QWindow> m_internalWindow; struct {
QPointer<Toplevel> focus;
QPointer<Decoration::DecoratedClientImpl> decoration;
QPointer<QWindow> internalWindow;
} m_focus;
bool m_inited = false;
}; };
inline inline

View file

@ -3,6 +3,7 @@
This file is part of the KDE project. This file is part of the KDE project.
Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org> Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2018 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -134,13 +135,14 @@ PointerInputRedirection::~PointerInputRedirection() = default;
void PointerInputRedirection::init() void PointerInputRedirection::init()
{ {
Q_ASSERT(!m_inited); Q_ASSERT(!inited());
m_cursor = new CursorImage(this); m_cursor = new CursorImage(this);
m_inited = true; setInited(true);
InputDeviceHandler::init();
connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged); connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged);
emit m_cursor->changed(); emit m_cursor->changed();
connect(workspace(), &Workspace::stackingOrderChanged, this, &PointerInputRedirection::update);
connect(workspace(), &Workspace::clientMinimizedChanged, this, &PointerInputRedirection::update);
connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange); connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange);
if (waylandServer()->hasScreenLockerIntegration()) { if (waylandServer()->hasScreenLockerIntegration()) {
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this,
@ -151,51 +153,16 @@ void PointerInputRedirection::init()
} }
); );
} }
connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(workspace(), &QObject::destroyed, this, [this] { setInited(false); });
connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer(), &QObject::destroyed, this, [this] { setInited(false); });
connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this,
[this] { [this] {
// need to force a focused pointer change // need to force a focused pointer change
waylandServer()->seat()->setFocusedPointerSurface(nullptr); waylandServer()->seat()->setFocusedPointerSurface(nullptr);
setWindow(); setFocus(nullptr);
update(); update();
} }
); );
connect(this, &PointerInputRedirection::internalWindowChanged, this,
[this] {
disconnect(m_internalWindowConnection);
m_internalWindowConnection = QMetaObject::Connection();
if (internalWindow()) {
m_internalWindowConnection = connect(internalWindow().data(), &QWindow::visibleChanged, this,
[this] (bool visible) {
if (!visible) {
update();
}
}
);
}
}
);
connect(this, &PointerInputRedirection::decorationChanged, this,
[this] {
disconnect(m_decorationGeometryConnection);
m_decorationGeometryConnection = QMetaObject::Connection();
if (decoration()) {
m_decorationGeometryConnection = connect(decoration()->client(), &AbstractClient::geometryChanged, this,
[this] {
// ensure maximize button gets the leave event when maximizing/restore a window, see BUG 385140
const auto oldDeco = decoration();
update();
if (oldDeco && oldDeco == decoration() && !decoration()->client()->isMove() && !decoration()->client()->isResize() && !areButtonsPressed()) {
// position of window did not change, we need to send HoverMotion manually
const QPointF p = m_pos - decoration()->client()->pos();
QHoverEvent event(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
}
}, Qt::QueuedConnection);
}
}
);
// connect the move resize of all window // connect the move resize of all window
auto setupMoveResizeConnection = [this] (AbstractClient *c) { auto setupMoveResizeConnection = [this] (AbstractClient *c) {
connect(c, &AbstractClient::clientStartUserMovedResized, this, &PointerInputRedirection::updateOnStartMoveResize); connect(c, &AbstractClient::clientStartUserMovedResized, this, &PointerInputRedirection::updateOnStartMoveResize);
@ -213,9 +180,9 @@ void PointerInputRedirection::init()
void PointerInputRedirection::updateOnStartMoveResize() void PointerInputRedirection::updateOnStartMoveResize()
{ {
breakPointerConstraints(window() ? window()->surface() : nullptr); breakPointerConstraints(focus() ? focus()->surface() : nullptr);
disconnectPointerConstraintsConnection(); disconnectPointerConstraintsConnection();
setWindow(); setFocus(nullptr);
waylandServer()->seat()->setFocusedPointerSurface(nullptr); waylandServer()->seat()->setFocusedPointerSurface(nullptr);
} }
@ -226,22 +193,22 @@ void PointerInputRedirection::updateToReset()
m_internalWindowConnection = QMetaObject::Connection(); m_internalWindowConnection = QMetaObject::Connection();
QEvent event(QEvent::Leave); QEvent event(QEvent::Leave);
QCoreApplication::sendEvent(internalWindow().data(), &event); QCoreApplication::sendEvent(internalWindow().data(), &event);
clearInternalWindow(); setInternalWindow(nullptr);
} }
if (decoration()) { if (decoration()) {
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
clearDecoration(); setDecoration(nullptr);
} }
if (window()) { if (focus()) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(window().data())) { if (AbstractClient *c = qobject_cast<AbstractClient*>(focus().data())) {
c->leaveEvent(); c->leaveEvent();
} }
disconnect(m_windowGeometryConnection); disconnect(m_focusGeometryConnection);
m_windowGeometryConnection = QMetaObject::Connection(); m_focusGeometryConnection = QMetaObject::Connection();
breakPointerConstraints(window()->surface()); breakPointerConstraints(focus()->surface());
disconnectPointerConstraintsConnection(); disconnectPointerConstraintsConnection();
setWindow(); setFocus(nullptr);
} }
waylandServer()->seat()->setFocusedPointerSurface(nullptr); waylandServer()->seat()->setFocusedPointerSurface(nullptr);
} }
@ -296,7 +263,7 @@ QVector<PositionUpdateBlocker::ScheduledPosition> PositionUpdateBlocker::s_sched
void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec, LibInput::Device *device) void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec, LibInput::Device *device)
{ {
if (!m_inited) { if (!inited()) {
return; return;
} }
if (PositionUpdateBlocker::isPositionBlocked()) { if (PositionUpdateBlocker::isPositionBlocked()) {
@ -311,14 +278,13 @@ void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &de
delta, deltaNonAccelerated, timeUsec, device); delta, deltaNonAccelerated, timeUsec, device);
event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts());
update();
input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event));
input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, 0)); input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, 0));
} }
void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time, LibInput::Device *device) void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time, LibInput::Device *device)
{ {
updateButton(button, state);
QEvent::Type type; QEvent::Type type;
switch (state) { switch (state) {
case InputRedirection::PointerButtonReleased: case InputRedirection::PointerButtonReleased:
@ -326,12 +292,15 @@ void PointerInputRedirection::processButton(uint32_t button, InputRedirection::P
break; break;
case InputRedirection::PointerButtonPressed: case InputRedirection::PointerButtonPressed:
type = QEvent::MouseButtonPress; type = QEvent::MouseButtonPress;
update();
break; break;
default: default:
Q_UNREACHABLE(); Q_UNREACHABLE();
return; return;
} }
updateButton(button, state);
MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons, MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons,
input()->keyboardModifiers(), time, QSizeF(), QSizeF(), 0, device); input()->keyboardModifiers(), time, QSizeF(), QSizeF(), 0, device);
event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts());
@ -339,11 +308,15 @@ void PointerInputRedirection::processButton(uint32_t button, InputRedirection::P
input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event));
if (!m_inited) { if (!inited()) {
return; return;
} }
input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, button)); input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, button));
if (state == InputRedirection::PointerButtonReleased) {
update();
}
} }
void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time, LibInput::Device *device) void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time, LibInput::Device *device)
@ -351,6 +324,7 @@ void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qr
if (delta == 0) { if (delta == 0) {
return; return;
} }
update();
emit input()->pointerAxisChanged(axis, delta); emit input()->pointerAxisChanged(axis, delta);
@ -361,7 +335,7 @@ void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qr
input()->processSpies(std::bind(&InputEventSpy::wheelEvent, std::placeholders::_1, &wheelEvent)); input()->processSpies(std::bind(&InputEventSpy::wheelEvent, std::placeholders::_1, &wheelEvent));
if (!m_inited) { if (!inited()) {
return; return;
} }
input()->processFilters(std::bind(&InputEventFilter::wheelEvent, std::placeholders::_1, &wheelEvent)); input()->processFilters(std::bind(&InputEventFilter::wheelEvent, std::placeholders::_1, &wheelEvent));
@ -370,7 +344,7 @@ void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qr
void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
@ -381,9 +355,10 @@ void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, quint32
void PointerInputRedirection::processSwipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) void PointerInputRedirection::processSwipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
update();
input()->processSpies(std::bind(&InputEventSpy::swipeGestureUpdate, std::placeholders::_1, delta, time)); input()->processSpies(std::bind(&InputEventSpy::swipeGestureUpdate, std::placeholders::_1, delta, time));
input()->processFilters(std::bind(&InputEventFilter::swipeGestureUpdate, std::placeholders::_1, delta, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureUpdate, std::placeholders::_1, delta, time));
@ -392,9 +367,10 @@ void PointerInputRedirection::processSwipeGestureUpdate(const QSizeF &delta, qui
void PointerInputRedirection::processSwipeGestureEnd(quint32 time, KWin::LibInput::Device *device) void PointerInputRedirection::processSwipeGestureEnd(quint32 time, KWin::LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
update();
input()->processSpies(std::bind(&InputEventSpy::swipeGestureEnd, std::placeholders::_1, time)); input()->processSpies(std::bind(&InputEventSpy::swipeGestureEnd, std::placeholders::_1, time));
input()->processFilters(std::bind(&InputEventFilter::swipeGestureEnd, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureEnd, std::placeholders::_1, time));
@ -403,9 +379,10 @@ void PointerInputRedirection::processSwipeGestureEnd(quint32 time, KWin::LibInpu
void PointerInputRedirection::processSwipeGestureCancelled(quint32 time, KWin::LibInput::Device *device) void PointerInputRedirection::processSwipeGestureCancelled(quint32 time, KWin::LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
update();
input()->processSpies(std::bind(&InputEventSpy::swipeGestureCancelled, std::placeholders::_1, time)); input()->processSpies(std::bind(&InputEventSpy::swipeGestureCancelled, std::placeholders::_1, time));
input()->processFilters(std::bind(&InputEventFilter::swipeGestureCancelled, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureCancelled, std::placeholders::_1, time));
@ -414,9 +391,10 @@ void PointerInputRedirection::processSwipeGestureCancelled(quint32 time, KWin::L
void PointerInputRedirection::processPinchGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) void PointerInputRedirection::processPinchGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
update();
input()->processSpies(std::bind(&InputEventSpy::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); input()->processSpies(std::bind(&InputEventSpy::pinchGestureBegin, std::placeholders::_1, fingerCount, time));
input()->processFilters(std::bind(&InputEventFilter::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureBegin, std::placeholders::_1, fingerCount, time));
@ -425,9 +403,10 @@ void PointerInputRedirection::processPinchGestureBegin(int fingerCount, quint32
void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time, KWin::LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
update();
input()->processSpies(std::bind(&InputEventSpy::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); input()->processSpies(std::bind(&InputEventSpy::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time));
input()->processFilters(std::bind(&InputEventFilter::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time));
@ -436,9 +415,10 @@ void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angle
void PointerInputRedirection::processPinchGestureEnd(quint32 time, KWin::LibInput::Device *device) void PointerInputRedirection::processPinchGestureEnd(quint32 time, KWin::LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
update();
input()->processSpies(std::bind(&InputEventSpy::pinchGestureEnd, std::placeholders::_1, time)); input()->processSpies(std::bind(&InputEventSpy::pinchGestureEnd, std::placeholders::_1, time));
input()->processFilters(std::bind(&InputEventFilter::pinchGestureEnd, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureEnd, std::placeholders::_1, time));
@ -447,9 +427,10 @@ void PointerInputRedirection::processPinchGestureEnd(quint32 time, KWin::LibInpu
void PointerInputRedirection::processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device) void PointerInputRedirection::processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
update();
input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time)); input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time));
input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time));
@ -465,78 +446,140 @@ bool PointerInputRedirection::areButtonsPressed() const
return false; return false;
} }
static bool s_cursorUpdateBlocking = false; bool PointerInputRedirection::focusUpdatesBlocked()
void PointerInputRedirection::update()
{ {
if (!m_inited) { if (!inited()) {
return; return true;
} }
if (waylandServer()->seat()->isDragPointer()) { if (waylandServer()->seat()->isDragPointer()) {
// ignore during drag and drop // ignore during drag and drop
return; return true;
}
if (waylandServer()->seat()->isTouchSequence()) {
// ignore during touch operations
return true;
} }
if (input()->isSelectingWindow()) { if (input()->isSelectingWindow()) {
return; return true;
} }
if (areButtonsPressed()) { if (areButtonsPressed()) {
return; return true;
} }
Toplevel *t = input()->findToplevel(m_pos.toPoint()); return false;
const auto oldDeco = decoration(); }
updateInternalWindow(m_pos);
if (!internalWindow()) { void PointerInputRedirection::cleanupInternalWindow(QWindow *old, QWindow *now)
updateDecoration(t, m_pos); {
} else {
updateDecoration(waylandServer()->findClient(internalWindow()), m_pos);
if (decoration()) {
disconnect(m_internalWindowConnection); disconnect(m_internalWindowConnection);
m_internalWindowConnection = QMetaObject::Connection(); m_internalWindowConnection = QMetaObject::Connection();
QEvent event(QEvent::Leave);
QCoreApplication::sendEvent(internalWindow().data(), &event); if (old) {
clearInternalWindow(); // leave internal window
// TODO: do this instead via Wayland protocol as below
QEvent leaveEvent(QEvent::Leave);
QCoreApplication::sendEvent(old, &leaveEvent);
}
if (now) {
m_internalWindowConnection = connect(internalWindow().data(), &QWindow::visibleChanged, this,
[this] (bool visible) {
if (!visible) {
update();
} }
} }
if (decoration() || internalWindow()) { );
t = nullptr;
} }
if (decoration() != oldDeco) {
emit decorationChanged();
} }
auto oldWindow = window();
if (!oldWindow.isNull() && t == window().data()) { void PointerInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now)
{
disconnect(m_decorationGeometryConnection);
m_decorationGeometryConnection = QMetaObject::Connection();
workspace()->updateFocusMousePosition(position().toPoint());
if (old) {
// send leave event to old decoration
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(old->decoration(), &event);
}
if (!now) {
// left decoration
return; return;
} }
auto seat = waylandServer()->seat();
// disconnect old surface waylandServer()->seat()->setFocusedPointerSurface(nullptr);
if (oldWindow) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(oldWindow.data())) { auto pos = m_pos - now->client()->pos();
c->leaveEvent(); QHoverEvent event(QEvent::HoverEnter, pos, pos);
QCoreApplication::instance()->sendEvent(now->decoration(), &event);
now->client()->processDecorationMove(pos.toPoint(), m_pos.toPoint());
m_decorationGeometryConnection = connect(decoration()->client(), &AbstractClient::geometryChanged, this,
[this] {
// ensure maximize button gets the leave event when maximizing/restore a window, see BUG 385140
const auto oldDeco = decoration();
update();
if (oldDeco &&
oldDeco == decoration() &&
!decoration()->client()->isMove() &&
!decoration()->client()->isResize() &&
!areButtonsPressed()) {
// position of window did not change, we need to send HoverMotion manually
const QPointF p = m_pos - decoration()->client()->pos();
QHoverEvent event(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
} }
disconnect(m_windowGeometryConnection); }, Qt::QueuedConnection);
m_windowGeometryConnection = QMetaObject::Connection(); }
breakPointerConstraints(oldWindow->surface());
static bool s_cursorUpdateBlocking = false;
void PointerInputRedirection::focusUpdate(Toplevel *focusOld, Toplevel *focusNow)
{
if (AbstractClient *ac = qobject_cast<AbstractClient*>(focusOld)) {
ac->leaveEvent();
breakPointerConstraints(ac->surface());
disconnectPointerConstraintsConnection(); disconnectPointerConstraintsConnection();
} }
if (AbstractClient *c = qobject_cast<AbstractClient*>(t)) { disconnect(m_focusGeometryConnection);
// only send enter if it wasn't on deco for the same client before m_focusGeometryConnection = QMetaObject::Connection();
if (decoration().isNull() || decoration()->client() != c) {
c->enterEvent(m_pos.toPoint()); if (AbstractClient *ac = qobject_cast<AbstractClient*>(focusNow)) {
ac->enterEvent(m_pos.toPoint());
workspace()->updateFocusMousePosition(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint());
} }
auto seat = waylandServer()->seat();
if (!focusNow || !focusNow->surface() || decoration()) {
// no new surface or internal window or on decoration -> cleanup
warpXcbOnSurfaceLeft(nullptr);
seat->setFocusedPointerSurface(nullptr);
return;
} }
if (t && t->surface()) {
setWindow(t); if (internalWindow()) {
// enter internal window
// TODO: do this instead via Wayland protocol as below
const auto pos = at()->pos();
QEnterEvent enterEvent(pos, pos, m_pos);
QCoreApplication::sendEvent(internalWindow().data(), &enterEvent);
}
// TODO: add convenient API to update global pos together with updating focused surface // TODO: add convenient API to update global pos together with updating focused surface
warpXcbOnSurfaceLeft(t->surface()); warpXcbOnSurfaceLeft(focusNow->surface());
// TODO: why? in order to reset the cursor icon?
s_cursorUpdateBlocking = true; s_cursorUpdateBlocking = true;
seat->setFocusedPointerSurface(nullptr); seat->setFocusedPointerSurface(nullptr);
s_cursorUpdateBlocking = false; s_cursorUpdateBlocking = false;
seat->setPointerPos(m_pos.toPoint()); seat->setPointerPos(m_pos.toPoint());
seat->setFocusedPointerSurface(t->surface(), t->inputTransformation()); seat->setFocusedPointerSurface(focusNow->surface(), focusNow->inputTransformation());
m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this,
m_focusGeometryConnection = connect(focusNow, &Toplevel::geometryChanged, this,
[this] { [this] {
if (window().isNull()) { // TODO: why no assert possible?
if (!focus()) {
return; return;
} }
// TODO: can we check on the client instead? // TODO: can we check on the client instead?
@ -545,24 +588,18 @@ void PointerInputRedirection::update()
return; return;
} }
auto seat = waylandServer()->seat(); auto seat = waylandServer()->seat();
if (window().data()->surface() != seat->focusedPointerSurface()) { if (focus()->surface() != seat->focusedPointerSurface()) {
return; return;
} }
seat->setFocusedPointerSurfaceTransformation(window().data()->inputTransformation()); seat->setFocusedPointerSurfaceTransformation(focus()->inputTransformation());
} }
); );
m_constraintsConnection = connect(window()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged,
m_constraintsConnection = connect(focusNow->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged,
this, &PointerInputRedirection::updatePointerConstraints); this, &PointerInputRedirection::updatePointerConstraints);
m_constraintsActivatedConnection = connect(workspace(), &Workspace::clientActivated, m_constraintsActivatedConnection = connect(workspace(), &Workspace::clientActivated,
this, &PointerInputRedirection::updatePointerConstraints); this, &PointerInputRedirection::updatePointerConstraints);
// check whether a pointer confinement/lock fires
updatePointerConstraints(); updatePointerConstraints();
} else {
setWindow();
warpXcbOnSurfaceLeft(nullptr);
seat->setFocusedPointerSurface(nullptr);
t = nullptr;
}
} }
void PointerInputRedirection::breakPointerConstraints(KWayland::Server::SurfaceInterface *surface) void PointerInputRedirection::breakPointerConstraints(KWayland::Server::SurfaceInterface *surface)
@ -624,10 +661,10 @@ void PointerInputRedirection::setEnableConstraints(bool set)
void PointerInputRedirection::updatePointerConstraints() void PointerInputRedirection::updatePointerConstraints()
{ {
if (window().isNull()) { if (focus().isNull()) {
return; return;
} }
const auto s = window()->surface(); const auto s = focus()->surface();
if (!s) { if (!s) {
return; return;
} }
@ -637,7 +674,7 @@ void PointerInputRedirection::updatePointerConstraints()
if (!supportsWarping()) { if (!supportsWarping()) {
return; return;
} }
const bool canConstrain = m_enableConstraints && window() == workspace()->activeClient(); const bool canConstrain = m_enableConstraints && focus() == workspace()->activeClient();
const auto cf = s->confinedPointer(); const auto cf = s->confinedPointer();
if (cf) { if (cf) {
if (cf->isConfined()) { if (cf->isConfined()) {
@ -648,21 +685,21 @@ void PointerInputRedirection::updatePointerConstraints()
} }
return; return;
} }
const QRegion r = getConstraintRegion(window().data(), cf.data()); const QRegion r = getConstraintRegion(focus().data(), cf.data());
if (canConstrain && r.contains(m_pos.toPoint())) { if (canConstrain && r.contains(m_pos.toPoint())) {
cf->setConfined(true); cf->setConfined(true);
m_confined = true; m_confined = true;
m_confinedPointerRegionConnection = connect(cf.data(), &KWayland::Server::ConfinedPointerInterface::regionChanged, this, m_confinedPointerRegionConnection = connect(cf.data(), &KWayland::Server::ConfinedPointerInterface::regionChanged, this,
[this] { [this] {
if (!window()) { if (!focus()) {
return; return;
} }
const auto s = window()->surface(); const auto s = focus()->surface();
if (!s) { if (!s) {
return; return;
} }
const auto cf = s->confinedPointer(); const auto cf = s->confinedPointer();
if (!getConstraintRegion(window().data(), cf.data()).contains(m_pos.toPoint())) { if (!getConstraintRegion(focus().data(), cf.data()).contains(m_pos.toPoint())) {
// pointer no longer in confined region, break the confinement // pointer no longer in confined region, break the confinement
cf->setConfined(false); cf->setConfined(false);
m_confined = false; m_confined = false;
@ -688,13 +725,13 @@ void PointerInputRedirection::updatePointerConstraints()
lock->setLocked(false); lock->setLocked(false);
m_locked = false; m_locked = false;
disconnectLockedPointerAboutToBeUnboundConnection(); disconnectLockedPointerAboutToBeUnboundConnection();
if (! (hint.x() < 0 || hint.y() < 0) && window()) { if (! (hint.x() < 0 || hint.y() < 0) && focus()) {
processMotion(window()->pos() - window()->clientContentPos() + hint, waylandServer()->seat()->timestamp()); processMotion(focus()->pos() - focus()->clientContentPos() + hint, waylandServer()->seat()->timestamp());
} }
} }
return; return;
} }
const QRegion r = getConstraintRegion(window().data(), lock.data()); const QRegion r = getConstraintRegion(focus().data(), lock.data());
if (canConstrain && r.contains(m_pos.toPoint())) { if (canConstrain && r.contains(m_pos.toPoint())) {
lock->setLocked(true); lock->setLocked(true);
m_locked = true; m_locked = true;
@ -704,10 +741,10 @@ void PointerInputRedirection::updatePointerConstraints()
m_lockedPointerAboutToBeUnboundConnection = connect(lock.data(), &KWayland::Server::LockedPointerInterface::aboutToBeUnbound, this, m_lockedPointerAboutToBeUnboundConnection = connect(lock.data(), &KWayland::Server::LockedPointerInterface::aboutToBeUnbound, this,
[this, lock]() { [this, lock]() {
const auto hint = lock->cursorPositionHint(); const auto hint = lock->cursorPositionHint();
if (hint.x() < 0 || hint.y() < 0 || !window()) { if (hint.x() < 0 || hint.y() < 0 || !focus()) {
return; return;
} }
auto globalHint = window()->pos() - window()->clientContentPos() + hint; auto globalHint = focus()->pos() - focus()->clientContentPos() + hint;
// When the resource finally goes away, reposition the cursor according to the hint // When the resource finally goes away, reposition the cursor according to the hint
connect(lock.data(), &KWayland::Server::LockedPointerInterface::unbound, this, connect(lock.data(), &KWayland::Server::LockedPointerInterface::unbound, this,
@ -755,10 +792,10 @@ void PointerInputRedirection::warpXcbOnSurfaceLeft(KWayland::Server::SurfaceInte
QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) const QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) const
{ {
if (!window()) { if (!focus()) {
return pos; return pos;
} }
auto s = window()->surface(); auto s = focus()->surface();
if (!s) { if (!s) {
return pos; return pos;
} }
@ -770,7 +807,7 @@ QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) con
return pos; return pos;
} }
const QRegion confinementRegion = getConstraintRegion(window().data(), cf.data()); const QRegion confinementRegion = getConstraintRegion(focus().data(), cf.data());
if (confinementRegion.contains(pos.toPoint())) { if (confinementRegion.contains(pos.toPoint())) {
return pos; return pos;
} }
@ -843,7 +880,7 @@ void PointerInputRedirection::warp(const QPointF &pos)
bool PointerInputRedirection::supportsWarping() const bool PointerInputRedirection::supportsWarping() const
{ {
if (!m_inited) { if (!inited()) {
return false; return false;
} }
if (m_supportsWarping) { if (m_supportsWarping) {
@ -857,7 +894,7 @@ bool PointerInputRedirection::supportsWarping() const
void PointerInputRedirection::updateAfterScreenChange() void PointerInputRedirection::updateAfterScreenChange()
{ {
if (!m_inited) { if (!inited()) {
return; return;
} }
if (screenContainsPos(m_pos)) { if (screenContainsPos(m_pos)) {
@ -872,7 +909,7 @@ void PointerInputRedirection::updateAfterScreenChange()
QImage PointerInputRedirection::cursorImage() const QImage PointerInputRedirection::cursorImage() const
{ {
if (!m_inited) { if (!inited()) {
return QImage(); return QImage();
} }
return m_cursor->image(); return m_cursor->image();
@ -880,7 +917,7 @@ QImage PointerInputRedirection::cursorImage() const
QPoint PointerInputRedirection::cursorHotSpot() const QPoint PointerInputRedirection::cursorHotSpot() const
{ {
if (!m_inited) { if (!inited()) {
return QPoint(); return QPoint();
} }
return m_cursor->hotSpot(); return m_cursor->hotSpot();
@ -888,15 +925,20 @@ QPoint PointerInputRedirection::cursorHotSpot() const
void PointerInputRedirection::markCursorAsRendered() void PointerInputRedirection::markCursorAsRendered()
{ {
if (!m_inited) { if (!inited()) {
return; return;
} }
m_cursor->markAsRendered(); m_cursor->markAsRendered();
} }
QPointF PointerInputRedirection::position() const
{
return m_pos.toPoint();
}
void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape)
{ {
if (!m_inited) { if (!inited()) {
return; return;
} }
// current pointer focus window should get a leave event // current pointer focus window should get a leave event
@ -906,7 +948,7 @@ void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape)
void PointerInputRedirection::removeEffectsOverrideCursor() void PointerInputRedirection::removeEffectsOverrideCursor()
{ {
if (!m_inited) { if (!inited()) {
return; return;
} }
// cursor position might have changed while there was an effect in place // cursor position might have changed while there was an effect in place
@ -916,7 +958,7 @@ void PointerInputRedirection::removeEffectsOverrideCursor()
void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape) void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape)
{ {
if (!m_inited) { if (!inited()) {
return; return;
} }
// send leave to current pointer focus window // send leave to current pointer focus window
@ -926,7 +968,7 @@ void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape)
void PointerInputRedirection::removeWindowSelectionCursor() void PointerInputRedirection::removeWindowSelectionCursor()
{ {
if (!m_inited) { if (!inited()) {
return; return;
} }
update(); update();
@ -1302,7 +1344,7 @@ void CursorImage::reevaluteSource()
setSource(CursorSource::Decoration); setSource(CursorSource::Decoration);
return; return;
} }
if (!m_pointer->window().isNull() && waylandServer()->seat()->focusedPointer()) { if (!m_pointer->focus().isNull() && waylandServer()->seat()->focusedPointer()) {
setSource(CursorSource::PointerSurface); setSource(CursorSource::PointerSurface);
return; return;
} }

View file

@ -3,6 +3,7 @@
This file is part of the KDE project. This file is part of the KDE project.
Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org> Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2018 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -64,7 +65,6 @@ public:
void init(); void init();
void update();
void updateAfterScreenChange(); void updateAfterScreenChange();
bool supportsWarping() const; bool supportsWarping() const;
void warp(const QPointF &pos); void warp(const QPointF &pos);
@ -75,6 +75,7 @@ public:
Qt::MouseButtons buttons() const { Qt::MouseButtons buttons() const {
return m_qtButtons; return m_qtButtons;
} }
bool areButtonsPressed() const;
QImage cursorImage() const; QImage cursorImage() const;
QPoint cursorHotSpot() const; QPoint cursorHotSpot() const;
@ -92,6 +93,8 @@ public:
return m_confined || m_locked; return m_confined || m_locked;
} }
bool focusUpdatesBlocked() override;
/** /**
* @internal * @internal
*/ */
@ -142,6 +145,13 @@ public:
void processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device = nullptr); void processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device = nullptr);
private: private:
void cleanupInternalWindow(QWindow *old, QWindow *now) override;
void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) override;
void focusUpdate(Toplevel *focusOld, Toplevel *focusNow) override;
QPointF position() const override;
void updateOnStartMoveResize(); void updateOnStartMoveResize();
void updateToReset(); void updateToReset();
void updatePosition(const QPointF &pos); void updatePosition(const QPointF &pos);
@ -152,14 +162,12 @@ private:
void disconnectLockedPointerAboutToBeUnboundConnection(); void disconnectLockedPointerAboutToBeUnboundConnection();
void disconnectPointerConstraintsConnection(); void disconnectPointerConstraintsConnection();
void breakPointerConstraints(KWayland::Server::SurfaceInterface *surface); void breakPointerConstraints(KWayland::Server::SurfaceInterface *surface);
bool areButtonsPressed() const;
CursorImage *m_cursor; CursorImage *m_cursor;
bool m_inited = false;
bool m_supportsWarping; bool m_supportsWarping;
QPointF m_pos; QPointF m_pos;
QHash<uint32_t, InputRedirection::PointerButtonState> m_buttons; QHash<uint32_t, InputRedirection::PointerButtonState> m_buttons;
Qt::MouseButtons m_qtButtons; Qt::MouseButtons m_qtButtons;
QMetaObject::Connection m_windowGeometryConnection; QMetaObject::Connection m_focusGeometryConnection;
QMetaObject::Connection m_internalWindowConnection; QMetaObject::Connection m_internalWindowConnection;
QMetaObject::Connection m_constraintsConnection; QMetaObject::Connection m_constraintsConnection;
QMetaObject::Connection m_constraintsActivatedConnection; QMetaObject::Connection m_constraintsActivatedConnection;

View file

@ -3,6 +3,7 @@
This file is part of the KDE project. This file is part of the KDE project.
Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org> Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2018 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -20,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "touch_input.h" #include "touch_input.h"
#include "abstract_client.h" #include "abstract_client.h"
#include "input.h" #include "input.h"
#include "pointer_input.h"
#include "input_event_spy.h" #include "input_event_spy.h"
#include "toplevel.h" #include "toplevel.h"
#include "wayland_server.h" #include "wayland_server.h"
@ -47,8 +49,9 @@ TouchInputRedirection::~TouchInputRedirection() = default;
void TouchInputRedirection::init() void TouchInputRedirection::init()
{ {
Q_ASSERT(!m_inited); Q_ASSERT(!inited());
m_inited = true; setInited(true);
InputDeviceHandler::init();
if (waylandServer()->hasScreenLockerIntegration()) { if (waylandServer()->hasScreenLockerIntegration()) {
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this,
@ -59,73 +62,83 @@ void TouchInputRedirection::init()
} }
); );
} }
connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(workspace(), &QObject::destroyed, this, [this] { setInited(false); });
connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer(), &QObject::destroyed, this, [this] { setInited(false); });
} }
void TouchInputRedirection::update(const QPointF &pos) bool TouchInputRedirection::focusUpdatesBlocked()
{ {
if (!m_inited) { if (!inited()) {
return; return true;
} }
if (m_windowUpdatedInCycle) { if (m_windowUpdatedInCycle) {
return; return true;
} }
m_windowUpdatedInCycle = true; m_windowUpdatedInCycle = true;
if (m_touches > 0) {
// first touch defines focus
return true;
}
return false;
}
void TouchInputRedirection::focusUpdate(Toplevel *focusOld, Toplevel *focusNow)
{
// TODO: handle pointer grab aka popups // TODO: handle pointer grab aka popups
Toplevel *t = input()->findToplevel(pos.toPoint());
auto oldWindow = window(); if (AbstractClient *ac = qobject_cast<AbstractClient*>(focusOld)) {
updateInternalWindow(pos); ac->leaveEvent();
if (!internalWindow()) {
updateDecoration(t, pos);
} else {
// TODO: send hover leave to decoration
if (decoration()) {
decoration()->client()->leaveEvent();
} }
clearDecoration(); disconnect(m_focusGeometryConnection);
m_focusGeometryConnection = QMetaObject::Connection();
if (AbstractClient *ac = qobject_cast<AbstractClient*>(focusNow)) {
ac->enterEvent(m_lastPosition.toPoint());
workspace()->updateFocusMousePosition(m_lastPosition.toPoint());
} }
if (decoration() || internalWindow()) {
t = nullptr; auto seat = waylandServer()->seat();
} else if (!decoration()) { if (!focusNow || !focusNow->surface() || decoration()) {
m_decorationId = -1; // no new surface or internal window or on decoration -> cleanup
} else if (!internalWindow()) { seat->setFocusedTouchSurface(nullptr);
m_internalId = -1;
}
if (!oldWindow.isNull() && t == oldWindow.data()) {
return; return;
} }
auto seat = waylandServer()->seat();
// disconnect old surface // TODO: invalidate pointer focus?
if (oldWindow) {
disconnect(m_windowGeometryConnection);
m_windowGeometryConnection = QMetaObject::Connection();
}
if (t && t->surface()) {
// FIXME: add input transformation API to KWayland::Server::SeatInterface for touch input // FIXME: add input transformation API to KWayland::Server::SeatInterface for touch input
seat->setFocusedTouchSurface(t->surface(), -1 * t->inputTransformation().map(t->pos()) + t->pos()); seat->setFocusedTouchSurface(focusNow->surface(), -1 * focusNow->inputTransformation().map(focusNow->pos()) + focusNow->pos());
m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this, m_focusGeometryConnection = connect(focusNow, &Toplevel::geometryChanged, this,
[this] { [this] {
if (window().isNull()) { if (focus().isNull()) {
return; return;
} }
auto seat = waylandServer()->seat(); auto seat = waylandServer()->seat();
if (window().data()->surface() != seat->focusedTouchSurface()) { if (focus().data()->surface() != seat->focusedTouchSurface()) {
return; return;
} }
auto t = window().data(); seat->setFocusedTouchSurfacePosition(-1 * focus()->inputTransformation().map(focus()->pos()) + focus()->pos());
seat->setFocusedTouchSurfacePosition(-1 * t->inputTransformation().map(t->pos()) + t->pos());
} }
); );
} else {
seat->setFocusedTouchSurface(nullptr);
t = nullptr;
} }
if (!t) {
setWindow(); void TouchInputRedirection::cleanupInternalWindow(QWindow *old, QWindow *now)
return; {
Q_UNUSED(old);
Q_UNUSED(now);
// nothing to do
}
void TouchInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now)
{
Q_UNUSED(now);
if (old) {
// send leave event to old decoration
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(old->decoration(), &event);
} }
setWindow(t);
} }
void TouchInputRedirection::insertId(quint32 internalId, qint32 kwaylandId) void TouchInputRedirection::insertId(quint32 internalId, qint32 kwaylandId)
@ -150,10 +163,15 @@ void TouchInputRedirection::removeId(quint32 internalId)
void TouchInputRedirection::processDown(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device) void TouchInputRedirection::processDown(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
m_lastPosition = pos;
m_windowUpdatedInCycle = false; m_windowUpdatedInCycle = false;
if (m_touches == 0) {
update();
}
m_touches++;
input()->processSpies(std::bind(&InputEventSpy::touchDown, std::placeholders::_1, id, pos, time)); input()->processSpies(std::bind(&InputEventSpy::touchDown, std::placeholders::_1, id, pos, time));
input()->processFilters(std::bind(&InputEventFilter::touchDown, std::placeholders::_1, id, pos, time)); input()->processFilters(std::bind(&InputEventFilter::touchDown, std::placeholders::_1, id, pos, time));
m_windowUpdatedInCycle = false; m_windowUpdatedInCycle = false;
@ -162,21 +180,26 @@ void TouchInputRedirection::processDown(qint32 id, const QPointF &pos, quint32 t
void TouchInputRedirection::processUp(qint32 id, quint32 time, LibInput::Device *device) void TouchInputRedirection::processUp(qint32 id, quint32 time, LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
m_windowUpdatedInCycle = false; m_windowUpdatedInCycle = false;
input()->processSpies(std::bind(&InputEventSpy::touchUp, std::placeholders::_1, id, time)); input()->processSpies(std::bind(&InputEventSpy::touchUp, std::placeholders::_1, id, time));
input()->processFilters(std::bind(&InputEventFilter::touchUp, std::placeholders::_1, id, time)); input()->processFilters(std::bind(&InputEventFilter::touchUp, std::placeholders::_1, id, time));
m_windowUpdatedInCycle = false; m_windowUpdatedInCycle = false;
m_touches--;
if (m_touches == 0) {
update();
}
} }
void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device) void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device)
{ {
Q_UNUSED(device) Q_UNUSED(device)
if (!m_inited) { if (!inited()) {
return; return;
} }
m_lastPosition = pos;
m_windowUpdatedInCycle = false; m_windowUpdatedInCycle = false;
input()->processSpies(std::bind(&InputEventSpy::touchMotion, std::placeholders::_1, id, pos, time)); input()->processSpies(std::bind(&InputEventSpy::touchMotion, std::placeholders::_1, id, pos, time));
input()->processFilters(std::bind(&InputEventFilter::touchMotion, std::placeholders::_1, id, pos, time)); input()->processFilters(std::bind(&InputEventFilter::touchMotion, std::placeholders::_1, id, pos, time));
@ -185,7 +208,7 @@ void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32
void TouchInputRedirection::cancel() void TouchInputRedirection::cancel()
{ {
if (!m_inited) { if (!inited()) {
return; return;
} }
waylandServer()->seat()->cancelTouchSequence(); waylandServer()->seat()->cancelTouchSequence();
@ -194,7 +217,7 @@ void TouchInputRedirection::cancel()
void TouchInputRedirection::frame() void TouchInputRedirection::frame()
{ {
if (!m_inited) { if (!inited()) {
return; return;
} }
waylandServer()->seat()->touchFrame(); waylandServer()->seat()->touchFrame();

View file

@ -3,6 +3,7 @@
This file is part of the KDE project. This file is part of the KDE project.
Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org> Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2018 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -49,7 +50,7 @@ public:
explicit TouchInputRedirection(InputRedirection *parent); explicit TouchInputRedirection(InputRedirection *parent);
virtual ~TouchInputRedirection(); virtual ~TouchInputRedirection();
void update(const QPointF &pos = QPointF()); bool focusUpdatesBlocked() override;
void init(); void init();
void processDown(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device = nullptr); void processDown(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device = nullptr);
@ -75,7 +76,16 @@ public:
return m_internalId; return m_internalId;
} }
QPointF position() const override {
return m_lastPosition;
}
private: private:
void cleanupInternalWindow(QWindow *old, QWindow *now) override;
void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) override;
void focusUpdate(Toplevel *focusOld, Toplevel *focusNow) override;
bool m_inited = false; bool m_inited = false;
qint32 m_decorationId = -1; qint32 m_decorationId = -1;
qint32 m_internalId = -1; qint32 m_internalId = -1;
@ -83,8 +93,11 @@ private:
* external/kwayland * external/kwayland
**/ **/
QHash<qint32, qint32> m_idMapper; QHash<qint32, qint32> m_idMapper;
QMetaObject::Connection m_windowGeometryConnection; QMetaObject::Connection m_focusGeometryConnection;
bool m_windowUpdatedInCycle = false; bool m_windowUpdatedInCycle = false;
QPointF m_lastPosition;
int m_touches = 0;
}; };
} }