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
icc-effect-5.17.5
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(hoverLeaveSpy.count(), 1);
kwinApp()->platform()->touchUp(0, timestamp++);
QCOMPARE(hoverMoveSpy.count(), 4);
QCOMPARE(hoverLeaveSpy.count(), 1);
QCOMPARE(hoverMoveSpy.count(), 3);
QCOMPARE(hoverLeaveSpy.count(), 2);
}
void DecorationInputTest::testTooltipDoesntEatKeyEvents_data()

View File

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

View File

@ -232,7 +232,7 @@ void TestPointerConstraints::testConfinedPointer()
QVERIFY(unconfinedSpy2.isValid());
// 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());
QCOMPARE(input()->pointer()->isConstrained(), true);
@ -241,7 +241,7 @@ void TestPointerConstraints::testConfinedPointer()
QVERIFY(unconfinedSpy2.wait());
QCOMPARE(input()->pointer()->isConstrained(), false);
// 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());
QCOMPARE(input()->pointer()->isConstrained(), true);
@ -272,7 +272,7 @@ void TestPointerConstraints::testConfinedPointer()
confinedPointer.reset(nullptr);
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.wait());
@ -348,7 +348,7 @@ void TestPointerConstraints::testLockedPointer()
QVERIFY(lockedSpy2.isValid());
// 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());
QCOMPARE(input()->pointer()->isConstrained(), true);
@ -361,7 +361,7 @@ void TestPointerConstraints::testLockedPointer()
lockedPointer.reset(nullptr);
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.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
Cursor::setPos(window->geometry().center());
QCOMPARE(p->window().data(), window);
QCOMPARE(p->focus().data(), window);
QCOMPARE(p->cursorImage(), fallbackCursor);
QVERIFY(enteredSpy.wait());
@ -1018,7 +1018,7 @@ void PointerInputTest::testCursorImage()
// move cursor somewhere else, should reset to fallback cursor
Cursor::setPos(window->geometry().bottomLeft() + QPoint(20, 20));
QVERIFY(p->window().isNull());
QVERIFY(p->focus().isNull());
QVERIFY(!p->cursorImage().isNull());
QCOMPARE(p->cursorImage(), fallbackCursor);
}

View File

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

370
input.cpp
View File

