From 6b1e2f24f4f131afc15f830a456ee059a4325415 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Sat, 16 Mar 2024 13:22:42 +0200 Subject: [PATCH] autotests: Add _NET_WM_MOVERESIZE tests --- .../integration/move_resize_window_test.cpp | 71 --- autotests/integration/x11_window_test.cpp | 439 ++++++++++++++++++ src/window.h | 8 +- 3 files changed, 443 insertions(+), 75 deletions(-) diff --git a/autotests/integration/move_resize_window_test.cpp b/autotests/integration/move_resize_window_test.cpp index 92baf93d5f..bfb579d6e3 100644 --- a/autotests/integration/move_resize_window_test.cpp +++ b/autotests/integration/move_resize_window_test.cpp @@ -54,7 +54,6 @@ private Q_SLOTS: void testPointerMoveEnd_data(); void testPointerMoveEnd(); void testClientSideMove(); - void testNetMove(); void testAdjustClientGeometryOfHiddenX11Panel_data(); void testAdjustClientGeometryOfHiddenX11Panel(); void testAdjustClientGeometryOfHiddenWaylandPanel_data(); @@ -536,76 +535,6 @@ void MoveResizeWindowTest::testClientSideMove() QCOMPARE(pointerEnteredSpy.last().last().toPoint(), QPoint(50, 25)); } -void MoveResizeWindowTest::testNetMove() -{ - // this test verifies that a move request for an X11 window through NET API works - // create an xcb window - Test::XcbConnectionPtr c = Test::createX11Connection(); - QVERIFY(!xcb_connection_has_error(c.get())); - - xcb_window_t windowId = xcb_generate_id(c.get()); - xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), - 0, 0, 100, 100, - 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); - xcb_size_hints_t hints; - memset(&hints, 0, sizeof(hints)); - xcb_icccm_size_hints_set_position(&hints, 1, 0, 0); - xcb_icccm_size_hints_set_size(&hints, 1, 100, 100); - xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); - // let's set a no-border - NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::WMWindowType, NET::Properties2()); - winInfo.setWindowType(NET::Override); - xcb_map_window(c.get(), windowId); - xcb_flush(c.get()); - - QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); - QVERIFY(windowCreatedSpy.wait()); - X11Window *window = windowCreatedSpy.first().first().value(); - QVERIFY(window); - QCOMPARE(window->window(), windowId); - const QRectF origGeo = window->frameGeometry(); - - QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); - QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); - QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); - QVERIFY(!workspace()->moveResizeWindow()); - - // use NETRootInfo to trigger a move request - quint32 timestamp = 0; - Test::pointerMotion(window->frameGeometry().center(), timestamp++); - Test::pointerButtonPressed(BTN_LEFT, timestamp++); - NETRootInfo root(c.get(), NET::Properties()); - root.moveResizeRequest(windowId, origGeo.center().x(), origGeo.center().y(), NET::Move, XCB_BUTTON_INDEX_1); - xcb_flush(c.get()); - - QVERIFY(interactiveMoveResizeStartedSpy.wait()); - QCOMPARE(workspace()->moveResizeWindow(), window); - QVERIFY(window->isInteractiveMove()); - QCOMPARE(window->geometryRestore(), origGeo); - QCOMPARE(Cursors::self()->mouse()->pos(), origGeo.center()); - - // let's move a step - Test::pointerMotionRelative(QPoint(10, 10), timestamp++); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); - QCOMPARE(interactiveMoveResizeSteppedSpy.first().last(), origGeo.translated(10, 10)); - - // let's cancel the move resize again through the net API - root.moveResizeRequest(windowId, window->frameGeometry().center().x(), window->frameGeometry().center().y(), NET::MoveResizeCancel, XCB_BUTTON_INDEX_1); - xcb_flush(c.get()); - QVERIFY(interactiveMoveResizeFinishedSpy.wait()); - - // and destroy the window again - xcb_unmap_window(c.get(), windowId); - xcb_destroy_window(c.get(), windowId); - xcb_flush(c.get()); - c.reset(); - - QSignalSpy windowClosedSpy(window, &X11Window::closed); - QVERIFY(windowClosedSpy.wait()); - - Test::pointerButtonReleased(BTN_LEFT, timestamp++); -} - void MoveResizeWindowTest::testAdjustClientGeometryOfHiddenX11Panel_data() { QTest::addColumn("panelGeometry"); diff --git a/autotests/integration/x11_window_test.cpp b/autotests/integration/x11_window_test.cpp index 16a5f11230..2a1eeeeace 100644 --- a/autotests/integration/x11_window_test.cpp +++ b/autotests/integration/x11_window_test.cpp @@ -19,6 +19,7 @@ #include +#include #include #include @@ -79,6 +80,17 @@ private Q_SLOTS: void testOnAllDesktops(); void testInitialOnAllDesktops(); void testChangeOnAllDesktops(); + void testNetWmKeyboardMove(); + void testNetWmKeyboardMoveCancel(); + void testNetWmKeyboardResize(); + void testNetWmKeyboardResizeCancel(); + void testNetWmButtonMove(); + void testNetWmButtonMoveNotPressed(); + void testNetWmButtonMoveCancel(); + void testNetWmButtonSize_data(); + void testNetWmButtonSize(); + void testNetWmButtonSizeNotPressed(); + void testNetWmButtonSizeCancel(); void testMinimumSize(); void testMaximumSize(); void testTrimCaption_data(); @@ -1359,6 +1371,433 @@ void X11WindowTest::testChangeOnAllDesktops() QCOMPARE(window->desktops(), (QList{VirtualDesktopManager::self()->desktopForX11Id(1)})); } +void X11WindowTest::testNetWmKeyboardMove() +{ + // This test verifies that a client can initiate a keyboard interactive move operation. + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive move. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::KeyboardMove, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); + QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); + QVERIFY(interactiveMoveResizeStartedSpy.wait()); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); + QVERIFY(window->isInteractiveMove()); + + // Move the window to the right. + const QRectF originalGeometry = window->frameGeometry(); + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); + Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0)); + + // Finish the interactive move. + Test::keyboardKeyPressed(KEY_ENTER, timestamp++); + Test::keyboardKeyReleased(KEY_ENTER, timestamp++); + QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0)); +} + +void X11WindowTest::testNetWmKeyboardMoveCancel() +{ + // This test verifies that a client can initiate a keyboard interactive move operation and then cancel it. + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive move. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::KeyboardMove, XCB_BUTTON_INDEX_ANY); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); + QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); + QVERIFY(interactiveMoveResizeStartedSpy.wait()); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); + QVERIFY(window->isInteractiveMove()); + + // Move the window to the right. + const QRectF originalGeometry = window->frameGeometry(); + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); + Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0)); + + // Cancel the interactive move. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::MoveResizeCancel, XCB_BUTTON_INDEX_ANY); + xcb_flush(c.get()); + } + QVERIFY(interactiveMoveResizeFinishedSpy.wait()); + QCOMPARE(window->frameGeometry(), originalGeometry); +} + +void X11WindowTest::testNetWmKeyboardResize() +{ + // This test verifies that a client can initiate a keyboard interactive resize operation. + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive resize. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width()), Xcb::toXNative(window->y() + window->height()), NET::KeyboardSize, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); + QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); + QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged); + QVERIFY(interactiveMoveResizeStartedSpy.wait()); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); + QVERIFY(window->isInteractiveResize()); + + // Move the window to the right, the frame geometry will be updated some time later. + const QRectF originalGeometry = window->frameGeometry(); + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); + Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(0, 0, 8, 0)); + + // Finish the interactive move. + Test::keyboardKeyPressed(KEY_ENTER, timestamp++); + Test::keyboardKeyReleased(KEY_ENTER, timestamp++); + QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(0, 0, 8, 0)); +} + +void X11WindowTest::testNetWmKeyboardResizeCancel() +{ + // This test verifies that a client can initiate a keyboard interactive resize operation and then cancel it. + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive resize. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width()), Xcb::toXNative(window->y() + window->height()), NET::KeyboardSize, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); + QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); + QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged); + QVERIFY(interactiveMoveResizeStartedSpy.wait()); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); + QVERIFY(window->isInteractiveResize()); + + // Move the window to the right, the frame geometry will be updated some time later. + const QRectF originalGeometry = window->frameGeometry(); + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); + Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(0, 0, 8, 0)); + + // Cancel the interactive move. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width()), Xcb::toXNative(window->y() + window->height()), NET::MoveResizeCancel, XCB_BUTTON_INDEX_ANY); + xcb_flush(c.get()); + } + QVERIFY(interactiveMoveResizeFinishedSpy.wait()); + QCOMPARE(window->frameGeometry(), originalGeometry); +} + +void X11WindowTest::testNetWmButtonMove() +{ + // This test verifies that a client can initiate an interactive move operation controlled by the pointer. + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive move. + const QRectF originalGeometry = window->frameGeometry(); + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::Move, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); + QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); + QVERIFY(interactiveMoveResizeStartedSpy.wait()); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); + QVERIFY(window->isInteractiveMove()); + + // Move the window to the right. + Test::pointerMotionRelative(QPointF(8, 0), timestamp++); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0)); + + // Finish the interactive move. + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0)); +} + +void X11WindowTest::testNetWmButtonMoveNotPressed() +{ + // This test verifies that an interactive move operation won't be started if the specified button is not pressed. + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive move. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::Move, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QVERIFY(!interactiveMoveResizeStartedSpy.wait(10)); +} + +void X11WindowTest::testNetWmButtonMoveCancel() +{ + // This test verifies that a client can initiate an interactive move operation controlled by the pointer and then cancel it. + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive move. + const QRectF originalGeometry = window->frameGeometry(); + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + auto releaseButton = qScopeGuard([×tamp]() { + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + }); + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::Move, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); + QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); + QVERIFY(interactiveMoveResizeStartedSpy.wait()); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); + QVERIFY(window->isInteractiveMove()); + + // Move the window to the right. + Test::pointerMotionRelative(QPointF(8, 0), timestamp++); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0)); + + // Cancel the interactive move. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::MoveResizeCancel, XCB_BUTTON_INDEX_ANY); + xcb_flush(c.get()); + } + QVERIFY(interactiveMoveResizeFinishedSpy.wait()); + QCOMPARE(window->frameGeometry(), originalGeometry); +} + +void X11WindowTest::testNetWmButtonSize_data() +{ + QTest::addColumn("gravity"); + QTest::addColumn("direction"); + + QTest::addRow("top-left") << Gravity::TopLeft << NET::Direction::TopLeft; + QTest::addRow("top") << Gravity::Top << NET::Direction::Top; + QTest::addRow("top-right") << Gravity::TopRight << NET::Direction::TopRight; + QTest::addRow("right") << Gravity::Right << NET::Direction::Right; + QTest::addRow("bottom-right") << Gravity::BottomRight << NET::Direction::BottomRight; + QTest::addRow("bottom") << Gravity::Bottom << NET::Direction::Bottom; + QTest::addRow("bottom-left") << Gravity::BottomLeft << NET::Direction::BottomLeft; + QTest::addRow("left") << Gravity::Left << NET::Direction::Left; +} + +static QPointF directionToVector(NET::Direction direction, const QSizeF &size) +{ + switch (direction) { + case NET::Direction::TopLeft: + return QPointF(-size.width(), -size.height()); + case NET::Direction::Top: + return QPointF(0, -size.height()); + case NET::Direction::TopRight: + return QPointF(size.width(), -size.height()); + case NET::Direction::Right: + return QPointF(size.width(), 0); + case NET::Direction::BottomRight: + return QPointF(size.width(), size.height()); + case NET::Direction::Bottom: + return QPointF(0, size.height()); + case NET::Direction::BottomLeft: + return QPointF(-size.width(), size.height()); + case NET::Direction::Left: + return QPointF(-size.width(), 0); + default: + Q_UNREACHABLE(); + } +} + +static QRectF expandRect(const QRectF &rect, NET::Direction direction, const QSizeF &amount) +{ + switch (direction) { + case NET::Direction::TopLeft: + return rect.adjusted(-amount.width(), -amount.height(), 0, 0); + case NET::Direction::Top: + return rect.adjusted(0, -amount.height(), 0, 0); + case NET::Direction::TopRight: + return rect.adjusted(0, -amount.height(), amount.width(), 0); + case NET::Direction::Right: + return rect.adjusted(0, 0, amount.width(), 0); + case NET::Direction::BottomRight: + return rect.adjusted(0, 0, amount.width(), amount.height()); + case NET::Direction::Bottom: + return rect.adjusted(0, 0, 0, amount.height()); + case NET::Direction::BottomLeft: + return rect.adjusted(-amount.width(), 0, 0, amount.height()); + case NET::Direction::Left: + return rect.adjusted(-amount.width(), 0, 0, 0); + default: + Q_UNREACHABLE(); + } +} + +void X11WindowTest::testNetWmButtonSize() +{ + // This test verifies that a client can initiate an interactive move operation controlled by the pointer. + + QFETCH(NET::Direction, direction); + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive move. + const QRectF originalGeometry = window->frameGeometry(); + const QPointF initialPointer = window->frameGeometry().center() + directionToVector(direction, originalGeometry.size() * 0.5); + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + Test::pointerMotion(initialPointer, timestamp++); + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(initialPointer.x()), Xcb::toXNative(initialPointer.y()), direction, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); + QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); + QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged); + QVERIFY(interactiveMoveResizeStartedSpy.wait()); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); + QVERIFY(window->isInteractiveResize()); + QTEST(window->interactiveMoveResizeGravity(), "gravity"); + + // Resize the window a tiny bit. + Test::pointerMotionRelative(directionToVector(direction, QSizeF(8, 8)), timestamp++); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), expandRect(originalGeometry, direction, QSizeF(8, 8))); + + // Finish the interactive move. + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), expandRect(originalGeometry, direction, QSizeF(8, 8))); +} + +void X11WindowTest::testNetWmButtonSizeNotPressed() +{ + // This test verifies that an interactive siize operation won't be started if the specified button is not pressed. + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive move. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x()), Xcb::toXNative(window->y()), NET::TopLeft, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QVERIFY(!interactiveMoveResizeStartedSpy.wait(10)); +} + +void X11WindowTest::testNetWmButtonSizeCancel() +{ + // This test verifies that a client can start an interactive resize and then cancel it. + + // Create an xcb window. + Test::XcbConnectionPtr c = Test::createX11Connection(); + QVERIFY(!xcb_connection_has_error(c.get())); + X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200)); + + // Request interactive resize. + const QRectF originalGeometry = window->frameGeometry(); + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + auto releaseButton = qScopeGuard([×tamp]() { + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + }); + Test::pointerMotion(originalGeometry.topLeft(), timestamp++); + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x()), Xcb::toXNative(window->y()), NET::TopLeft, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); + QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); + QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); + QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged); + QVERIFY(interactiveMoveResizeStartedSpy.wait()); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); + QVERIFY(window->isInteractiveResize()); + + // Resize the window a tiny bit. + Test::pointerMotionRelative(QPointF(-8, -8), timestamp++); + QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(-8, -8, 0, 0)); + + // Cancel the interactive resize. + { + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(window->window(), Xcb::toXNative(window->x()), Xcb::toXNative(window->y()), NET::MoveResizeCancel, XCB_BUTTON_INDEX_1); + xcb_flush(c.get()); + } + QVERIFY(interactiveMoveResizeFinishedSpy.wait()); + QCOMPARE(window->frameGeometry(), originalGeometry); +} + void X11WindowTest::testMinimumSize() { // This test verifies that the minimum size constraint is correctly applied. diff --git a/src/window.h b/src/window.h index de92482553..51525338cb 100644 --- a/src/window.h +++ b/src/window.h @@ -1115,6 +1115,10 @@ public: { return isInteractiveMoveResize() && interactiveMoveResizeGravity() != Gravity::None; } + Gravity interactiveMoveResizeGravity() const + { + return m_interactiveMoveResize.gravity; + } QPointF interactiveMoveResizeAnchor() const { return m_interactiveMoveResize.anchor; @@ -1618,10 +1622,6 @@ protected: return m_interactiveMoveResize.initialGeometry; } void setMoveResizeGeometry(const QRectF &geo); - Gravity interactiveMoveResizeGravity() const - { - return m_interactiveMoveResize.gravity; - } void setInteractiveMoveResizeGravity(Gravity gravity) { m_interactiveMoveResize.gravity = gravity;