Don't update the focused pointer Surface if a button is pressed

Summary:
During pointer motion we already had the condition that an update of
focused pointer surface can only happen when no button is pressed. But
there are more conditions where we try to update the focused pointer even
if a button is pressed. E.g. if the stacking order changes.

This happens when trying to move one of Qt's dock widgets:
 1. Press inside a dock widget
 2. Qt opens another window, which is underneath the cursor
 3. KWin sends pointer leave to parent window
 4. dock widget movement breaks

This change ensures that also this sequence works as expected and the
pointer gets only updated when there are no buttons pressed, no matter
from where we go into the update code path.

BUG: 372876

Test Plan: Dock widgets in Dolphin can be moved now.

Reviewers: #kwin, #plasma

Subscribers: plasma-devel, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D5461
This commit is contained in:
Martin Gräßlin 2017-04-15 11:36:21 +02:00
parent c3ecf55bf8
commit 9a13743c49
3 changed files with 70 additions and 4 deletions

View file

@ -75,6 +75,7 @@ private Q_SLOTS:
void testEffectOverrideCursorImage();
void testPopup();
void testDecoCancelsPopup();
void testWindowUnderCursorWhileButtonPressed();
private:
void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50));
@ -1125,6 +1126,63 @@ void PointerInputTest::testDecoCancelsPopup()
kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, timestamp++);
}
void PointerInputTest::testWindowUnderCursorWhileButtonPressed()
{
// this test verifies that opening a window underneath the mouse cursor does not
// trigger a leave event if a button is pressed
// see BUG: 372876
// first create a parent surface
using namespace KWayland::Client;
auto pointer = m_seat->createPointer(m_seat);
QVERIFY(pointer);
QVERIFY(pointer->isValid());
QSignalSpy enteredSpy(pointer, &Pointer::entered);
QVERIFY(enteredSpy.isValid());
QSignalSpy leftSpy(pointer, &Pointer::left);
QVERIFY(leftSpy.isValid());
Cursor::setPos(800, 800);
QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
QVERIFY(clientAddedSpy.isValid());
Surface *surface = Test::createSurface(m_compositor);
QVERIFY(surface);
ShellSurface *shellSurface = Test::createShellSurface(surface, surface);
QVERIFY(shellSurface);
render(surface);
QVERIFY(clientAddedSpy.wait());
AbstractClient *window = workspace()->activeClient();
QVERIFY(window);
// move cursor over window
QVERIFY(!window->geometry().contains(QPoint(800, 800)));
Cursor::setPos(window->geometry().center());
QVERIFY(enteredSpy.wait());
// click inside window
quint32 timestamp = 0;
kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++);
// now create a second window as transient
Surface *popupSurface = Test::createSurface(m_compositor);
QVERIFY(popupSurface);
ShellSurface *popupShellSurface = Test::createShellSurface(popupSurface, popupSurface);
QVERIFY(popupShellSurface);
popupShellSurface->setTransient(surface, QPoint(0, 0));
render(popupSurface);
QVERIFY(clientAddedSpy.wait());
auto popupClient = clientAddedSpy.last().first().value<ShellClient*>();
QVERIFY(popupClient);
QVERIFY(popupClient != window);
QCOMPARE(window->geometry(), popupClient->geometry());
QVERIFY(!leftSpy.wait());
kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++);
// now that the button is no longer pressed we should get the leave event
QVERIFY(leftSpy.wait());
QCOMPARE(leftSpy.count(), 1);
QCOMPARE(enteredSpy.count(), 2);
}
}
WAYLANDTEST_MAIN(KWin::PointerInputTest)

View file

@ -223,10 +223,7 @@ public:
auto seat = waylandServer()->seat();
seat->setTimestamp(event->timestamp());
if (event->type() == QEvent::MouseMove) {
if (event->buttons() == Qt::NoButton) {
// update pointer window only if no button is pressed
input()->pointer()->update();
}
if (pointerSurfaceAllowed()) {
seat->setPointerPos(event->screenPos().toPoint());
}

View file

@ -436,6 +436,17 @@ void PointerInputRedirection::update()
if (input()->isSelectingWindow()) {
return;
}
auto areButtonsPressed = [this] {
for (auto state : qAsConst(m_buttons)) {
if (state == InputRedirection::PointerButtonPressed) {
return true;
}
}
return false;
};
if (areButtonsPressed()) {
return;
}
Toplevel *t = m_input->findToplevel(m_pos.toPoint());
const auto oldDeco = m_decoration;
updateInternalWindow(m_pos);