@ -3,6 +3,7 @@
This file is part of the KDE project.
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
it under the terms of the GNU General Public License as published by
@ -227,12 +228,14 @@ public:
auto seat = waylandServer()->seat();
seat->setTimestamp(event->timestamp());
if (event->type() == QEvent::MouseMove) {
input()->pointer()->update();
if (pointerSurfaceAllowed()) {
// TODO: should the pointer position always stay in sync, i.e. not do the check?
seat->setPointerPos(event->screenPos().toPoint());
}
} else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) {
if (pointerSurfaceAllowed()) {
// TODO: can we leak presses/releases here when we move the mouse in between from an allowed surface to
// disallowed one or vice versa?
event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton);
}
}
@ -293,9 +296,6 @@ public:
}
auto seat = waylandServer()->seat();
seat->setTimestamp(time);
if (!seat->isTouchSequence()) {
input()->touch()->update(pos);
}
if (touchSurfaceAllowed()) {
input()->touch()->insertId(id, seat->touchDown(pos));
}
@ -477,6 +477,52 @@ public:
}
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 {
@ -764,13 +810,6 @@ class InternalWindowEventFilter : public InputEventFilter {
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())
{
@ -883,12 +922,11 @@ class InternalWindowEventFilter : public InputEventFilter {
}
auto touch = input()->touch();
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;
}
// a new touch point
seat->setTimestamp(time);
touch->update(pos);
auto internal = touch->internalWindow();
if (!internal) {
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
m_lastGlobalTouchPos = pos;
m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y());
QEnterEvent enterEvent(m_lastLocalTouchPos, m_lastLocalTouchPos, pos);
QCoreApplication::sendEvent(internal.data(), &enterEvent);
QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers());
e.setAccepted(false);
QCoreApplication::sendEvent(internal.data(), &e);
@ -918,6 +960,7 @@ class InternalWindowEventFilter : public InputEventFilter {
}
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;
@ -941,6 +984,9 @@ class InternalWindowEventFilter : public InputEventFilter {
e.setAccepted(false);
QCoreApplication::sendEvent(internal.data(), &e);
QEvent leaveEvent(QEvent::Leave);
QCoreApplication::sendEvent(internal.data(), &leaveEvent);
m_lastGlobalTouchPos = QPointF();
m_lastLocalTouchPos = QPointF();
input()->touch()->setInternalPressId(-1);
@ -962,9 +1008,6 @@ public:
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());
@ -1034,14 +1077,18 @@ public:
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();
QHoverEvent hoverEvent(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos);
QCoreApplication::sendEvent(decoration->decoration(), &hoverEvent);
QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers());
e.setAccepted(false);
QCoreApplication::sendEvent(decoration->decoration(), &e);
@ -1065,13 +1112,10 @@ public:
}
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());
}
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 {
@ -1087,25 +1131,15 @@ public:
// 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);
}
}
QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers());
e.setAccepted(false);
QCoreApplication::sendEvent(decoration->decoration(), &e);
decoration->client()->processDecorationButtonRelease(&e);
QHoverEvent leaveEvent(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::sendEvent(decoration->decoration(), &leaveEvent);
m_lastGlobalTouchPos = QPointF();
m_lastLocalTouchPos = QPointF();
@ -1218,7 +1252,7 @@ public:
if (event->type() != QEvent::MouseButtonPress) {
return false;
}
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->pointer()->window().data());
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->pointer()->focus().data());
if (!c) {
return false;
}
@ -1233,7 +1267,7 @@ public:
// only actions on vertical scroll
return false;
}
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->pointer()->window().data());
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->pointer()->focus().data());
if (!c) {
return false;
}
@ -1250,8 +1284,7 @@ public:
if (seat->isTouchSequence()) {
return false;
}
input()->touch()->update(pos);
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->touch()->window().data());
AbstractClient *c = dynamic_cast<AbstractClient*>(input()->touch()->focus().data());
if (!c) {
return false;
}
@ -1275,11 +1308,6 @@ public:
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()) {
@ -1292,9 +1320,6 @@ public:
break;
case QEvent::MouseButtonRelease:
seat->pointerButtonReleased(nativeButton);
if (event->buttons() == Qt::NoButton) {
input()->pointer()->update();
}
break;
default:
break;
@ -1328,9 +1353,6 @@ public:
}
auto seat = waylandServer()->seat();
seat->setTimestamp(time);
if (!seat->isTouchSequence()) {
input()->touch()->update(pos);
}
input()->touch()->insertId(id, seat->touchDown(pos));
return true;
}
@ -1447,7 +1469,7 @@ public:
case QEvent::MouseMove: {
const auto pos = input()->globalPointer();
seat->setPointerPos(pos);
if (Toplevel *t = input()->findToplevel(pos.toPoint())) {
if (Toplevel *t = input()->pointer()->at()) {
// TODO: consider decorations
if (t->surface() != seat->dragSurface()) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(t)) {
@ -1670,8 +1692,8 @@ void InputRedirection::setupInputFilters()
installInputEventFilter(new TabBoxInputFilter);
#endif
installInputEventFilter(new GlobalShortcutFilter);
installInputEventFilter(new InternalWindowEventFilter);
installInputEventFilter(new DecorationEventFilter);
installInputEventFilter(new InternalWindowEventFilter);
if (waylandServer()) {
installInputEventFilter(new WindowActionInputFilter);
installInputEventFilter(new ForwardInputFilter);
@ -2083,109 +2105,167 @@ InputDeviceHandler::InputDeviceHandler(InputRedirection *input)
InputDeviceHandler::~InputDeviceHandler() = default;
void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos)
void InputDeviceHandler::init()
{
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;
connect(workspace(), &Workspace::stackingOrderChanged, this, &InputDeviceHandler::update);
connect(workspace(), &Workspace::clientMinimizedChanged, this, &InputDeviceHandler::update);
connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &InputDeviceHandler::update);
}
bool InputDeviceHandler::setAt(Toplevel *toplevel)
{
if (m_at == toplevel) {
return false;
}
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 {
needsReset = true;
}
if (needsReset) {
m_decoration.clear();
toplevel = input()->findToplevel(pos);
}
bool leftSend = false;
auto oldWindow = qobject_cast<AbstractClient*>(window().data());
if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) {
leftSend = true;
oldWindow->leaveEvent();
// Always set the toplevel at the position of the input device.
setAt(toplevel);
if (focusUpdatesBlocked()) {
return;
}
if (oldDeco && oldDeco != m_decoration) {
if (oldDeco->client() != t && !leftSend) {
leftSend = true;
oldDeco->client()->leaveEvent();
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();
}
// send leave
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event);
return;
}
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());
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();
}
}
void InputDeviceHandler::updateInternalWindow(const QPointF &pos)
QWindow* InputDeviceHandler::findInternalWindow(const QPoint &pos) const
{
const auto oldInternalWindow = m_internalWindow;
bool found = false;
// TODO: screen locked check without going through wayland server
bool needsReset = waylandServer()->isScreenLocked();
if (waylandServer()->isScreenLocked()) {
return nullptr;
}
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 (internalClients.isEmpty()) {
return nullptr;
}
if (needsReset) {
m_internalWindow.clear();
}
if (oldInternalWindow != m_internalWindow) {
// changed
if (oldInternalWindow) {
QEvent event(QEvent::Leave);
QCoreApplication::sendEvent(oldInternalWindow.data(), &event);
auto it = internalClients.end();
do {
--it;
QWindow *w = (*it)->internalWindow();
if (!w || !w->isVisible()) {
continue;
}
if (m_internalWindow) {
QEnterEvent event(pos - m_internalWindow->position(),
pos - m_internalWindow->position(),
pos);
QCoreApplication::sendEvent(m_internalWindow.data(), &event);
if (!(*it)->geometry().contains(pos)) {
continue;
}
emit internalWindowChanged();
}
// check input mask
const QRegion mask = w->mask().translated(w->geometry().topLeft());
if (!mask.isEmpty() && !mask.contains(pos)) {
continue;
}
if (w->property("outputOnly").toBool()) {
continue;
}
return w;
} while (it != internalClients.begin());
return nullptr;
}
} // namespace

88
input.h
View File

@ -3,6 +3,7 @@
This file is part of the KDE project.
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
it under the terms of the GNU General Public License as published by
@ -365,50 +366,91 @@ protected:
void passToWaylandServer(QKeyEvent *event);
};
class InputDeviceHandler : public QObject
class KWIN_EXPORT InputDeviceHandler : public QObject
{
Q_OBJECT
public:
virtual ~InputDeviceHandler();
virtual void init();
QPointer<Toplevel> window() const {
return m_window;
void update();
/**
* @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 {
return m_decoration;
return m_focus.decoration;
}
/**
* @brief The internal window currently receiving events.
* @return QWindow with pointer focus.
**/
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:
void atChanged(Toplevel *old, Toplevel *now);
void decorationChanged();
void internalWindowChanged();
protected:
explicit InputDeviceHandler(InputRedirection *parent);
void updateDecoration(Toplevel *t, const QPointF &pos);
void updateInternalWindow(const QPointF &pos);
void setWindow(QPointer<Toplevel> window = QPointer<Toplevel>()) {
m_window = window;
virtual void cleanupInternalWindow(QWindow *old, QWindow *now) = 0;
virtual void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) = 0;
virtual void focusUpdate(Toplevel *old, Toplevel *now) = 0;
virtual bool focusUpdatesBlocked() {
return false;
}
void clearDecoration() {
m_decoration.clear();
inline bool inited() const {
return m_inited;
}
void clearInternalWindow() {
m_internalWindow.clear();
inline void setInited(bool set) {
m_inited = set;
}
private:
/**
* @brief The Toplevel which currently receives events
*/
QPointer<Toplevel> m_window;
/**
* @brief The Decoration which currently receives events.
**/
QPointer<Decoration::DecoratedClientImpl> m_decoration;
QPointer<QWindow> m_internalWindow;
bool setAt(Toplevel *toplevel);
void updateFocus();
bool updateDecoration();
void updateInternalWindow(QWindow *window);
QWindow* findInternalWindow(const QPoint &pos) const;
QPointer<Toplevel> m_at;
struct {
QPointer<Toplevel> focus;
QPointer<Decoration::DecoratedClientImpl> decoration;
QPointer<QWindow> internalWindow;
} m_focus;
bool m_inited = false;
};
inline

View File

@ -3,6 +3,7 @@
This file is part of the KDE project.
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
it under the terms of the GNU General Public License as published by
@ -134,13 +135,14 @@ PointerInputRedirection::~PointerInputRedirection() = default;
void PointerInputRedirection::init()
{
Q_ASSERT(!m_inited);
Q_ASSERT(!inited());
m_cursor = new CursorImage(this);
m_inited = true;
setInited(true);
InputDeviceHandler::init();
connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged);
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);
if (waylandServer()->hasScreenLockerIntegration()) {
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(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; });
connect(workspace(), &QObject::destroyed, this, [this] { setInited(false); });
connect(waylandServer(), &QObject::destroyed, this, [this] { setInited(false); });
connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this,
[this] {
// need to force a focused pointer change
waylandServer()->seat()->setFocusedPointerSurface(nullptr);
setWindow();
setFocus(nullptr);
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
auto setupMoveResizeConnection = [this] (AbstractClient *c) {
connect(c, &AbstractClient::clientStartUserMovedResized, this, &PointerInputRedirection::updateOnStartMoveResize);
@ -213,9 +180,9 @@ void PointerInputRedirection::init()
void PointerInputRedirection::updateOnStartMoveResize()
{
breakPointerConstraints(window() ? window()->surface() : nullptr);
breakPointerConstraints(focus() ? focus()->surface() : nullptr);
disconnectPointerConstraintsConnection();
setWindow();
setFocus(nullptr);
waylandServer()->seat()->setFocusedPointerSurface(nullptr);
}
@ -226,22 +193,22 @@ void PointerInputRedirection::updateToReset()
m_internalWindowConnection = QMetaObject::Connection();
QEvent event(QEvent::Leave);
QCoreApplication::sendEvent(internalWindow().data(), &event);
clearInternalWindow();
setInternalWindow(nullptr);
}
if (decoration()) {
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
clearDecoration();
setDecoration(nullptr);
}
if (window()) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(window().data())) {
if (focus()) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(focus().data())) {
c->leaveEvent();
}
disconnect(m_windowGeometryConnection);
m_windowGeometryConnection = QMetaObject::Connection();
breakPointerConstraints(window()->surface());
disconnect(m_focusGeometryConnection);
m_focusGeometryConnection = QMetaObject::Connection();
breakPointerConstraints(focus()->surface());
disconnectPointerConstraintsConnection();
setWindow();
setFocus(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)
{
if (!m_inited) {
if (!inited()) {
return;
}
if (PositionUpdateBlocker::isPositionBlocked()) {
@ -311,14 +278,13 @@ void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &de
delta, deltaNonAccelerated, timeUsec, device);
event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts());
update();
input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event));
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)
{
updateButton(button, state);
QEvent::Type type;
switch (state) {
case InputRedirection::PointerButtonReleased:
@ -326,12 +292,15 @@ void PointerInputRedirection::processButton(uint32_t button, InputRedirection::P
break;
case InputRedirection::PointerButtonPressed:
type = QEvent::MouseButtonPress;
update();
break;
default:
Q_UNREACHABLE();
return;
}
updateButton(button, state);
MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons,
input()->keyboardModifiers(), time, QSizeF(), QSizeF(), 0, device);
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));
if (!m_inited) {
if (!inited()) {
return;
}
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)
@ -351,6 +324,7 @@ void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qr
if (delta == 0) {
return;
}
update();
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));
if (!m_inited) {
if (!inited()) {
return;
}
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)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
@ -381,9 +355,10 @@ void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, quint32
void PointerInputRedirection::processSwipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
update();
input()->processSpies(std::bind(&InputEventSpy::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)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
update();
input()->processSpies(std::bind(&InputEventSpy::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)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
update();
input()->processSpies(std::bind(&InputEventSpy::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)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
update();
input()->processSpies(std::bind(&InputEventSpy::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)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
update();
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));
@ -436,9 +415,10 @@ void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angle
void PointerInputRedirection::processPinchGestureEnd(quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
update();
input()->processSpies(std::bind(&InputEventSpy::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)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
update();
input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time));
input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time));
@ -465,104 +446,160 @@ bool PointerInputRedirection::areButtonsPressed() const
return false;
}
static bool s_cursorUpdateBlocking = false;
void PointerInputRedirection::update()
bool PointerInputRedirection::focusUpdatesBlocked()
{
if (!m_inited) {
return;
if (!inited()) {
return true;
}
if (waylandServer()->seat()->isDragPointer()) {
// ignore during drag and drop
return;
return true;
}
if (waylandServer()->seat()->isTouchSequence()) {
// ignore during touch operations
return true;
}
if (input()->isSelectingWindow()) {
return;
return true;
}
if (areButtonsPressed()) {
return;
return true;
}
Toplevel *t = input()->findToplevel(m_pos.toPoint());
const auto oldDeco = decoration();
updateInternalWindow(m_pos);
if (!internalWindow()) {
updateDecoration(t, m_pos);
} else {
updateDecoration(waylandServer()->findClient(internalWindow()), m_pos);
if (decoration()) {
disconnect(m_internalWindowConnection);
m_internalWindowConnection = QMetaObject::Connection();
QEvent event(QEvent::Leave);
QCoreApplication::sendEvent(internalWindow().data(), &event);
clearInternalWindow();
}
return false;
}
void PointerInputRedirection::cleanupInternalWindow(QWindow *old, QWindow *now)
{
disconnect(m_internalWindowConnection);
m_internalWindowConnection = QMetaObject::Connection();
if (old) {
// leave internal window
// TODO: do this instead via Wayland protocol as below
QEvent leaveEvent(QEvent::Leave);
QCoreApplication::sendEvent(old, &leaveEvent);
}
if (decoration() || internalWindow()) {
t = nullptr;
}
if (decoration() != oldDeco) {
emit decorationChanged();
}
auto oldWindow = window();
if (!oldWindow.isNull() && t == window().data()) {
return;
}
auto seat = waylandServer()->seat();
// disconnect old surface
if (oldWindow) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(oldWindow.data())) {
c->leaveEvent();
}
disconnect(m_windowGeometryConnection);
m_windowGeometryConnection = QMetaObject::Connection();
breakPointerConstraints(oldWindow->surface());
disconnectPointerConstraintsConnection();
}
if (AbstractClient *c = qobject_cast<AbstractClient*>(t)) {
// only send enter if it wasn't on deco for the same client before
if (decoration().isNull() || decoration()->client() != c) {
c->enterEvent(m_pos.toPoint());
workspace()->updateFocusMousePosition(m_pos.toPoint());
}
}
if (t && t->surface()) {
setWindow(t);
// TODO: add convenient API to update global pos together with updating focused surface
warpXcbOnSurfaceLeft(t->surface());
s_cursorUpdateBlocking = true;
seat->setFocusedPointerSurface(nullptr);
s_cursorUpdateBlocking = false;
seat->setPointerPos(m_pos.toPoint());
seat->setFocusedPointerSurface(t->surface(), t->inputTransformation());
m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this,
[this] {
if (window().isNull()) {
return;
if (now) {
m_internalWindowConnection = connect(internalWindow().data(), &QWindow::visibleChanged, this,
[this] (bool visible) {
if (!visible) {
update();
}
// TODO: can we check on the client instead?
if (workspace()->getMovingClient()) {
// don't update while moving
return;
}
auto seat = waylandServer()->seat();
if (window().data()->surface() != seat->focusedPointerSurface()) {
return;
}
seat->setFocusedPointerSurfaceTransformation(window().data()->inputTransformation());
}
);
m_constraintsConnection = connect(window()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged,
this, &PointerInputRedirection::updatePointerConstraints);
m_constraintsActivatedConnection = connect(workspace(), &Workspace::clientActivated,
this, &PointerInputRedirection::updatePointerConstraints);
// check whether a pointer confinement/lock fires
updatePointerConstraints();
} else {
setWindow();
}
}
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;
}
waylandServer()->seat()->setFocusedPointerSurface(nullptr);
auto pos = m_pos - now->client()->pos();
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);
}
}, Qt::QueuedConnection);
}
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();
}
disconnect(m_focusGeometryConnection);
m_focusGeometryConnection = QMetaObject::Connection();
if (AbstractClient *ac = qobject_cast<AbstractClient*>(focusNow)) {
ac->enterEvent(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);
t = nullptr;
return;
}
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
warpXcbOnSurfaceLeft(focusNow->surface());
// TODO: why? in order to reset the cursor icon?
s_cursorUpdateBlocking = true;
seat->setFocusedPointerSurface(nullptr);
s_cursorUpdateBlocking = false;
seat->setPointerPos(m_pos.toPoint());
seat->setFocusedPointerSurface(focusNow->surface(), focusNow->inputTransformation());
m_focusGeometryConnection = connect(focusNow, &Toplevel::geometryChanged, this,
[this] {
// TODO: why no assert possible?
if (!focus()) {
return;
}
// TODO: can we check on the client instead?
if (workspace()->getMovingClient()) {
// don't update while moving
return;
}
auto seat = waylandServer()->seat();
if (focus()->surface() != seat->focusedPointerSurface()) {
return;
}
seat->setFocusedPointerSurfaceTransformation(focus()->inputTransformation());
}
);
m_constraintsConnection = connect(focusNow->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged,
this, &PointerInputRedirection::updatePointerConstraints);
m_constraintsActivatedConnection = connect(workspace(), &Workspace::clientActivated,
this, &PointerInputRedirection::updatePointerConstraints);
updatePointerConstraints();
}
void PointerInputRedirection::breakPointerConstraints(KWayland::Server::SurfaceInterface *surface)
@ -624,10 +661,10 @@ void PointerInputRedirection::setEnableConstraints(bool set)
void PointerInputRedirection::updatePointerConstraints()
{
if (window().isNull()) {
if (focus().isNull()) {
return;
}
const auto s = window()->surface();
const auto s = focus()->surface();
if (!s) {
return;
}
@ -637,7 +674,7 @@ void PointerInputRedirection::updatePointerConstraints()
if (!supportsWarping()) {
return;
}
const bool canConstrain = m_enableConstraints && window() == workspace()->activeClient();
const bool canConstrain = m_enableConstraints && focus() == workspace()->activeClient();
const auto cf = s->confinedPointer();
if (cf) {
if (cf->isConfined()) {
@ -648,21 +685,21 @@ void PointerInputRedirection::updatePointerConstraints()
}
return;
}
const QRegion r = getConstraintRegion(window().data(), cf.data());
const QRegion r = getConstraintRegion(focus().data(), cf.data());
if (canConstrain && r.contains(m_pos.toPoint())) {
cf->setConfined(true);
m_confined = true;
m_confinedPointerRegionConnection = connect(cf.data(), &KWayland::Server::ConfinedPointerInterface::regionChanged, this,
[this] {
if (!window()) {
if (!focus()) {
return;
}
const auto s = window()->surface();
const auto s = focus()->surface();
if (!s) {
return;
}
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
cf->setConfined(false);
m_confined = false;
@ -688,13 +725,13 @@ void PointerInputRedirection::updatePointerConstraints()
lock->setLocked(false);
m_locked = false;
disconnectLockedPointerAboutToBeUnboundConnection();
if (! (hint.x() < 0 || hint.y() < 0) && window()) {
processMotion(window()->pos() - window()->clientContentPos() + hint, waylandServer()->seat()->timestamp());
if (! (hint.x() < 0 || hint.y() < 0) && focus()) {
processMotion(focus()->pos() - focus()->clientContentPos() + hint, waylandServer()->seat()->timestamp());
}
}
return;
}
const QRegion r = getConstraintRegion(window().data(), lock.data());
const QRegion r = getConstraintRegion(focus().data(), lock.data());
if (canConstrain && r.contains(m_pos.toPoint())) {
lock->setLocked(true);
m_locked = true;
@ -704,10 +741,10 @@ void PointerInputRedirection::updatePointerConstraints()
m_lockedPointerAboutToBeUnboundConnection = connect(lock.data(), &KWayland::Server::LockedPointerInterface::aboutToBeUnbound, this,
[this, lock]() {
const auto hint = lock->cursorPositionHint();
if (hint.x() < 0 || hint.y() < 0 || !window()) {
if (hint.x() < 0 || hint.y() < 0 || !focus()) {
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
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
{
if (!window()) {
if (!focus()) {
return pos;
}
auto s = window()->surface();
auto s = focus()->surface();
if (!s) {
return pos;
}
@ -770,7 +807,7 @@ QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) con
return pos;
}
const QRegion confinementRegion = getConstraintRegion(window().data(), cf.data());
const QRegion confinementRegion = getConstraintRegion(focus().data(), cf.data());
if (confinementRegion.contains(pos.toPoint())) {
return pos;
}
@ -843,7 +880,7 @@ void PointerInputRedirection::warp(const QPointF &pos)
bool PointerInputRedirection::supportsWarping() const
{
if (!m_inited) {
if (!inited()) {
return false;
}
if (m_supportsWarping) {
@ -857,7 +894,7 @@ bool PointerInputRedirection::supportsWarping() const
void PointerInputRedirection::updateAfterScreenChange()
{
if (!m_inited) {
if (!inited()) {
return;
}
if (screenContainsPos(m_pos)) {
@ -872,7 +909,7 @@ void PointerInputRedirection::updateAfterScreenChange()
QImage PointerInputRedirection::cursorImage() const
{
if (!m_inited) {
if (!inited()) {
return QImage();
}
return m_cursor->image();
@ -880,7 +917,7 @@ QImage PointerInputRedirection::cursorImage() const
QPoint PointerInputRedirection::cursorHotSpot() const
{
if (!m_inited) {
if (!inited()) {
return QPoint();
}
return m_cursor->hotSpot();
@ -888,15 +925,20 @@ QPoint PointerInputRedirection::cursorHotSpot() const
void PointerInputRedirection::markCursorAsRendered()
{
if (!m_inited) {
if (!inited()) {
return;
}
m_cursor->markAsRendered();
}
QPointF PointerInputRedirection::position() const
{
return m_pos.toPoint();
}
void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape)
{
if (!m_inited) {
if (!inited()) {
return;
}
// current pointer focus window should get a leave event
@ -906,7 +948,7 @@ void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape)
void PointerInputRedirection::removeEffectsOverrideCursor()
{
if (!m_inited) {
if (!inited()) {
return;
}
// 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)
{
if (!m_inited) {
if (!inited()) {
return;
}
// send leave to current pointer focus window
@ -926,7 +968,7 @@ void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape)
void PointerInputRedirection::removeWindowSelectionCursor()
{
if (!m_inited) {
if (!inited()) {
return;
}
update();
@ -1302,7 +1344,7 @@ void CursorImage::reevaluteSource()
setSource(CursorSource::Decoration);
return;
}
if (!m_pointer->window().isNull() && waylandServer()->seat()->focusedPointer()) {
if (!m_pointer->focus().isNull() && waylandServer()->seat()->focusedPointer()) {
setSource(CursorSource::PointerSurface);
return;
}

View File

@ -3,6 +3,7 @@
This file is part of the KDE project.
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
it under the terms of the GNU General Public License as published by
@ -64,7 +65,6 @@ public:
void init();
void update();
void updateAfterScreenChange();
bool supportsWarping() const;
void warp(const QPointF &pos);
@ -75,6 +75,7 @@ public:
Qt::MouseButtons buttons() const {
return m_qtButtons;
}
bool areButtonsPressed() const;
QImage cursorImage() const;
QPoint cursorHotSpot() const;
@ -92,6 +93,8 @@ public:
return m_confined || m_locked;
}
bool focusUpdatesBlocked() override;
/**
* @internal
*/
@ -142,6 +145,13 @@ public:
void processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device = nullptr);
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 updateToReset();
void updatePosition(const QPointF &pos);
@ -152,14 +162,12 @@ private:
void disconnectLockedPointerAboutToBeUnboundConnection();
void disconnectPointerConstraintsConnection();
void breakPointerConstraints(KWayland::Server::SurfaceInterface *surface);
bool areButtonsPressed() const;
CursorImage *m_cursor;
bool m_inited = false;
bool m_supportsWarping;
QPointF m_pos;
QHash<uint32_t, InputRedirection::PointerButtonState> m_buttons;
Qt::MouseButtons m_qtButtons;
QMetaObject::Connection m_windowGeometryConnection;
QMetaObject::Connection m_focusGeometryConnection;
QMetaObject::Connection m_internalWindowConnection;
QMetaObject::Connection m_constraintsConnection;
QMetaObject::Connection m_constraintsActivatedConnection;

View File

@ -3,6 +3,7 @@
This file is part of the KDE project.
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
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 "abstract_client.h"
#include "input.h"
#include "pointer_input.h"
#include "input_event_spy.h"
#include "toplevel.h"
#include "wayland_server.h"
@ -47,8 +49,9 @@ TouchInputRedirection::~TouchInputRedirection() = default;
void TouchInputRedirection::init()
{
Q_ASSERT(!m_inited);
m_inited = true;
Q_ASSERT(!inited());
setInited(true);
InputDeviceHandler::init();
if (waylandServer()->hasScreenLockerIntegration()) {
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(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; });
connect(workspace(), &QObject::destroyed, this, [this] { setInited(false); });
connect(waylandServer(), &QObject::destroyed, this, [this] { setInited(false); });
}
void TouchInputRedirection::update(const QPointF &pos)
bool TouchInputRedirection::focusUpdatesBlocked()
{
if (!m_inited) {
return;
if (!inited()) {
return true;
}
if (m_windowUpdatedInCycle) {
return;
return 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
Toplevel *t = input()->findToplevel(pos.toPoint());
auto oldWindow = window();
updateInternalWindow(pos);
if (!internalWindow()) {
updateDecoration(t, pos);
} else {
// TODO: send hover leave to decoration
if (decoration()) {
decoration()->client()->leaveEvent();
}
clearDecoration();
if (AbstractClient *ac = qobject_cast<AbstractClient*>(focusOld)) {
ac->leaveEvent();
}
if (decoration() || internalWindow()) {
t = nullptr;
} else if (!decoration()) {
m_decorationId = -1;
} else if (!internalWindow()) {
m_internalId = -1;
}
if (!oldWindow.isNull() && t == oldWindow.data()) {
return;
disconnect(m_focusGeometryConnection);
m_focusGeometryConnection = QMetaObject::Connection();
if (AbstractClient *ac = qobject_cast<AbstractClient*>(focusNow)) {
ac->enterEvent(m_lastPosition.toPoint());
workspace()->updateFocusMousePosition(m_lastPosition.toPoint());
}
auto seat = waylandServer()->seat();
// disconnect old surface
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
seat->setFocusedTouchSurface(t->surface(), -1 * t->inputTransformation().map(t->pos()) + t->pos());
m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this,
[this] {
if (window().isNull()) {
return;
}
auto seat = waylandServer()->seat();
if (window().data()->surface() != seat->focusedTouchSurface()) {
return;
}
auto t = window().data();
seat->setFocusedTouchSurfacePosition(-1 * t->inputTransformation().map(t->pos()) + t->pos());
}
);
} else {
if (!focusNow || !focusNow->surface() || decoration()) {
// no new surface or internal window or on decoration -> cleanup
seat->setFocusedTouchSurface(nullptr);
t = nullptr;
}
if (!t) {
setWindow();
return;
}
setWindow(t);
// TODO: invalidate pointer focus?
// FIXME: add input transformation API to KWayland::Server::SeatInterface for touch input
seat->setFocusedTouchSurface(focusNow->surface(), -1 * focusNow->inputTransformation().map(focusNow->pos()) + focusNow->pos());
m_focusGeometryConnection = connect(focusNow, &Toplevel::geometryChanged, this,
[this] {
if (focus().isNull()) {
return;
}
auto seat = waylandServer()->seat();
if (focus().data()->surface() != seat->focusedTouchSurface()) {
return;
}
seat->setFocusedTouchSurfacePosition(-1 * focus()->inputTransformation().map(focus()->pos()) + focus()->pos());
}
);
}
void TouchInputRedirection::cleanupInternalWindow(QWindow *old, QWindow *now)
{
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);
}
}
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)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
m_lastPosition = pos;
m_windowUpdatedInCycle = false;
if (m_touches == 0) {
update();
}
m_touches++;
input()->processSpies(std::bind(&InputEventSpy::touchDown, std::placeholders::_1, id, pos, time));
input()->processFilters(std::bind(&InputEventFilter::touchDown, std::placeholders::_1, id, pos, time));
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)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
m_windowUpdatedInCycle = false;
input()->processSpies(std::bind(&InputEventSpy::touchUp, std::placeholders::_1, id, time));
input()->processFilters(std::bind(&InputEventFilter::touchUp, std::placeholders::_1, id, time));
m_windowUpdatedInCycle = false;
m_touches--;
if (m_touches == 0) {
update();
}
}
void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device)
{
Q_UNUSED(device)
if (!m_inited) {
if (!inited()) {
return;
}
m_lastPosition = pos;
m_windowUpdatedInCycle = false;
input()->processSpies(std::bind(&InputEventSpy::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()
{
if (!m_inited) {
if (!inited()) {
return;
}
waylandServer()->seat()->cancelTouchSequence();
@ -194,7 +217,7 @@ void TouchInputRedirection::cancel()
void TouchInputRedirection::frame()
{
if (!m_inited) {
if (!inited()) {
return;
}
waylandServer()->seat()->touchFrame();

View File

@ -3,6 +3,7 @@
This file is part of the KDE project.
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
it under the terms of the GNU General Public License as published by
@ -49,7 +50,7 @@ public:
explicit TouchInputRedirection(InputRedirection *parent);
virtual ~TouchInputRedirection();
void update(const QPointF &pos = QPointF());
bool focusUpdatesBlocked() override;
void init();
void processDown(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device = nullptr);
@ -75,7 +76,16 @@ public:
return m_internalId;
}
QPointF position() const override {
return m_lastPosition;
}
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;
qint32 m_decorationId = -1;
qint32 m_internalId = -1;
@ -83,8 +93,11 @@ private:
* external/kwayland
**/
QHash<qint32, qint32> m_idMapper;
QMetaObject::Connection m_windowGeometryConnection;
QMetaObject::Connection m_focusGeometryConnection;
bool m_windowUpdatedInCycle = false;
QPointF m_lastPosition;
int m_touches = 0;
};
}