diff --git a/autotests/integration/debug_console_test.cpp b/autotests/integration/debug_console_test.cpp index bff819529..3272abc47 100644 --- a/autotests/integration/debug_console_test.cpp +++ b/autotests/integration/debug_console_test.cpp @@ -1,532 +1,532 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "debug_console.h" #include "screens.h" #include "shell_client.h" #include "wayland_server.h" #include "xcbutils.h" #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_debug_console-0"); class DebugConsoleTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanup(); void topLevelTest_data(); void topLevelTest(); void testX11Client(); void testX11Unmanaged(); void testWaylandClient_data(); void testWaylandClient(); void testInternalWindow(); void testClosingDebugConsole(); }; void DebugConsoleTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void DebugConsoleTest::cleanup() { Test::destroyWaylandConnection(); } void DebugConsoleTest::topLevelTest_data() { QTest::addColumn("row"); QTest::addColumn("column"); QTest::addColumn("expectedValid"); // this tests various combinations of row/column on the top level whether they are valid // valid are rows 0-4 with column 0, everything else is invalid QTest::newRow("0/0") << 0 << 0 << true; QTest::newRow("0/1") << 0 << 1 << false; QTest::newRow("0/3") << 0 << 3 << false; QTest::newRow("1/0") << 1 << 0 << true; QTest::newRow("1/1") << 1 << 1 << false; QTest::newRow("1/3") << 1 << 3 << false; QTest::newRow("2/0") << 2 << 0 << true; QTest::newRow("3/0") << 3 << 0 << true; QTest::newRow("4/0") << 4 << 0 << false; QTest::newRow("100/0") << 4 << 0 << false; } void DebugConsoleTest::topLevelTest() { DebugConsoleModel model; QCOMPARE(model.rowCount(QModelIndex()), 4); QCOMPARE(model.columnCount(QModelIndex()), 2); QFETCH(int, row); QFETCH(int, column); const QModelIndex index = model.index(row, column, QModelIndex()); QTEST(index.isValid(), "expectedValid"); if (index.isValid()) { QVERIFY(!model.parent(index).isValid()); QVERIFY(model.data(index, Qt::DisplayRole).isValid()); QCOMPARE(model.data(index, Qt::DisplayRole).userType(), int(QMetaType::QString)); for (int i = Qt::DecorationRole; i <= Qt::UserRole; i++) { QVERIFY(!model.data(index, i).isValid()); } } } void DebugConsoleTest::testX11Client() { DebugConsoleModel model; QModelIndex x11TopLevelIndex = model.index(0, 0, QModelIndex()); QVERIFY(x11TopLevelIndex.isValid()); // we don't have any windows yet QCOMPARE(model.rowCount(x11TopLevelIndex), 0); QVERIFY(!model.hasChildren(x11TopLevelIndex)); // child index must be invalid QVERIFY(!model.index(0, 0, x11TopLevelIndex).isValid()); QVERIFY(!model.index(0, 1, x11TopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, x11TopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, x11TopLevelIndex).isValid()); // start glxgears, to get a window, which should be added to the model QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); QProcess glxgears; glxgears.start(QStringLiteral("glxgears")); QVERIFY(glxgears.waitForStarted()); QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QVERIFY(model.hasChildren(x11TopLevelIndex)); QCOMPARE(model.rowCount(x11TopLevelIndex), 1); QCOMPARE(rowsInsertedSpy.first().at(0).value(), x11TopLevelIndex); QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); QModelIndex clientIndex = model.index(0, 0, x11TopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), x11TopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(0, 1, x11TopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, x11TopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, x11TopLevelIndex).isValid()); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // creating a second model should be initialized directly with the X11 child DebugConsoleModel model2; QVERIFY(model2.hasChildren(model2.index(0, 0, QModelIndex()))); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); glxgears.terminate(); QVERIFY(glxgears.waitForFinished()); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), x11TopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); // the child should be gone again QVERIFY(!model.hasChildren(x11TopLevelIndex)); QVERIFY(!model2.hasChildren(model2.index(0, 0, QModelIndex()))); } void DebugConsoleTest::testX11Unmanaged() { DebugConsoleModel model; QModelIndex unmanagedTopLevelIndex = model.index(1, 0, QModelIndex()); QVERIFY(unmanagedTopLevelIndex.isValid()); // we don't have any windows yet QCOMPARE(model.rowCount(unmanagedTopLevelIndex), 0); QVERIFY(!model.hasChildren(unmanagedTopLevelIndex)); // child index must be invalid QVERIFY(!model.index(0, 0, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(0, 1, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, unmanagedTopLevelIndex).isValid()); // we need to create an unmanaged window QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); // let's create an override redirect window const uint32_t values[] = {true}; Xcb::Window window(QRect(0, 0, 10, 10), XCB_CW_OVERRIDE_REDIRECT, values); window.map(); QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QVERIFY(model.hasChildren(unmanagedTopLevelIndex)); QCOMPARE(model.rowCount(unmanagedTopLevelIndex), 1); QCOMPARE(rowsInsertedSpy.first().at(0).value(), unmanagedTopLevelIndex); QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); QModelIndex clientIndex = model.index(0, 0, unmanagedTopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), unmanagedTopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(0, 1, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, unmanagedTopLevelIndex).isValid()); QCOMPARE(model.data(clientIndex, Qt::DisplayRole).toString(), QString::number(window)); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // creating a second model should be initialized directly with the X11 child DebugConsoleModel model2; QVERIFY(model2.hasChildren(model2.index(1, 0, QModelIndex()))); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); window.unmap(); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), unmanagedTopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); // the child should be gone again QVERIFY(!model.hasChildren(unmanagedTopLevelIndex)); QVERIFY(!model2.hasChildren(model2.index(1, 0, QModelIndex()))); } void DebugConsoleTest::testWaylandClient_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void DebugConsoleTest::testWaylandClient() { DebugConsoleModel model; QModelIndex waylandTopLevelIndex = model.index(2, 0, QModelIndex()); QVERIFY(waylandTopLevelIndex.isValid()); // we don't have any windows yet QCOMPARE(model.rowCount(waylandTopLevelIndex), 0); QVERIFY(!model.hasChildren(waylandTopLevelIndex)); // child index must be invalid QVERIFY(!model.index(0, 0, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(0, 1, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, waylandTopLevelIndex).isValid()); // we need to create a wayland window QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); // create our connection - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); // create the Surface and ShellSurface using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(surface->isValid()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QVERIFY(!shellSurface.isNull()); Test::render(surface.data(), QSize(10, 10), Qt::red); // now we have the window, it should be added to our model QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QVERIFY(model.hasChildren(waylandTopLevelIndex)); QCOMPARE(model.rowCount(waylandTopLevelIndex), 1); QCOMPARE(rowsInsertedSpy.first().at(0).value(), waylandTopLevelIndex); QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); QModelIndex clientIndex = model.index(0, 0, waylandTopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), waylandTopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(0, 1, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, waylandTopLevelIndex).isValid()); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // creating a second model should be initialized directly with the X11 child DebugConsoleModel model2; QVERIFY(model2.hasChildren(model2.index(2, 0, QModelIndex()))); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); shellSurface.reset(); Test::flushWaylandConnection(); qDebug() << rowsRemovedSpy.count(); QEXPECT_FAIL("wlShell", "Deleting a ShellSurface does not result in the server removing the ShellClient", Continue); QVERIFY(rowsRemovedSpy.wait()); surface.reset(); if (rowsRemovedSpy.isEmpty()) { QVERIFY(rowsRemovedSpy.wait()); } QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), waylandTopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); // the child should be gone again QVERIFY(!model.hasChildren(waylandTopLevelIndex)); QVERIFY(!model2.hasChildren(model2.index(2, 0, QModelIndex()))); } class HelperWindow : public QRasterWindow { Q_OBJECT public: HelperWindow() : QRasterWindow(nullptr) {} ~HelperWindow() = default; Q_SIGNALS: void entered(); void left(); void mouseMoved(const QPoint &global); void mousePressed(); void mouseReleased(); void wheel(); void keyPressed(); void keyReleased(); protected: void paintEvent(QPaintEvent *event) override { Q_UNUSED(event) QPainter p(this); p.fillRect(0, 0, width(), height(), Qt::red); } }; void DebugConsoleTest::testInternalWindow() { DebugConsoleModel model; QModelIndex internalTopLevelIndex = model.index(3, 0, QModelIndex()); QVERIFY(internalTopLevelIndex.isValid()); // there might already be some internal windows, so we cannot reliable test whether there are children // given that we just test whether adding a window works. QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); QScopedPointer w(new HelperWindow); w->setGeometry(0, 0, 100, 100); w->show(); QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QCOMPARE(rowsInsertedSpy.first().first().value(), internalTopLevelIndex); QModelIndex clientIndex = model.index(rowsInsertedSpy.first().last().toInt(), 0, internalTopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), internalTopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt(), 1, internalTopLevelIndex).isValid()); QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt(), 2, internalTopLevelIndex).isValid()); QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt() + 1, 0, internalTopLevelIndex).isValid()); // the wayland shell client top level should not have gained this window QVERIFY(!model.hasChildren(model.index(2, 0, QModelIndex()))); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); w->hide(); w.reset(); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), internalTopLevelIndex); } void DebugConsoleTest::testClosingDebugConsole() { // this test verifies that the DebugConsole gets destroyed when closing the window // BUG: 369858 DebugConsole *console = new DebugConsole; QSignalSpy destroyedSpy(console, &QObject::destroyed); QVERIFY(destroyedSpy.isValid()); QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); console->show(); QCOMPARE(console->windowHandle()->isVisible(), true); QTRY_COMPARE(clientAddedSpy.count(), 1); ShellClient *c = clientAddedSpy.first().first().value(); QVERIFY(c->isInternal()); QCOMPARE(c->internalWindow(), console->windowHandle()); QVERIFY(c->isDecorated()); QCOMPARE(c->isMinimizable(), false); c->closeWindow(); QVERIFY(destroyedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::DebugConsoleTest) #include "debug_console_test.moc" diff --git a/autotests/integration/decoration_input_test.cpp b/autotests/integration/decoration_input_test.cpp index 72c205235..83020d3a3 100644 --- a/autotests/integration/decoration_input_test.cpp +++ b/autotests/integration/decoration_input_test.cpp @@ -1,569 +1,569 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "pointer_input.h" #include "touch_input.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(Qt::WindowFrameSection) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_decoration_input-0"); class DecorationInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testAxis_data(); void testAxis(); void testDoubleClick_data(); void testDoubleClick(); void testDoubleTap_data(); void testDoubleTap(); void testHover_data(); void testHover(); void testPressToMove_data(); void testPressToMove(); void testTapToMove_data(); void testTapToMove(); void testResizeOutsideWindow_data(); void testResizeOutsideWindow(); private: AbstractClient *showWindow(Test::ShellSurfaceType type); }; #define MOTION(target) \ kwinApp()->platform()->pointerMotion(target, timestamp++) #define PRESS \ kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++) #define RELEASE \ kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++) AbstractClient *DecorationInputTest::showWindow(Test::ShellSurfaceType type) { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; Surface *surface = Test::createSurface(Test::waylandCompositor()); VERIFY(surface); auto shellSurface = Test::createShellSurface(type, surface, surface); VERIFY(shellSurface); auto deco = Test::waylandServerSideDecoration()->create(surface, surface); QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged); VERIFY(decoSpy.isValid()); VERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); VERIFY(decoSpy.wait()); COMPARE(deco->mode(), ServerSideDecoration::Mode::Server); // let's render auto c = Test::renderAndWaitForShown(surface, QSize(500, 50), Qt::blue); VERIFY(c); COMPARE(workspace()->activeClient(), c); #undef VERIFY #undef COMPARE return c; } void DecorationInputTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // change some options KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group(QStringLiteral("MouseBindings")).writeEntry("CommandTitlebarWheel", QStringLiteral("above/below")); config->group(QStringLiteral("Windows")).writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops")); config->group(QStringLiteral("Desktops")).writeEntry("Number", 2); config->sync(); kwinApp()->setConfig(config); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void DecorationInputTest::init() { using namespace KWayland::Client; - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration)); QVERIFY(Test::waitForWaylandPointer()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void DecorationInputTest::cleanup() { Test::destroyWaylandConnection(); } void DecorationInputTest::testAxis_data() { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); QTest::addColumn("type"); QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topLeft|xdg") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdg") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdg") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; } void DecorationInputTest::testAxis() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QCOMPARE(c->titlebarPosition(), AbstractClient::PositionTop); QVERIFY(!c->keepAbove()); QVERIFY(!c->keepBelow()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); QVERIFY(!input()->pointer()->decoration().isNull()); QCOMPARE(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), Qt::TitleBarArea); // TODO: mouse wheel direction looks wrong to me // simulate wheel kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(c->keepBelow()); QVERIFY(!c->keepAbove()); kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(!c->keepAbove()); kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(c->keepAbove()); // test top most deco pixel, BUG: 362860 c->move(0, 0); QFETCH(QPoint, decoPoint); MOTION(decoPoint); QVERIFY(!input()->pointer()->decoration().isNull()); QCOMPARE(input()->pointer()->decoration()->client(), c); QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(!c->keepAbove()); } void DecorationInputTest::testDoubleClick_data() { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); QTest::addColumn("type"); QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topLeft|xdg") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdg") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdg") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; } void KWin::DecorationInputTest::testDoubleClick() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QVERIFY(!c->isOnAllDesktops()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); // double click PRESS; RELEASE; PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); // double click again PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); PRESS; RELEASE; QVERIFY(!c->isOnAllDesktops()); // test top most deco pixel, BUG: 362860 c->move(0, 0); QFETCH(QPoint, decoPoint); MOTION(decoPoint); QVERIFY(!input()->pointer()->decoration().isNull()); QCOMPARE(input()->pointer()->decoration()->client(), c); QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); // double click PRESS; RELEASE; QVERIFY(!c->isOnAllDesktops()); PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); } void DecorationInputTest::testDoubleTap_data() { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); QTest::addColumn("type"); QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topLeft|xdg") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdg") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdg") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; } void KWin::DecorationInputTest::testDoubleTap() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QVERIFY(!c->isOnAllDesktops()); quint32 timestamp = 1; const QPoint tapPoint(c->geometry().center().x(), c->clientPos().y() / 2); // double tap kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(c->isOnAllDesktops()); // double tap again kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(c->isOnAllDesktops()); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(!c->isOnAllDesktops()); // test top most deco pixel, BUG: 362860 c->move(0, 0); QFETCH(QPoint, decoPoint); // double click kwinApp()->platform()->touchDown(0, decoPoint, timestamp++); QVERIFY(!input()->touch()->decoration().isNull()); QCOMPARE(input()->touch()->decoration()->client(), c); QTEST(input()->touch()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(!c->isOnAllDesktops()); kwinApp()->platform()->touchDown(0, decoPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(c->isOnAllDesktops()); } void DecorationInputTest::testHover_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void DecorationInputTest::testHover() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); // our left border is moved out of the visible area, so move the window to a better place c->move(QPoint(20, 0)); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); QCOMPARE(c->cursor(), Qt::ArrowCursor); MOTION(QPoint(20, 0)); QCOMPARE(c->cursor(), Qt::SizeFDiagCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, 0)); QCOMPARE(c->cursor(), Qt::SizeVerCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, 0)); QCOMPARE(c->cursor(), Qt::SizeBDiagCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, c->height() / 2)); QCOMPARE(c->cursor(), Qt::SizeHorCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, c->height() - 1)); QCOMPARE(c->cursor(), Qt::SizeFDiagCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, c->height() - 1)); QCOMPARE(c->cursor(), Qt::SizeVerCursor); MOTION(QPoint(c->geometry().x(), c->height() - 1)); QCOMPARE(c->cursor(), Qt::SizeBDiagCursor); MOTION(QPoint(c->geometry().x(), c->height() / 2)); QCOMPARE(c->cursor(), Qt::SizeHorCursor); MOTION(c->geometry().center()); QEXPECT_FAIL("", "Cursor not set back on leave", Continue); QCOMPARE(c->cursor(), Qt::ArrowCursor); } void DecorationInputTest::testPressToMove_data() { QTest::addColumn("offset"); QTest::addColumn("offset2"); QTest::addColumn("offset3"); QTest::addColumn("type"); QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To right|xdg") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To left|xdg") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To bottom|xdg") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To top|xdg") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; } void DecorationInputTest::testPressToMove() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); QCOMPARE(c->cursor(), Qt::ArrowCursor); PRESS; QVERIFY(!c->isMove()); QFETCH(QPoint, offset); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset); const QPoint oldPos = c->pos(); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 1); RELEASE; QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); QCOMPARE(c->pos(), oldPos + offset); // again PRESS; QVERIFY(!c->isMove()); QFETCH(QPoint, offset2); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 2); QFETCH(QPoint, offset3); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3); RELEASE; QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); // TODO: the offset should also be included QCOMPARE(c->pos(), oldPos + offset2 + offset3); } void DecorationInputTest::testTapToMove_data() { QTest::addColumn("offset"); QTest::addColumn("offset2"); QTest::addColumn("offset3"); QTest::addColumn("type"); QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To right|xdg") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To left|xdg") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To bottom|xdg") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To top|xdg") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; } void DecorationInputTest::testTapToMove() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); quint32 timestamp = 1; QPoint p = QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2); kwinApp()->platform()->touchDown(0, p, timestamp++); QVERIFY(!c->isMove()); QFETCH(QPoint, offset); QCOMPARE(input()->touch()->decorationPressId(), 0); kwinApp()->platform()->touchMotion(0, p + offset, timestamp++); const QPoint oldPos = c->pos(); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 1); kwinApp()->platform()->touchUp(0, timestamp++); QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); QCOMPARE(c->pos(), oldPos + offset); // again kwinApp()->platform()->touchDown(1, p + offset, timestamp++); QCOMPARE(input()->touch()->decorationPressId(), 1); QVERIFY(!c->isMove()); QFETCH(QPoint, offset2); kwinApp()->platform()->touchMotion(1, QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2, timestamp++); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 2); QFETCH(QPoint, offset3); kwinApp()->platform()->touchMotion(1, QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3, timestamp++); kwinApp()->platform()->touchUp(1, timestamp++); QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); // TODO: the offset should also be included QCOMPARE(c->pos(), oldPos + offset2 + offset3); } void DecorationInputTest::testResizeOutsideWindow_data() { QTest::addColumn("type"); QTest::addColumn("edge"); QTest::addColumn("expectedCursor"); QTest::newRow("wlShell - left") << Test::ShellSurfaceType::WlShell << Qt::LeftEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV5 - left") << Test::ShellSurfaceType::XdgShellV5 << Qt::LeftEdge << Qt::SizeHorCursor; QTest::newRow("wlShell - right") << Test::ShellSurfaceType::WlShell << Qt::RightEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV5 - right") << Test::ShellSurfaceType::XdgShellV5 << Qt::RightEdge << Qt::SizeHorCursor; QTest::newRow("wlShell - bottom") << Test::ShellSurfaceType::WlShell << Qt::BottomEdge << Qt::SizeVerCursor; QTest::newRow("xdgShellV5 - bottom") << Test::ShellSurfaceType::XdgShellV5 << Qt::BottomEdge << Qt::SizeVerCursor; } void DecorationInputTest::testResizeOutsideWindow() { // this test verifies that one can resize the window outside the decoration with NoSideBorder // first adjust config kwinApp()->config()->group("org.kde.kdecoration2").writeEntry("BorderSize", QStringLiteral("None")); kwinApp()->config()->sync(); workspace()->slotReconfigure(); // now create window QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QVERIFY(c->geometry() != c->inputGeometry()); QVERIFY(c->inputGeometry().contains(c->geometry())); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); // go to border quint32 timestamp = 1; QFETCH(Qt::Edge, edge); switch (edge) { case Qt::LeftEdge: MOTION(QPoint(c->geometry().x() -1, c->geometry().center().y())); break; case Qt::RightEdge: MOTION(QPoint(c->geometry().x() + c->geometry().width() +1, c->geometry().center().y())); break; case Qt::BottomEdge: MOTION(QPoint(c->geometry().center().x(), c->geometry().y() + c->geometry().height() + 1)); break; default: break; } QVERIFY(!c->geometry().contains(KWin::Cursor::pos())); // pressing should trigger resize PRESS; QVERIFY(!c->isResize()); QVERIFY(startMoveResizedSpy.wait()); QVERIFY(c->isResize()); RELEASE; QVERIFY(!c->isResize()); } } WAYLANDTEST_MAIN(KWin::DecorationInputTest) #include "decoration_input_test.moc" diff --git a/autotests/integration/dont_crash_cancel_animation.cpp b/autotests/integration/dont_crash_cancel_animation.cpp index 9a7331937..9fe7c8294 100644 --- a/autotests/integration/dont_crash_cancel_animation.cpp +++ b/autotests/integration/dont_crash_cancel_animation.cpp @@ -1,126 +1,126 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "client.h" #include "composite.h" #include "deleted.h" #include "effects.h" #include "effectloader.h" #include "screens.h" #include "shell_client.h" #include "wayland_server.h" #include "workspace.h" #include "scripting/scriptedeffect.h" #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_cancel_animation-0"); class DontCrashCancelAnimationFromAnimationEndedTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testScript(); }; void DontCrashCancelAnimationFromAnimationEndedTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(Compositor::self()); QSignalSpy compositorToggledSpy(Compositor::self(), &Compositor::compositingToggled); QVERIFY(compositorToggledSpy.isValid()); QVERIFY(compositorToggledSpy.wait()); QVERIFY(effects); } void DontCrashCancelAnimationFromAnimationEndedTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); } void DontCrashCancelAnimationFromAnimationEndedTest::cleanup() { Test::destroyWaylandConnection(); } void DontCrashCancelAnimationFromAnimationEndedTest::testScript() { // load a scripted effect which deletes animation data ScriptedEffect *effect = ScriptedEffect::create(QStringLiteral("crashy"), QFINDTESTDATA("data/anim-data-delete-effect/effect.js"), 10); QVERIFY(effect); const auto children = effects->children(); for (auto it = children.begin(); it != children.end(); ++it) { if (qstrcmp((*it)->metaObject()->className(), "KWin::EffectLoader") != 0) { continue; } QVERIFY(QMetaObject::invokeMethod(*it, "effectLoaded", Q_ARG(KWin::Effect*, effect), Q_ARG(QString, QStringLiteral("crashy")))); break; } QVERIFY(static_cast(effects)->isEffectLoaded(QStringLiteral("crashy"))); using namespace KWayland::Client; // create a window Surface *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); ShellSurface *shellSurface = Test::createShellSurface(surface, surface); QVERIFY(shellSurface); // let's render auto c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // make sure we animate QTest::qWait(200); // wait for the window to be passed to Deleted QSignalSpy windowDeletedSpy(c, &AbstractClient::windowClosed); QVERIFY(windowDeletedSpy.isValid()); surface->deleteLater(); QVERIFY(windowDeletedSpy.wait()); // make sure we animate QTest::qWait(200); } } WAYLANDTEST_MAIN(KWin::DontCrashCancelAnimationFromAnimationEndedTest) #include "dont_crash_cancel_animation.moc" diff --git a/autotests/integration/dont_crash_no_border.cpp b/autotests/integration/dont_crash_no_border.cpp index 5b9c8ef7d..359baf2fa 100644 --- a/autotests/integration/dont_crash_no_border.cpp +++ b/autotests/integration/dont_crash_no_border.cpp @@ -1,133 +1,133 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "client.h" #include "cursor.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_no_border-0"); class DontCrashNoBorder : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testCreateWindow_data(); void testCreateWindow(); }; void DontCrashNoBorder::initTestCase() { if (!QFile::exists(QStringLiteral("/dev/dri/card0"))) { QSKIP("Needs a dri device"); } qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("org.kde.kdecoration2").writeEntry("NoPlugin", true); config->sync(); kwinApp()->setConfig(config); // this test needs to enforce OpenGL compositing to get into the crashy condition qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void DontCrashNoBorder::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Decoration)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void DontCrashNoBorder::cleanup() { Test::destroyWaylandConnection(); } void DontCrashNoBorder::testCreateWindow_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void DontCrashNoBorder::testCreateWindow() { // create a window and ensure that this doesn't crash using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QVERIFY(shellSurface); QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), ServerSideDecoration::Mode::Server); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(500, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QVERIFY(!c->isDecorated()); } } WAYLANDTEST_MAIN(KWin::DontCrashNoBorder) #include "dont_crash_no_border.moc" diff --git a/autotests/integration/effects/fade_test.cpp b/autotests/integration/effects/fade_test.cpp index a7ef8a8eb..53c730108 100644 --- a/autotests/integration/effects/fade_test.cpp +++ b/autotests/integration/effects/fade_test.cpp @@ -1,182 +1,182 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "composite.h" #include "effects.h" #include "effectloader.h" #include "cursor.h" #include "platform.h" #include "scene_qpainter.h" #include "shell_client.h" #include "wayland_server.h" #include "workspace.h" #include "effect_builtins.h" #include #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_effects_translucency-0"); class FadeTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testWindowCloseAfterWindowHidden_data(); void testWindowCloseAfterWindowHidden(); private: Effect *m_fadeEffect = nullptr; }; void FadeTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // disable all effects - we don't want to have it interact with the rendering auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup plugins(config, QStringLiteral("Plugins")); ScriptedEffectLoader loader; const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); for (QString name : builtinNames) { plugins.writeEntry(name + QStringLiteral("Enabled"), false); } config->sync(); kwinApp()->setConfig(config); qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QVERIFY(KWin::Compositor::self()); } void FadeTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); // load the translucency effect EffectsHandlerImpl *e = static_cast(effects); // find the effectsloader auto effectloader = e->findChild(); QVERIFY(effectloader); QSignalSpy effectLoadedSpy(effectloader, &AbstractEffectLoader::effectLoaded); QVERIFY(effectLoadedSpy.isValid()); QVERIFY(!e->isEffectLoaded(QStringLiteral("kwin4_effect_fade"))); QVERIFY(e->loadEffect(QStringLiteral("kwin4_effect_fade"))); QVERIFY(e->isEffectLoaded(QStringLiteral("kwin4_effect_fade"))); QCOMPARE(effectLoadedSpy.count(), 1); m_fadeEffect = effectLoadedSpy.first().first().value(); QVERIFY(m_fadeEffect); } void FadeTest::cleanup() { Test::destroyWaylandConnection(); EffectsHandlerImpl *e = static_cast(effects); if (e->isEffectLoaded(QStringLiteral("kwin4_effect_fade"))) { e->unloadEffect(QStringLiteral("kwin4_effect_fade")); } QVERIFY(!e->isEffectLoaded(QStringLiteral("kwin4_effect_fade"))); m_fadeEffect = nullptr; } void FadeTest::testWindowCloseAfterWindowHidden_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void FadeTest::testWindowCloseAfterWindowHidden() { // this test simulates the showing/hiding/closing of a Wayland window // especially the situation that a window got unmapped and destroyed way later QVERIFY(!m_fadeEffect->isActive()); QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); QVERIFY(windowAddedSpy.isValid()); QSignalSpy windowHiddenSpy(effects, &EffectsHandler::windowHidden); QVERIFY(windowHiddenSpy.isValid()); QSignalSpy windowShownSpy(effects, &EffectsHandler::windowShown); QVERIFY(windowShownSpy.isValid()); QSignalSpy windowClosedSpy(effects, &EffectsHandler::windowClosed); QVERIFY(windowClosedSpy.isValid()); QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QTRY_COMPARE(windowAddedSpy.count(), 1); QTRY_COMPARE(m_fadeEffect->isActive(), true); QTest::qWait(500); QTRY_COMPARE(m_fadeEffect->isActive(), false); // now unmap the surface surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(windowHiddenSpy.wait()); QCOMPARE(m_fadeEffect->isActive(), true); QTest::qWait(500); QTRY_COMPARE(m_fadeEffect->isActive(), false); // and map again Test::render(surface.data(), QSize(100, 50), Qt::red); QVERIFY(windowShownSpy.wait()); QTRY_COMPARE(m_fadeEffect->isActive(), true); QTest::qWait(500); QTRY_COMPARE(m_fadeEffect->isActive(), false); // and unmap once more surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(windowHiddenSpy.wait()); QCOMPARE(m_fadeEffect->isActive(), true); QTest::qWait(500); QTRY_COMPARE(m_fadeEffect->isActive(), false); // and now destroy shellSurface.reset(); surface.reset(); QVERIFY(windowClosedSpy.wait()); QCOMPARE(m_fadeEffect->isActive(), false); } WAYLANDTEST_MAIN(FadeTest) #include "fade_test.moc" diff --git a/autotests/integration/effects/slidingpopups_test.cpp b/autotests/integration/effects/slidingpopups_test.cpp index b5d59d5a1..254a00e40 100644 --- a/autotests/integration/effects/slidingpopups_test.cpp +++ b/autotests/integration/effects/slidingpopups_test.cpp @@ -1,375 +1,375 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "composite.h" #include "effects.h" #include "effectloader.h" #include "cursor.h" #include "platform.h" #include "scene_qpainter.h" #include "shell_client.h" #include "wayland_server.h" #include "workspace.h" #include "effect_builtins.h" #include #include #include #include #include #include #include #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_effects_slidingpopups-0"); class SlidingPopupsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testWithOtherEffect_data(); void testWithOtherEffect(); void testWithOtherEffectWayland_data(); void testWithOtherEffectWayland(); }; void SlidingPopupsTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // disable all effects - we don't want to have it interact with the rendering auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup plugins(config, QStringLiteral("Plugins")); ScriptedEffectLoader loader; const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); for (QString name : builtinNames) { plugins.writeEntry(name + QStringLiteral("Enabled"), false); } config->sync(); kwinApp()->setConfig(config); // TODO: make effects use KWin's config directly KSharedConfig::openConfig(QStringLiteral(KWIN_CONFIG), KConfig::NoGlobals)->group("Effect-Wobbly").writeEntry(QStringLiteral("Settings"), QStringLiteral("Custom")); KSharedConfig::openConfig(QStringLiteral(KWIN_CONFIG), KConfig::NoGlobals)->group("Effect-Wobbly").writeEntry(QStringLiteral("OpenEffect"), true); KSharedConfig::openConfig(QStringLiteral(KWIN_CONFIG), KConfig::NoGlobals)->group("Effect-Wobbly").writeEntry(QStringLiteral("CloseEffect"), true); if (QFile::exists(QStringLiteral("/dev/dri/card0"))) { qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); } qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QVERIFY(Compositor::self()); } void SlidingPopupsTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Decoration)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); } void SlidingPopupsTest::cleanup() { Test::destroyWaylandConnection(); EffectsHandlerImpl *e = static_cast(effects); while (!e->loadedEffects().isEmpty()) { const QString effect = e->loadedEffects().first(); e->unloadEffect(effect); QVERIFY(!e->isEffectLoaded(effect)); } } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void SlidingPopupsTest::testWithOtherEffect_data() { QTest::addColumn("effectsToLoad"); QTest::newRow("scale, slide") << QStringList{QStringLiteral("kwin4_effect_scalein"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, scale") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("kwin4_effect_scalein")}; QTest::newRow("fade, slide") << QStringList{QStringLiteral("kwin4_effect_fade"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, fade") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("kwin4_effect_fade")}; if (effects->compositingType() & KWin::OpenGLCompositing) { QTest::newRow("glide, slide") << QStringList{QStringLiteral("glide"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, glide") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("glide")}; QTest::newRow("wobblywindows, slide") << QStringList{QStringLiteral("wobblywindows"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, wobblywindows") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("wobblywindows")}; QTest::newRow("fallapart, slide") << QStringList{QStringLiteral("fallapart"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, fallapart") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("fallapart")}; } } void SlidingPopupsTest::testWithOtherEffect() { // this test verifies that slidingpopups effect grabs the window added role // independently of the sequence how the effects are loaded. // see BUG 336866 EffectsHandlerImpl *e = static_cast(effects); // find the effectsloader auto effectloader = e->findChild(); QVERIFY(effectloader); QSignalSpy effectLoadedSpy(effectloader, &AbstractEffectLoader::effectLoaded); QVERIFY(effectLoadedSpy.isValid()); Effect *slidingPoupus = nullptr; Effect *otherEffect = nullptr; QFETCH(QStringList, effectsToLoad); for (const QString &effectName : effectsToLoad) { QVERIFY(!e->isEffectLoaded(effectName)); QVERIFY(e->loadEffect(effectName)); QVERIFY(e->isEffectLoaded(effectName)); QCOMPARE(effectLoadedSpy.count(), 1); Effect *effect = effectLoadedSpy.first().first().value(); if (effectName == QStringLiteral("slidingpopups")) { slidingPoupus = effect; } else { otherEffect = effect; } effectLoadedSpy.clear(); } QVERIFY(slidingPoupus); QVERIFY(otherEffect); QVERIFY(!slidingPoupus->isActive()); QVERIFY(!otherEffect->isActive()); // give the compositor some time to render QTest::qWait(50); QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); QVERIFY(windowAddedSpy.isValid()); // create an xcb window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 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, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo winInfo(c.data(), w, rootWindow(), NET::Properties(), NET::Properties2()); winInfo.setWindowType(NET::Normal); // and get the slide atom const QByteArray effectAtomName = QByteArrayLiteral("_KDE_SLIDE"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c.data(), false, effectAtomName.length(), effectAtomName.constData()); const int size = 2; int32_t data[size]; data[0] = 0; data[1] = 0; QScopedPointer atom(xcb_intern_atom_reply(c.data(), atomCookie, nullptr)); QVERIFY(!atom.isNull()); xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atom->atom, atom->atom, 32, size, data); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QVERIFY(client->isNormalWindow()); // sliding popups should be active QVERIFY(windowAddedSpy.wait()); QTRY_VERIFY(slidingPoupus->isActive()); QVERIFY(!otherEffect->isActive()); // wait till effect ends QTRY_VERIFY(!slidingPoupus->isActive()); QTest::qWait(300); QVERIFY(!otherEffect->isActive()); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QSignalSpy windowDeletedSpy(effects, &EffectsHandler::windowDeleted); QVERIFY(windowDeletedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); // again we should have the sliding popups active QVERIFY(slidingPoupus->isActive()); QVERIFY(!otherEffect->isActive()); QVERIFY(windowDeletedSpy.wait()); QCOMPARE(windowDeletedSpy.count(), 1); QTRY_VERIFY(!slidingPoupus->isActive()); QTest::qWait(300); QVERIFY(!otherEffect->isActive()); xcb_destroy_window(c.data(), w); c.reset(); } void SlidingPopupsTest::testWithOtherEffectWayland_data() { QTest::addColumn("effectsToLoad"); QTest::newRow("scale, slide") << QStringList{QStringLiteral("kwin4_effect_scalein"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, scale") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("kwin4_effect_scalein")}; QTest::newRow("fade, slide") << QStringList{QStringLiteral("kwin4_effect_fade"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, fade") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("kwin4_effect_fade")}; if (effects->compositingType() & KWin::OpenGLCompositing) { QTest::newRow("glide, slide") << QStringList{QStringLiteral("glide"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, glide") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("glide")}; QTest::newRow("wobblywindows, slide") << QStringList{QStringLiteral("wobblywindows"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, wobblywindows") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("wobblywindows")}; QTest::newRow("fallapart, slide") << QStringList{QStringLiteral("fallapart"), QStringLiteral("slidingpopups")}; QTest::newRow("slide, fallapart") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("fallapart")}; } } void SlidingPopupsTest::testWithOtherEffectWayland() { // this test verifies that slidingpopups effect grabs the window added role // independently of the sequence how the effects are loaded. // see BUG 336866 // the test is like testWithOtherEffect, but simulates using a Wayland window EffectsHandlerImpl *e = static_cast(effects); // find the effectsloader auto effectloader = e->findChild(); QVERIFY(effectloader); QSignalSpy effectLoadedSpy(effectloader, &AbstractEffectLoader::effectLoaded); QVERIFY(effectLoadedSpy.isValid()); Effect *slidingPoupus = nullptr; Effect *otherEffect = nullptr; QFETCH(QStringList, effectsToLoad); for (const QString &effectName : effectsToLoad) { QVERIFY(!e->isEffectLoaded(effectName)); QVERIFY(e->loadEffect(effectName)); QVERIFY(e->isEffectLoaded(effectName)); QCOMPARE(effectLoadedSpy.count(), 1); Effect *effect = effectLoadedSpy.first().first().value(); if (effectName == QStringLiteral("slidingpopups")) { slidingPoupus = effect; } else { otherEffect = effect; } effectLoadedSpy.clear(); } QVERIFY(slidingPoupus); QVERIFY(otherEffect); QVERIFY(!slidingPoupus->isActive()); QVERIFY(!otherEffect->isActive()); QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); QVERIFY(windowAddedSpy.isValid()); using namespace KWayland::Client; // the test created the slide protocol, let's create a Registry and listen for it QScopedPointer registry(new Registry); registry->create(Test::waylandConnection()); QSignalSpy interfacesAnnouncedSpy(registry.data(), &Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); registry->setup(); QVERIFY(interfacesAnnouncedSpy.wait()); auto slideInterface = registry->interface(Registry::Interface::Slide); QVERIFY(slideInterface.name != 0); QScopedPointer slideManager(registry->createSlideManager(slideInterface.name, slideInterface.version)); QVERIFY(slideManager); // create Wayland window QScopedPointer surface(Test::createSurface()); QVERIFY(surface); QScopedPointer slide(slideManager->createSlide(surface.data())); slide->setLocation(Slide::Location::Left); slide->commit(); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(shellSurface); QCOMPARE(windowAddedSpy.count(), 0); auto client = Test::renderAndWaitForShown(surface.data(), QSize(10, 20), Qt::blue); QVERIFY(client); QVERIFY(client->isNormalWindow()); // sliding popups should be active QCOMPARE(windowAddedSpy.count(), 1); QTRY_VERIFY(slidingPoupus->isActive()); QVERIFY(!otherEffect->isActive()); // wait till effect ends QTRY_VERIFY(!slidingPoupus->isActive()); QTest::qWait(300); QVERIFY(!otherEffect->isActive()); // and destroy the window again shellSurface.reset(); surface.reset(); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QSignalSpy windowDeletedSpy(effects, &EffectsHandler::windowDeleted); QVERIFY(windowDeletedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); // again we should have the sliding popups active QVERIFY(slidingPoupus->isActive()); QVERIFY(!otherEffect->isActive()); QVERIFY(windowDeletedSpy.wait()); QCOMPARE(windowDeletedSpy.count(), 1); QTRY_VERIFY(!slidingPoupus->isActive()); QTest::qWait(300); QVERIFY(!otherEffect->isActive()); } WAYLANDTEST_MAIN(SlidingPopupsTest) #include "slidingpopups_test.moc" diff --git a/autotests/integration/globalshortcuts_test.cpp b/autotests/integration/globalshortcuts_test.cpp index 441822e27..ada731649 100644 --- a/autotests/integration/globalshortcuts_test.cpp +++ b/autotests/integration/globalshortcuts_test.cpp @@ -1,199 +1,199 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "input.h" #include "platform.h" #include "screens.h" #include "shell_client.h" #include "useractions.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_globalshortcuts-0"); class GlobalShortcutsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testConsumedShift(); void testRepeatedTrigger(); void testUserActionsMenu(); void testMetaShiftW(); }; void GlobalShortcutsTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); waylandServer()->initWorkspace(); } void GlobalShortcutsTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(640, 512)); } void GlobalShortcutsTest::cleanup() { Test::destroyWaylandConnection(); } void GlobalShortcutsTest::testConsumedShift() { // this test verifies that a shortcut with a consumed shift modifier triggers // create the action QScopedPointer action(new QAction(nullptr)); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(QStringLiteral("globalshortcuts-test-consumed-shift")); QSignalSpy triggeredSpy(action.data(), &QAction::triggered); QVERIFY(triggeredSpy.isValid()); KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::Key_Percent}, KGlobalAccel::NoAutoloading); input()->registerShortcut(Qt::Key_Percent, action.data()); // press shift+5 quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_5, timestamp++); QTRY_COMPARE(triggeredSpy.count(), 1); kwinApp()->platform()->keyboardKeyReleased(KEY_5, timestamp++); // release shift kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); } void GlobalShortcutsTest::testRepeatedTrigger() { // this test verifies that holding a key, triggers repeated global shortcut // in addition pressing another key should stop triggering the shortcut QScopedPointer action(new QAction(nullptr)); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(QStringLiteral("globalshortcuts-test-consumed-shift")); QSignalSpy triggeredSpy(action.data(), &QAction::triggered); QVERIFY(triggeredSpy.isValid()); KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::Key_Percent}, KGlobalAccel::NoAutoloading); input()->registerShortcut(Qt::Key_Percent, action.data()); // we need to configure the key repeat first. It is only enabled on libinput waylandServer()->seat()->setKeyRepeatInfo(25, 300); // press shift+5 quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_WAKEUP, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_5, timestamp++); QTRY_COMPARE(triggeredSpy.count(), 1); // and should repeat QVERIFY(triggeredSpy.wait()); QVERIFY(triggeredSpy.wait()); // now release the key kwinApp()->platform()->keyboardKeyReleased(KEY_5, timestamp++); QVERIFY(!triggeredSpy.wait(500)); kwinApp()->platform()->keyboardKeyReleased(KEY_WAKEUP, timestamp++); QVERIFY(!triggeredSpy.wait(500)); // release shift kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); } void GlobalShortcutsTest::testUserActionsMenu() { // this test tries to trigger the user actions menu with Alt+F3 // the problem here is that pressing F3 consumes modifiers as it's part of the // Ctrl+alt+F3 keysym for vt switching. xkbcommon considers all modifiers as consumed // which a transformation to any keysym would cause // for more information see: // https://bugs.freedesktop.org/show_bug.cgi?id=92818 // https://github.com/xkbcommon/libxkbcommon/issues/17 // first create a window QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); quint32 timestamp = 0; QVERIFY(!workspace()->userActionsMenu()->isShown()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_F3, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_F3, timestamp++); QTRY_VERIFY(workspace()->userActionsMenu()->isShown()); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); } void GlobalShortcutsTest::testMetaShiftW() { // BUG 370341 QScopedPointer action(new QAction(nullptr)); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(QStringLiteral("globalshortcuts-test-meta-shift-w")); QSignalSpy triggeredSpy(action.data(), &QAction::triggered); QVERIFY(triggeredSpy.isValid()); KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::META + Qt::SHIFT + Qt::Key_W}, KGlobalAccel::NoAutoloading); input()->registerShortcut(Qt::META + Qt::SHIFT + Qt::Key_W, action.data()); // press meta+shift+w quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::MetaModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::MetaModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_W, timestamp++); QTRY_COMPARE(triggeredSpy.count(), 1); kwinApp()->platform()->keyboardKeyReleased(KEY_W, timestamp++); // release meta+shift kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); } WAYLANDTEST_MAIN(GlobalShortcutsTest) #include "globalshortcuts_test.moc" diff --git a/autotests/integration/input_stacking_order.cpp b/autotests/integration/input_stacking_order.cpp index c6ccb6833..7acc98e1c 100644 --- a/autotests/integration/input_stacking_order.cpp +++ b/autotests/integration/input_stacking_order.cpp @@ -1,190 +1,190 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "deleted.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_input_stacking_order-0"); class InputStackingOrderTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testPointerFocusUpdatesOnStackingOrderChange_data(); void testPointerFocusUpdatesOnStackingOrderChange(); private: void render(KWayland::Client::Surface *surface); }; void InputStackingOrderTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void InputStackingOrderTest::init() { using namespace KWayland::Client; - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandPointer()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void InputStackingOrderTest::cleanup() { Test::destroyWaylandConnection(); } void InputStackingOrderTest::render(KWayland::Client::Surface *surface) { Test::render(surface, QSize(100, 50), Qt::blue); Test::flushWaylandConnection(); } void InputStackingOrderTest::testPointerFocusUpdatesOnStackingOrderChange_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void InputStackingOrderTest::testPointerFocusUpdatesOnStackingOrderChange() { // this test creates two windows which overlap // the pointer is in the overlapping area which means the top most window has focus // as soon as the top most window gets lowered the window should lose focus and the // other window should gain focus without a mouse event in between using namespace KWayland::Client; // create pointer and signal spy for enter and leave signals auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat()); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer, &Pointer::left); QVERIFY(leftSpy.isValid()); // now create the two windows and make them overlap QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface1); QFETCH(Test::ShellSurfaceType, type); auto shellSurface1 = Test::createShellSurface(type, surface1, surface1); QVERIFY(shellSurface1); render(surface1); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface2); auto shellSurface2 = Test::createShellSurface(type, surface2, surface2); QVERIFY(shellSurface2); render(surface2); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); // now make windows overlap window2->move(window1->pos()); QCOMPARE(window1->geometry(), window2->geometry()); // enter kwinApp()->platform()->pointerMotion(QPointF(25, 25), 1); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 1); // window 2 should have focus QCOMPARE(pointer->enteredSurface(), surface2); // also on the server QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window2->surface()); // raise window 1 above window 2 QVERIFY(leftSpy.isEmpty()); workspace()->raiseClient(window1); // should send leave to window2 QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.count(), 1); // and an enter to window1 QCOMPARE(enteredSpy.count(), 2); QCOMPARE(pointer->enteredSurface(), surface1); QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window1->surface()); // let's destroy window1, that should pass focus to window2 again QSignalSpy windowClosedSpy(window1, &Toplevel::windowClosed); QVERIFY(windowClosedSpy.isValid()); surface1->deleteLater(); QVERIFY(windowClosedSpy.wait()); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 3); QCOMPARE(pointer->enteredSurface(), surface2); QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window2->surface()); } } WAYLANDTEST_MAIN(KWin::InputStackingOrderTest) #include "input_stacking_order.moc" diff --git a/autotests/integration/internal_window.cpp b/autotests/integration/internal_window.cpp index 58deadc45..6c2a670d0 100644 --- a/autotests/integration/internal_window.cpp +++ b/autotests/integration/internal_window.cpp @@ -1,500 +1,500 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "cursor.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include using namespace KWayland::Client; namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_internal_window-0"); class InternalWindowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testEnterLeave(); void testPointerPressRelease(); void testPointerAxis(); void testKeyboard_data(); void testKeyboard(); void testKeyboardShowWithoutActivating(); void testKeyboardTriggersLeave(); void testTouch(); }; class HelperWindow : public QRasterWindow { Q_OBJECT public: HelperWindow(); ~HelperWindow(); QPoint latestGlobalMousePos() const { return m_latestGlobalMousePos; } Qt::MouseButtons pressedButtons() const { return m_pressedButtons; } Q_SIGNALS: void entered(); void left(); void mouseMoved(const QPoint &global); void mousePressed(); void mouseReleased(); void wheel(); void keyPressed(); void keyReleased(); protected: void paintEvent(QPaintEvent *event) override; bool event(QEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; private: QPoint m_latestGlobalMousePos; Qt::MouseButtons m_pressedButtons = Qt::MouseButtons(); }; HelperWindow::HelperWindow() : QRasterWindow(nullptr) { setFlags(Qt::FramelessWindowHint); } HelperWindow::~HelperWindow() = default; void HelperWindow::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter p(this); p.fillRect(0, 0, width(), height(), Qt::red); } bool HelperWindow::event(QEvent *event) { if (event->type() == QEvent::Enter) { emit entered(); } if (event->type() == QEvent::Leave) { emit left(); } return QRasterWindow::event(event); } void HelperWindow::mouseMoveEvent(QMouseEvent *event) { m_latestGlobalMousePos = event->globalPos(); emit mouseMoved(event->globalPos()); } void HelperWindow::mousePressEvent(QMouseEvent *event) { m_latestGlobalMousePos = event->globalPos(); m_pressedButtons = event->buttons(); emit mousePressed(); } void HelperWindow::mouseReleaseEvent(QMouseEvent *event) { m_latestGlobalMousePos = event->globalPos(); m_pressedButtons = event->buttons(); emit mouseReleased(); } void HelperWindow::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) emit wheel(); } void HelperWindow::keyPressEvent(QKeyEvent *event) { Q_UNUSED(event) emit keyPressed(); } void HelperWindow::keyReleaseEvent(QKeyEvent *event) { Q_UNUSED(event) emit keyReleased(); } void InternalWindowTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void InternalWindowTest::init() { Cursor::setPos(QPoint(1280, 512)); - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandKeyboard()); } void InternalWindowTest::cleanup() { Test::destroyWaylandConnection(); } void InternalWindowTest::testEnterLeave() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; QVERIFY(!workspace()->findToplevel(nullptr)); QVERIFY(!workspace()->findToplevel(&win)); win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); QVERIFY(!workspace()->activeClient()); ShellClient *c = clientAddedSpy.first().first().value(); QVERIFY(c->isInternal()); QCOMPARE(workspace()->findToplevel(&win), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 100)); QVERIFY(c->isShown(false)); QVERIFY(workspace()->xStackingOrder().contains(c)); QSignalSpy enterSpy(&win, &HelperWindow::entered); QVERIFY(enterSpy.isValid()); QSignalSpy leaveSpy(&win, &HelperWindow::left); QVERIFY(leaveSpy.isValid()); QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); QVERIFY(moveSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); QTRY_COMPARE(enterSpy.count(), 1); kwinApp()->platform()->pointerMotion(QPoint(60, 50), timestamp++); QTRY_COMPARE(moveSpy.count(), 1); QCOMPARE(moveSpy.first().first().toPoint(), QPoint(60, 50)); kwinApp()->platform()->pointerMotion(QPoint(101, 50), timestamp++); QTRY_COMPARE(leaveSpy.count(), 1); // set a mask on the window win.setMask(QRegion(10, 20, 30, 40)); // outside the mask we should not get an enter kwinApp()->platform()->pointerMotion(QPoint(5, 5), timestamp++); QVERIFY(!enterSpy.wait(100)); QCOMPARE(enterSpy.count(), 1); // inside the mask we should still get an enter kwinApp()->platform()->pointerMotion(QPoint(25, 27), timestamp++); QTRY_COMPARE(enterSpy.count(), 2); // hide the window, which should be removed from the stacking order win.hide(); QTRY_VERIFY(!c->isShown(false)); QVERIFY(!workspace()->xStackingOrder().contains(c)); // show again win.show(); QTRY_VERIFY(c->isShown(false)); QVERIFY(workspace()->xStackingOrder().contains(c)); } void InternalWindowTest::testPointerPressRelease() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QTRY_COMPARE(pressSpy.count(), 1); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); } void InternalWindowTest::testPointerAxis() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy wheelSpy(&win, &HelperWindow::wheel); QVERIFY(wheelSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QTRY_COMPARE(wheelSpy.count(), 1); kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QTRY_COMPARE(wheelSpy.count(), 2); } void InternalWindowTest::testKeyboard_data() { QTest::addColumn("cursorPos"); QTest::newRow("on Window") << QPoint(50, 50); QTest::newRow("outside Window") << QPoint(250, 250); } void InternalWindowTest::testKeyboard() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QVERIFY(internalClient->readyForPainting()); quint32 timestamp = 1; QFETCH(QPoint, cursorPos); kwinApp()->platform()->pointerMotion(cursorPos, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QTRY_COMPARE(pressSpy.count(), 1); QCOMPARE(releaseSpy.count(), 0); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); QCOMPARE(pressSpy.count(), 1); } void InternalWindowTest::testKeyboardShowWithoutActivating() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setProperty("_q_showWithoutActivating", true); win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QVERIFY(internalClient->readyForPainting()); quint32 timestamp = 1; const QPoint cursorPos = QPoint(50, 50); kwinApp()->platform()->pointerMotion(cursorPos, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QCOMPARE(pressSpy.count(), 0); QVERIFY(!pressSpy.wait(100)); QCOMPARE(releaseSpy.count(), 0); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QCOMPARE(releaseSpy.count(), 0); QVERIFY(!releaseSpy.wait(100)); QCOMPARE(pressSpy.count(), 0); } void InternalWindowTest::testKeyboardTriggersLeave() { // this test verifies that a leave event is sent to a client when an internal window // gets a key event QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); QVERIFY(!keyboard.isNull()); QVERIFY(keyboard->isValid()); QSignalSpy enteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(keyboard.data(), &Keyboard::left); QVERIFY(leftSpy.isValid()); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface.data())); // now let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isInternal()); if (enteredSpy.isEmpty()) { QVERIFY(enteredSpy.wait()); } QCOMPARE(enteredSpy.count(), 1); // create internal window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QVERIFY(internalClient->readyForPainting()); QVERIFY(leftSpy.isEmpty()); QVERIFY(!leftSpy.wait(100)); // now let's trigger a key, which should result in a leave quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QVERIFY(leftSpy.wait()); QCOMPARE(pressSpy.count(), 1); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); // after hiding the internal window, next key press should trigger an enter win.hide(); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QVERIFY(enteredSpy.wait()); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); } void InternalWindowTest::testTouch() { // touch events for internal windows are emulated through mouse events QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); QVERIFY(releaseSpy.isValid()); QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); QVERIFY(moveSpy.isValid()); quint32 timestamp = 1; QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); kwinApp()->platform()->touchDown(0, QPointF(50, 50), timestamp++); QCOMPARE(pressSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // further touch down should not trigger kwinApp()->platform()->touchDown(1, QPointF(75, 75), timestamp++); QCOMPARE(pressSpy.count(), 1); kwinApp()->platform()->touchUp(1, timestamp++); QCOMPARE(releaseSpy.count(), 0); QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // another press kwinApp()->platform()->touchDown(1, QPointF(10, 10), timestamp++); QCOMPARE(pressSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // simulate the move QCOMPARE(moveSpy.count(), 0); kwinApp()->platform()->touchMotion(0, QPointF(80, 90), timestamp++); QCOMPARE(moveSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // move on other ID should not do anything kwinApp()->platform()->touchMotion(1, QPointF(20, 30), timestamp++); QCOMPARE(moveSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // now up our main point kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(releaseSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); // and up the additional point kwinApp()->platform()->touchUp(1, timestamp++); QCOMPARE(releaseSpy.count(), 1); QCOMPARE(moveSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); } } WAYLANDTEST_MAIN(KWin::InternalWindowTest) #include "internal_window.moc" diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index 2d68ae44c..716b0c2b8 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -1,187 +1,186 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_WAYLAND_TEST_H #define KWIN_WAYLAND_TEST_H #include "../../main.h" // Qt #include namespace KWayland { namespace Client { class ConnectionThread; class Compositor; class PlasmaShell; class PlasmaWindowManagement; class PointerConstraints; class Seat; class ServerSideDecorationManager; class Shell; class ShellSurface; class ShmPool; class Surface; class XdgShellSurface; } } namespace KWin { class AbstractClient; class ShellClient; class WaylandTestApplication : public Application { Q_OBJECT public: WaylandTestApplication(int &argc, char **argv); virtual ~WaylandTestApplication(); protected: void performStartup() override; private: void createBackend(); void createX11Connection(); void continueStartupWithScreens(); void continueStartupWithX(); void startXwaylandServer(); int m_xcbConnectionFd = -1; QProcess *m_xwaylandProcess = nullptr; QMetaObject::Connection m_xwaylandFailConnection; }; namespace Test { enum class AdditionalWaylandInterface { Seat = 1 << 0, Decoration = 1 << 1, PlasmaShell = 1 << 2, WindowManagement = 1 << 3, PointerConstraints = 1 << 4 }; Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) /** * Creates a Wayland Connection in a dedicated thread and creates various * client side objects which can be used to create windows. - * @param socketName The name of the Wayland socket to connect to. * @returns @c true if created successfully, @c false if there was an error * @see destroyWaylandConnection **/ -bool setupWaylandConnection(const QString &socketName, AdditionalWaylandInterfaces flags = AdditionalWaylandInterfaces()); +bool setupWaylandConnection(AdditionalWaylandInterfaces flags = AdditionalWaylandInterfaces()); /** * Destroys the Wayland Connection created with @link{setupWaylandConnection}. * This can be called from cleanup in order to ensure that no Wayland Connection * leaks into the next test method. * @see setupWaylandConnection */ void destroyWaylandConnection(); KWayland::Client::ConnectionThread *waylandConnection(); KWayland::Client::Compositor *waylandCompositor(); KWayland::Client::Shell *waylandShell(); KWayland::Client::ShmPool *waylandShmPool(); KWayland::Client::Seat *waylandSeat(); KWayland::Client::ServerSideDecorationManager *waylandServerSideDecoration(); KWayland::Client::PlasmaShell *waylandPlasmaShell(); KWayland::Client::PlasmaWindowManagement *waylandWindowManagement(); KWayland::Client::PointerConstraints *waylandPointerConstraints(); bool waitForWaylandPointer(); bool waitForWaylandTouch(); bool waitForWaylandKeyboard(); void flushWaylandConnection(); KWayland::Client::Surface *createSurface(QObject *parent = nullptr); enum class ShellSurfaceType { WlShell, XdgShellV5 }; QObject *createShellSurface(ShellSurfaceType type, KWayland::Client::Surface *surface, QObject *parent = nullptr); KWayland::Client::ShellSurface *createShellSurface(KWayland::Client::Surface *surface, QObject *parent = nullptr); KWayland::Client::XdgShellSurface *createXdgShellV5Surface(KWayland::Client::Surface *surface, QObject *parent = nullptr); /** * Creates a shared memory buffer of @p size in @p color and attaches it to the @p surface. * The @p surface gets damaged and committed, thus it's rendered. **/ void render(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format = QImage::Format_ARGB32); /** * Waits till a new ShellClient is shown and returns the created ShellClient. * If no ShellClient gets shown during @p timeout @c null is returned. **/ ShellClient *waitForWaylandWindowShown(int timeout = 5000); /** * Combination of @link{render} and @link{waitForWaylandWindowShown}. **/ ShellClient *renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format = QImage::Format_ARGB32, int timeout = 5000); /** * Waits for the @p client to be destroyed. **/ bool waitForWindowDestroyed(AbstractClient *client); /** * Locks the screen and waits till the screen is locked. * @returns @c true if the screen could be locked, @c false otherwise **/ bool lockScreen(); /** * Unlocks the screen and waits till the screen is unlocked. * @returns @c true if the screen could be unlocked, @c false otherwise **/ bool unlockScreen(); } } Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Test::AdditionalWaylandInterfaces) Q_DECLARE_METATYPE(KWin::Test::ShellSurfaceType) #define WAYLANDTEST_MAIN_HELPER(TestObject, DPI) \ int main(int argc, char *argv[]) \ { \ setenv("QT_QPA_PLATFORM", "wayland-org.kde.kwin.qpa", true); \ setenv("QT_QPA_PLATFORM_PLUGIN_PATH", KWINQPAPATH, true); \ setenv("KWIN_FORCE_OWN_QPA", "1", true); \ DPI; \ KWin::WaylandTestApplication app(argc, argv); \ app.setAttribute(Qt::AA_Use96Dpi, true); \ TestObject tc; \ return QTest::qExec(&tc, argc, argv); \ } #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) #define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject, QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling) ) #else #define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject,) #endif #endif diff --git a/autotests/integration/lockscreen.cpp b/autotests/integration/lockscreen.cpp index e35ecb49c..0f645fdb4 100644 --- a/autotests/integration/lockscreen.cpp +++ b/autotests/integration/lockscreen.cpp @@ -1,758 +1,758 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include #include #include //screenlocker #include #include Q_DECLARE_METATYPE(Qt::Orientation) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_lock_screen-0"); class LockScreenTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testPointer(); void testPointerButton(); void testPointerAxis(); void testKeyboard(); void testScreenEdge(); void testEffects(); void testEffectsKeyboard(); void testEffectsKeyboardAutorepeat(); void testMoveWindow(); void testPointerShortcut(); void testAxisShortcut_data(); void testAxisShortcut(); void testKeyboardShortcut(); void testTouch(); private: void unlock(); AbstractClient *showWindow(); KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; }; class HelperEffect : public Effect { Q_OBJECT public: HelperEffect() {} ~HelperEffect() {} void windowInputMouseEvent(QEvent*) override { emit inputEvent(); } void grabbedKeyboardEvent(QKeyEvent *e) override { emit keyEvent(e->text()); } Q_SIGNALS: void inputEvent(); void keyEvent(const QString&); }; #define LOCK \ QVERIFY(!waylandServer()->isScreenLocked()); \ QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); \ QVERIFY(lockStateChangedSpy.isValid()); \ ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); \ QCOMPARE(lockStateChangedSpy.count(), 1); \ QVERIFY(waylandServer()->isScreenLocked()); #define UNLOCK \ int expectedLockCount = 1; \ if (ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::Locked) { \ expectedLockCount = 2; \ } \ QCOMPARE(lockStateChangedSpy.count(), expectedLockCount); \ unlock(); \ if (lockStateChangedSpy.count() < expectedLockCount + 1) { \ QVERIFY(lockStateChangedSpy.wait()); \ } \ QCOMPARE(lockStateChangedSpy.count(), expectedLockCount + 1); \ QVERIFY(!waylandServer()->isScreenLocked()); #define MOTION(target) \ kwinApp()->platform()->pointerMotion(target, timestamp++) #define PRESS \ kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++) #define RELEASE \ kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++) #define KEYPRESS( key ) \ kwinApp()->platform()->keyboardKeyPressed(key, timestamp++) #define KEYRELEASE( key ) \ kwinApp()->platform()->keyboardKeyReleased(key, timestamp++) void LockScreenTest::unlock() { using namespace ScreenLocker; const auto children = KSldApp::self()->children(); for (auto it = children.begin(); it != children.end(); ++it) { if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) { continue; } QMetaObject::invokeMethod(*it, "requestUnlock"); break; } } AbstractClient *LockScreenTest::showWindow() { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; Surface *surface = Test::createSurface(m_compositor); VERIFY(surface); ShellSurface *shellSurface = Test::createShellSurface(surface, surface); VERIFY(shellSurface); // let's render auto c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); VERIFY(c); COMPARE(workspace()->activeClient(), c); #undef VERIFY #undef COMPARE return c; } void LockScreenTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); setenv("QMLSCENE_DEVICE", "softwarecontext", true); waylandServer()->initWorkspace(); } void LockScreenTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandPointer()); m_connection = Test::waylandConnection(); m_compositor = Test::waylandCompositor(); m_shell = Test::waylandShell(); m_shm = Test::waylandShmPool(); m_seat = Test::waylandSeat(); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void LockScreenTest::cleanup() { Test::destroyWaylandConnection(); } void LockScreenTest::testPointer() { using namespace KWayland::Client; QScopedPointer pointer(m_seat->createPointer()); QVERIFY(!pointer.isNull()); QSignalSpy enteredSpy(pointer.data(), &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer.data(), &Pointer::left); QVERIFY(leftSpy.isValid()); AbstractClient *c = showWindow(); QVERIFY(c); // first move cursor into the center of the window quint32 timestamp = 1; MOTION(c->geometry().center()); QVERIFY(enteredSpy.wait()); LOCK QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.count(), 1); // simulate moving out in and out again MOTION(c->geometry().center()); MOTION(c->geometry().bottomRight() + QPoint(100, 100)); MOTION(c->geometry().bottomRight() + QPoint(100, 100)); QVERIFY(!leftSpy.wait()); QCOMPARE(leftSpy.count(), 1); QCOMPARE(enteredSpy.count(), 1); // go back on the window MOTION(c->geometry().center()); // and unlock UNLOCK QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); // move on the window MOTION(c->geometry().center() + QPoint(100, 100)); QVERIFY(leftSpy.wait()); MOTION(c->geometry().center()); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 3); } void LockScreenTest::testPointerButton() { using namespace KWayland::Client; QScopedPointer pointer(m_seat->createPointer()); QVERIFY(!pointer.isNull()); QSignalSpy enteredSpy(pointer.data(), &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy buttonChangedSpy(pointer.data(), &Pointer::buttonStateChanged); QVERIFY(buttonChangedSpy.isValid()); AbstractClient *c = showWindow(); QVERIFY(c); // first move cursor into the center of the window quint32 timestamp = 1; MOTION(c->geometry().center()); QVERIFY(enteredSpy.wait()); // and simulate a click PRESS; QVERIFY(buttonChangedSpy.wait()); RELEASE; QVERIFY(buttonChangedSpy.wait()); LOCK // and simulate a click PRESS; QVERIFY(!buttonChangedSpy.wait()); RELEASE; QVERIFY(!buttonChangedSpy.wait()); UNLOCK QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); // and click again PRESS; QVERIFY(buttonChangedSpy.wait()); RELEASE; QVERIFY(buttonChangedSpy.wait()); } void LockScreenTest::testPointerAxis() { using namespace KWayland::Client; QScopedPointer pointer(m_seat->createPointer()); QVERIFY(!pointer.isNull()); QSignalSpy axisChangedSpy(pointer.data(), &Pointer::axisChanged); QVERIFY(axisChangedSpy.isValid()); QSignalSpy enteredSpy(pointer.data(), &Pointer::entered); QVERIFY(enteredSpy.isValid()); AbstractClient *c = showWindow(); QVERIFY(c); // first move cursor into the center of the window quint32 timestamp = 1; MOTION(c->geometry().center()); QVERIFY(enteredSpy.wait()); // and simulate axis kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QVERIFY(axisChangedSpy.wait()); LOCK // and simulate axis kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QVERIFY(!axisChangedSpy.wait(100)); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(!axisChangedSpy.wait(100)); // and unlock UNLOCK QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); // and move axis again kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QVERIFY(axisChangedSpy.wait()); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(axisChangedSpy.wait()); } void LockScreenTest::testKeyboard() { using namespace KWayland::Client; QScopedPointer keyboard(m_seat->createKeyboard()); QVERIFY(!keyboard.isNull()); QSignalSpy enteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(keyboard.data(), &Keyboard::left); QVERIFY(leftSpy.isValid()); QSignalSpy keyChangedSpy(keyboard.data(), &Keyboard::keyChanged); QVERIFY(keyChangedSpy.isValid()); AbstractClient *c = showWindow(); QVERIFY(c); QVERIFY(enteredSpy.wait()); QTRY_COMPARE(enteredSpy.count(), 1); quint32 timestamp = 1; KEYPRESS(KEY_A); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 1); QCOMPARE(keyChangedSpy.at(0).at(0).value(), quint32(KEY_A)); QCOMPARE(keyChangedSpy.at(0).at(1).value(), Keyboard::KeyState::Pressed); QCOMPARE(keyChangedSpy.at(0).at(2).value(), quint32(1)); KEYRELEASE(KEY_A); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 2); QCOMPARE(keyChangedSpy.at(1).at(0).value(), quint32(KEY_A)); QCOMPARE(keyChangedSpy.at(1).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(1).at(2).value(), quint32(2)); LOCK QVERIFY(leftSpy.wait()); KEYPRESS(KEY_B); KEYRELEASE(KEY_B); QCOMPARE(leftSpy.count(), 1); QCOMPARE(keyChangedSpy.count(), 2); UNLOCK QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); KEYPRESS(KEY_C); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 3); KEYRELEASE(KEY_C); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 4); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(keyChangedSpy.at(2).at(0).value(), quint32(KEY_C)); QCOMPARE(keyChangedSpy.at(3).at(0).value(), quint32(KEY_C)); QCOMPARE(keyChangedSpy.at(2).at(2).value(), quint32(5)); QCOMPARE(keyChangedSpy.at(3).at(2).value(), quint32(6)); QCOMPARE(keyChangedSpy.at(2).at(1).value(), Keyboard::KeyState::Pressed); QCOMPARE(keyChangedSpy.at(3).at(1).value(), Keyboard::KeyState::Released); } void LockScreenTest::testScreenEdge() { QSignalSpy screenEdgeSpy(ScreenEdges::self(), &ScreenEdges::approaching); QVERIFY(screenEdgeSpy.isValid()); QCOMPARE(screenEdgeSpy.count(), 0); quint32 timestamp = 1; MOTION(QPoint(5, 5)); QCOMPARE(screenEdgeSpy.count(), 1); LOCK MOTION(QPoint(4, 4)); QCOMPARE(screenEdgeSpy.count(), 1); // and unlock UNLOCK MOTION(QPoint(5, 5)); QCOMPARE(screenEdgeSpy.count(), 2); } void LockScreenTest::testEffects() { QScopedPointer effect(new HelperEffect); QSignalSpy inputSpy(effect.data(), &HelperEffect::inputEvent); QVERIFY(inputSpy.isValid()); effects->startMouseInterception(effect.data(), Qt::ArrowCursor); quint32 timestamp = 1; QCOMPARE(inputSpy.count(), 0); MOTION(QPoint(5, 5)); QCOMPARE(inputSpy.count(), 1); // simlate click PRESS; QCOMPARE(inputSpy.count(), 2); RELEASE; QCOMPARE(inputSpy.count(), 3); LOCK MOTION(QPoint(6, 6)); QCOMPARE(inputSpy.count(), 3); // simlate click PRESS; QCOMPARE(inputSpy.count(), 3); RELEASE; QCOMPARE(inputSpy.count(), 3); UNLOCK MOTION(QPoint(5, 5)); QCOMPARE(inputSpy.count(), 4); // simlate click PRESS; QCOMPARE(inputSpy.count(), 5); RELEASE; QCOMPARE(inputSpy.count(), 6); effects->stopMouseInterception(effect.data()); } void LockScreenTest::testEffectsKeyboard() { QScopedPointer effect(new HelperEffect); QSignalSpy inputSpy(effect.data(), &HelperEffect::keyEvent); QVERIFY(inputSpy.isValid()); effects->grabKeyboard(effect.data()); quint32 timestamp = 1; KEYPRESS(KEY_A); QCOMPARE(inputSpy.count(), 1); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); KEYRELEASE(KEY_A); QCOMPARE(inputSpy.count(), 2); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); LOCK KEYPRESS(KEY_B); QCOMPARE(inputSpy.count(), 2); KEYRELEASE(KEY_B); QCOMPARE(inputSpy.count(), 2); UNLOCK KEYPRESS(KEY_C); QCOMPARE(inputSpy.count(), 3); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(2).first().toString(), QStringLiteral("c")); KEYRELEASE(KEY_C); QCOMPARE(inputSpy.count(), 4); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(2).first().toString(), QStringLiteral("c")); QCOMPARE(inputSpy.at(3).first().toString(), QStringLiteral("c")); effects->ungrabKeyboard(); } void LockScreenTest::testEffectsKeyboardAutorepeat() { // this test is just like testEffectsKeyboard, but tests auto repeat key events // while the key is pressed the Effect should get auto repeated events // but the lock screen should filter them out QScopedPointer effect(new HelperEffect); QSignalSpy inputSpy(effect.data(), &HelperEffect::keyEvent); QVERIFY(inputSpy.isValid()); effects->grabKeyboard(effect.data()); // we need to configure the key repeat first. It is only enabled on libinput waylandServer()->seat()->setKeyRepeatInfo(25, 300); quint32 timestamp = 1; KEYPRESS(KEY_A); QCOMPARE(inputSpy.count(), 1); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); QVERIFY(inputSpy.wait()); QVERIFY(inputSpy.count() > 1); // and still more events QVERIFY(inputSpy.wait()); QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); // now release inputSpy.clear(); KEYRELEASE(KEY_A); QCOMPARE(inputSpy.count(), 1); // while locked key repeat should not pass any events to the Effect LOCK KEYPRESS(KEY_B); QVERIFY(!inputSpy.wait(200)); KEYRELEASE(KEY_B); QVERIFY(!inputSpy.wait(200)); UNLOCK // don't test again, that's covered by testEffectsKeyboard effects->ungrabKeyboard(); } void LockScreenTest::testMoveWindow() { using namespace KWayland::Client; AbstractClient *c = showWindow(); QVERIFY(c); QSignalSpy clientStepUserMovedResizedSpy(c, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); quint32 timestamp = 1; workspace()->slotWindowMove(); QCOMPARE(workspace()->getMovingClient(), c); QVERIFY(c->isMove()); kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); // TODO adjust once the expected fail is fixed kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); // while locking our window should continue to be in move resize LOCK QCOMPARE(workspace()->getMovingClient(), c); QVERIFY(c->isMove()); kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); UNLOCK QCOMPARE(workspace()->getMovingClient(), c); QVERIFY(c->isMove()); kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_ESC, timestamp++); QVERIFY(!c->isMove()); } void LockScreenTest::testPointerShortcut() { using namespace KWayland::Client; QScopedPointer action(new QAction(nullptr)); QSignalSpy actionSpy(action.data(), &QAction::triggered); QVERIFY(actionSpy.isValid()); input()->registerPointerShortcut(Qt::MetaModifier, Qt::LeftButton, action.data()); // try to trigger the shortcut quint32 timestamp = 1; #define PERFORM(expectedCount) \ kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); \ PRESS; \ QCoreApplication::instance()->processEvents(); \ QCOMPARE(actionSpy.count(), expectedCount); \ RELEASE; \ kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); \ QCoreApplication::instance()->processEvents(); \ QCOMPARE(actionSpy.count(), expectedCount); PERFORM(1) // now the same thing with a locked screen LOCK PERFORM(1) // and as unlocked UNLOCK PERFORM(2) #undef PERFORM } void LockScreenTest::testAxisShortcut_data() { QTest::addColumn("direction"); QTest::addColumn("sign"); QTest::newRow("up") << Qt::Vertical << 1; QTest::newRow("down") << Qt::Vertical << -1; QTest::newRow("left") << Qt::Horizontal << 1; QTest::newRow("right") << Qt::Horizontal << -1; } void LockScreenTest::testAxisShortcut() { using namespace KWayland::Client; QScopedPointer action(new QAction(nullptr)); QSignalSpy actionSpy(action.data(), &QAction::triggered); QVERIFY(actionSpy.isValid()); QFETCH(Qt::Orientation, direction); QFETCH(int, sign); PointerAxisDirection axisDirection = PointerAxisUp; if (direction == Qt::Vertical) { axisDirection = sign > 0 ? PointerAxisUp : PointerAxisDown; } else { axisDirection = sign > 0 ? PointerAxisLeft : PointerAxisRight; } input()->registerAxisShortcut(Qt::MetaModifier, axisDirection, action.data()); // try to trigger the shortcut quint32 timestamp = 1; #define PERFORM(expectedCount) \ kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); \ if (direction == Qt::Vertical) \ kwinApp()->platform()->pointerAxisVertical(sign * 5.0, timestamp++); \ else \ kwinApp()->platform()->pointerAxisHorizontal(sign * 5.0, timestamp++); \ QCoreApplication::instance()->processEvents(); \ QCOMPARE(actionSpy.count(), expectedCount); \ kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); \ QCoreApplication::instance()->processEvents(); \ QCOMPARE(actionSpy.count(), expectedCount); PERFORM(1) // now the same thing with a locked screen LOCK PERFORM(1) // and as unlocked UNLOCK PERFORM(2) #undef PERFORM } void LockScreenTest::testKeyboardShortcut() { using namespace KWayland::Client; QScopedPointer action(new QAction(nullptr)); QSignalSpy actionSpy(action.data(), &QAction::triggered); QVERIFY(actionSpy.isValid()); input()->registerShortcut(Qt::CTRL + Qt::META + Qt::ALT + Qt::Key_Space, action.data()); // try to trigger the shortcut quint32 timestamp = 1; KEYPRESS(KEY_LEFTCTRL); KEYPRESS(KEY_LEFTMETA); KEYPRESS(KEY_LEFTALT); KEYPRESS(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 1); KEYRELEASE(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 1); LOCK KEYPRESS(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 1); KEYRELEASE(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 1); UNLOCK KEYPRESS(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 2); KEYRELEASE(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 2); KEYRELEASE(KEY_LEFTCTRL); KEYRELEASE(KEY_LEFTMETA); KEYRELEASE(KEY_LEFTALT); } void LockScreenTest::testTouch() { using namespace KWayland::Client; auto touch = m_seat->createTouch(m_seat); QVERIFY(touch); QVERIFY(touch->isValid()); AbstractClient *c = showWindow(); QVERIFY(c); QSignalSpy sequenceStartedSpy(touch, &Touch::sequenceStarted); QVERIFY(sequenceStartedSpy.isValid()); QSignalSpy cancelSpy(touch, &Touch::sequenceCanceled); QVERIFY(cancelSpy.isValid()); QSignalSpy pointRemovedSpy(touch, &Touch::pointRemoved); QVERIFY(pointRemovedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(1, QPointF(25, 25), timestamp++); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); LOCK QVERIFY(cancelSpy.wait()); kwinApp()->platform()->touchUp(1, timestamp++); QVERIFY(!pointRemovedSpy.wait(100)); kwinApp()->platform()->touchDown(1, QPointF(25, 25), timestamp++); kwinApp()->platform()->touchMotion(1, QPointF(26, 26), timestamp++); kwinApp()->platform()->touchUp(1, timestamp++); UNLOCK kwinApp()->platform()->touchDown(1, QPointF(25, 25), timestamp++); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 2); kwinApp()->platform()->touchUp(1, timestamp++); QVERIFY(pointRemovedSpy.wait()); QCOMPARE(pointRemovedSpy.count(), 1); } } WAYLANDTEST_MAIN(KWin::LockScreenTest) #include "lockscreen.moc" diff --git a/autotests/integration/maximize_test.cpp b/autotests/integration/maximize_test.cpp index a13f0fc54..530a0c6a3 100644 --- a/autotests/integration/maximize_test.cpp +++ b/autotests/integration/maximize_test.cpp @@ -1,220 +1,220 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "platform.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_maximized-0"); class TestMaximized : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMaximizedPassedToDeco(); void testInitiallyMaximized(); void testBorderlessMaximizedWindow(); }; void TestMaximized::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TestMaximized::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Decoration)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(1280, 512)); } void TestMaximized::cleanup() { Test::destroyWaylandConnection(); // adjust config auto group = kwinApp()->config()->group("Windows"); group.writeEntry("BorderlessMaximizedWindows", false); group.sync(); Workspace::self()->slotReconfigure(); QCOMPARE(options->borderlessMaximizedWindows(), false); } void TestMaximized::testMaximizedPassedToDeco() { // this test verifies that when a ShellClient gets maximized the Decoration receives the signal QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QScopedPointer ssd(Test::waylandServerSideDecoration()->create(surface.data())); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangedSpy.isValid()); QVERIFY(client); QVERIFY(client->isDecorated()); auto decoration = client->decoration(); QVERIFY(decoration); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); // now maximize QVERIFY(sizeChangedSpy.isEmpty()); QSignalSpy bordersChangedSpy(decoration, &KDecoration2::Decoration::bordersChanged); QVERIFY(bordersChangedSpy.isValid()); QSignalSpy maximizedChangedSpy(decoration->client().data(), &KDecoration2::DecoratedClient::maximizedChanged); QVERIFY(maximizedChangedSpy.isValid()); workspace()->slotWindowMaximize(); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(maximizedChangedSpy.count(), 1); QCOMPARE(maximizedChangedSpy.last().first().toBool(), true); QCOMPARE(bordersChangedSpy.count(), 1); QCOMPARE(decoration->borderLeft(), 0); QCOMPARE(decoration->borderBottom(), 0); QCOMPARE(decoration->borderRight(), 0); QVERIFY(decoration->borderTop() != 0); QVERIFY(sizeChangedSpy.isEmpty()); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(sizeChangedSpy.count(), 1); QCOMPARE(sizeChangedSpy.first().first().toSize(), QSize(1280, 1024 - decoration->borderTop())); // now unmaximize again workspace()->slotWindowMaximize(); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(maximizedChangedSpy.count(), 2); QCOMPARE(maximizedChangedSpy.last().first().toBool(), false); QCOMPARE(bordersChangedSpy.count(), 2); QVERIFY(decoration->borderTop() != 0); QVERIFY(decoration->borderLeft() != 0); QVERIFY(decoration->borderRight() != 0); QVERIFY(decoration->borderBottom() != 0); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(sizeChangedSpy.count(), 2); QCOMPARE(sizeChangedSpy.last().first().toSize(), QSize(100, 50)); } void TestMaximized::testInitiallyMaximized() { // this test verifies that a window created as maximized, will be maximized QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangedSpy.isValid()); shellSurface->setMaximized(); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(shellSurface->size(), QSize(1280, 1024)); // now let's render in an incorrect size auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QCOMPARE(client->geometry(), QRect(0, 0, 100, 50)); QEXPECT_FAIL("", "Should go out of maximzied", Continue); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QVERIFY(client->shellSurface()->isMaximized()); } void TestMaximized::testBorderlessMaximizedWindow() { // test case verifies that borderless maximized window works // see BUG 370982 // adjust config auto group = kwinApp()->config()->group("Windows"); group.writeEntry("BorderlessMaximizedWindows", true); group.sync(); Workspace::self()->slotReconfigure(); QCOMPARE(options->borderlessMaximizedWindows(), true); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QScopedPointer ssd(Test::waylandServerSideDecoration()->create(surface.data())); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client->isDecorated()); const QRect origGeo = client->geometry(); QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangedSpy.isValid()); // go to maximized shellSurface->setMaximized(); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(shellSurface->size(), QSize(1280, 1024)); QSignalSpy geometryChangedSpy(client, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); Test::render(surface.data(), shellSurface->size(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(client->isDecorated(), false); // go back to normal shellSurface->setToplevel(); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(shellSurface->size(), QSize(100, 50)); Test::render(surface.data(), QSize(100, 50), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->geometry(), origGeo); QCOMPARE(client->isDecorated(), true); } WAYLANDTEST_MAIN(TestMaximized) #include "maximize_test.moc" diff --git a/autotests/integration/move_resize_window_test.cpp b/autotests/integration/move_resize_window_test.cpp index 9720a7113..54023c7ab 100644 --- a/autotests/integration/move_resize_window_test.cpp +++ b/autotests/integration/move_resize_window_test.cpp @@ -1,772 +1,772 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "atoms.h" #include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "effects.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::AbstractClient::QuickTileMode) Q_DECLARE_METATYPE(KWin::MaximizeMode) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0"); class MoveResizeWindowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMove(); void testPackTo_data(); void testPackTo(); void testPackAgainstClient_data(); void testPackAgainstClient(); void testGrowShrink_data(); void testGrowShrink(); void testPointerMoveEnd_data(); void testPointerMoveEnd(); void testClientSideMove_data(); void testClientSideMove(); void testPlasmaShellSurfaceMovable_data(); void testPlasmaShellSurfaceMovable(); void testNetMove(); void testAdjustClientGeometryOfAutohidingX11Panel_data(); void testAdjustClientGeometryOfAutohidingX11Panel(); void testAdjustClientGeometryOfAutohidingWaylandPanel_data(); void testAdjustClientGeometryOfAutohidingWaylandPanel(); private: KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Shell *m_shell = nullptr; }; void MoveResizeWindowTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("MaximizeMode"); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 1); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); } void MoveResizeWindowTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::PlasmaShell | Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell | Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandPointer()); m_connection = Test::waylandConnection(); m_compositor = Test::waylandCompositor(); m_shell = Test::waylandShell(); screens()->setCurrent(0); } void MoveResizeWindowTest::cleanup() { Test::destroyWaylandConnection(); } void MoveResizeWindowTest::testMove() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); QSignalSpy moveResizedChangedSpy(c, &AbstractClient::moveResizedChanged); QVERIFY(moveResizedChangedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(c, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); // effects signal handlers QSignalSpy windowStartUserMovedResizedSpy(effects, &EffectsHandler::windowStartUserMovedResized); QVERIFY(windowStartUserMovedResizedSpy.isValid()); QSignalSpy windowStepUserMovedResizedSpy(effects, &EffectsHandler::windowStepUserMovedResized); QVERIFY(windowStepUserMovedResizedSpy.isValid()); QSignalSpy windowFinishUserMovedResizedSpy(effects, &EffectsHandler::windowFinishUserMovedResized); QVERIFY(windowFinishUserMovedResizedSpy.isValid()); // begin move QVERIFY(workspace()->getMovingClient() == nullptr); QCOMPARE(c->isMove(), false); workspace()->slotWindowMove(); QCOMPARE(workspace()->getMovingClient(), c); QCOMPARE(startMoveResizedSpy.count(), 1); QCOMPARE(moveResizedChangedSpy.count(), 1); QCOMPARE(windowStartUserMovedResizedSpy.count(), 1); QCOMPARE(c->isMove(), true); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // send some key events, not going through input redirection const QPoint cursorPos = Cursor::pos(); c->keyPressEvent(Qt::Key_Right); c->updateMoveResize(Cursor::pos()); QCOMPARE(Cursor::pos(), cursorPos + QPoint(8, 0)); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); c->keyPressEvent(Qt::Key_Right); c->updateMoveResize(Cursor::pos()); QCOMPARE(Cursor::pos(), cursorPos + QPoint(16, 0)); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(windowStepUserMovedResizedSpy.count(), 2); c->keyPressEvent(Qt::Key_Down | Qt::ALT); c->updateMoveResize(Cursor::pos()); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(clientStepUserMovedResizedSpy.count(), 3); QCOMPARE(c->geometry(), QRect(16, 32, 100, 50)); QCOMPARE(Cursor::pos(), cursorPos + QPoint(16, 32)); // let's end QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); c->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(moveResizedChangedSpy.count(), 2); QCOMPARE(windowFinishUserMovedResizedSpy.count(), 1); QCOMPARE(c->geometry(), QRect(16, 32, 100, 50)); QCOMPARE(c->isMove(), false); QVERIFY(workspace()->getMovingClient() == nullptr); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(c)); } void MoveResizeWindowTest::testPackTo_data() { QTest::addColumn("methodCall"); QTest::addColumn("expectedGeometry"); QTest::newRow("left") << QStringLiteral("slotWindowPackLeft") << QRect(0, 487, 100, 50); QTest::newRow("up") << QStringLiteral("slotWindowPackUp") << QRect(590, 0, 100, 50); QTest::newRow("right") << QStringLiteral("slotWindowPackRight") << QRect(1180, 487, 100, 50); QTest::newRow("down") << QStringLiteral("slotWindowPackDown") << QRect(590, 974, 100, 50); } void MoveResizeWindowTest::testPackTo() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // let's place it centered Placement::self()->placeCentered(c, QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometry(), QRect(590, 487, 100, 50)); QFETCH(QString, methodCall); QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); QTEST(c->geometry(), "expectedGeometry"); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(c)); } void MoveResizeWindowTest::testPackAgainstClient_data() { QTest::addColumn("methodCall"); QTest::addColumn("expectedGeometry"); QTest::newRow("left") << QStringLiteral("slotWindowPackLeft") << QRect(10, 487, 100, 50); QTest::newRow("up") << QStringLiteral("slotWindowPackUp") << QRect(590, 10, 100, 50); QTest::newRow("right") << QStringLiteral("slotWindowPackRight") << QRect(1170, 487, 100, 50); QTest::newRow("down") << QStringLiteral("slotWindowPackDown") << QRect(590, 964, 100, 50); } void MoveResizeWindowTest::testPackAgainstClient() { using namespace KWayland::Client; QScopedPointer surface1(Test::createSurface()); QVERIFY(!surface1.isNull()); QScopedPointer surface2(Test::createSurface()); QVERIFY(!surface2.isNull()); QScopedPointer surface3(Test::createSurface()); QVERIFY(!surface3.isNull()); QScopedPointer surface4(Test::createSurface()); QVERIFY(!surface4.isNull()); QScopedPointer shellSurface1(Test::createShellSurface(surface1.data())); QVERIFY(!shellSurface1.isNull()); QScopedPointer shellSurface2(Test::createShellSurface(surface2.data())); QVERIFY(!shellSurface2.isNull()); QScopedPointer shellSurface3(Test::createShellSurface(surface3.data())); QVERIFY(!shellSurface3.isNull()); QScopedPointer shellSurface4(Test::createShellSurface(surface4.data())); QVERIFY(!shellSurface4.isNull()); auto renderWindow = [this] (Surface *surface, const QString &methodCall, const QRect &expectedGeometry) { // let's render auto c = Test::renderAndWaitForShown(surface, QSize(10, 10), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry().size(), QSize(10, 10)); // let's place it centered Placement::self()->placeCentered(c, QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometry(), QRect(635, 507, 10, 10)); QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); QCOMPARE(c->geometry(), expectedGeometry); }; renderWindow(surface1.data(), QStringLiteral("slotWindowPackLeft"), QRect(0, 507, 10, 10)); renderWindow(surface2.data(), QStringLiteral("slotWindowPackUp"), QRect(635, 0, 10, 10)); renderWindow(surface3.data(), QStringLiteral("slotWindowPackRight"), QRect(1270, 507, 10, 10)); renderWindow(surface4.data(), QStringLiteral("slotWindowPackDown"), QRect(635, 1014, 10, 10)); QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // let's place it centered Placement::self()->placeCentered(c, QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometry(), QRect(590, 487, 100, 50)); QFETCH(QString, methodCall); QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); QTEST(c->geometry(), "expectedGeometry"); } void MoveResizeWindowTest::testGrowShrink_data() { QTest::addColumn("methodCall"); QTest::addColumn("expectedGeometry"); QTest::newRow("grow vertical") << QStringLiteral("slotWindowGrowVertical") << QRect(590, 487, 100, 537); QTest::newRow("grow horizontal") << QStringLiteral("slotWindowGrowHorizontal") << QRect(590, 487, 690, 50); QTest::newRow("shrink vertical") << QStringLiteral("slotWindowShrinkVertical") << QRect(590, 487, 100, 23); QTest::newRow("shrink horizontal") << QStringLiteral("slotWindowShrinkHorizontal") << QRect(590, 487, 40, 50); } void MoveResizeWindowTest::testGrowShrink() { using namespace KWayland::Client; // block geometry helper QScopedPointer surface1(Test::createSurface()); QVERIFY(!surface1.isNull()); QScopedPointer shellSurface1(Test::createShellSurface(surface1.data())); QVERIFY(!shellSurface1.isNull()); Test::render(surface1.data(), QSize(650, 514), Qt::blue); QVERIFY(Test::waitForWaylandWindowShown()); workspace()->slotWindowPackRight(); workspace()->slotWindowPackDown(); QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // let's place it centered Placement::self()->placeCentered(c, QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometry(), QRect(590, 487, 100, 50)); QFETCH(QString, methodCall); QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); QVERIFY(sizeChangeSpy.wait()); Test::render(surface.data(), shellSurface->size(), Qt::red); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QTEST(c->geometry(), "expectedGeometry"); } void MoveResizeWindowTest::testPointerMoveEnd_data() { QTest::addColumn("additionalButton"); QTest::newRow("BTN_RIGHT") << BTN_RIGHT; QTest::newRow("BTN_MIDDLE") << BTN_MIDDLE; QTest::newRow("BTN_SIDE") << BTN_SIDE; QTest::newRow("BTN_EXTRA") << BTN_EXTRA; QTest::newRow("BTN_FORWARD") << BTN_FORWARD; QTest::newRow("BTN_BACK") << BTN_BACK; QTest::newRow("BTN_TASK") << BTN_TASK; for (int i=BTN_TASK + 1; i < BTN_JOYSTICK; i++) { QTest::newRow(QByteArray::number(i, 16).constData()) << i; } } void MoveResizeWindowTest::testPointerMoveEnd() { // this test verifies that moving a window through pointer only ends if all buttons are released using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c, workspace()->activeClient()); QVERIFY(!c->isMove()); // let's trigger the left button quint32 timestamp = 1; kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QVERIFY(!c->isMove()); workspace()->slotWindowMove(); QVERIFY(c->isMove()); // let's press another button QFETCH(int, additionalButton); kwinApp()->platform()->pointerButtonPressed(additionalButton, timestamp++); QVERIFY(c->isMove()); // release the left button, should still have the window moving kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QVERIFY(c->isMove()); // but releasing the other button should now end moving kwinApp()->platform()->pointerButtonReleased(additionalButton, timestamp++); QVERIFY(!c->isMove()); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(c)); } void MoveResizeWindowTest::testClientSideMove_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void MoveResizeWindowTest::testClientSideMove() { using namespace KWayland::Client; Cursor::setPos(640, 512); QScopedPointer pointer(Test::waylandSeat()->createPointer()); QSignalSpy pointerEnteredSpy(pointer.data(), &Pointer::entered); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy pointerLeftSpy(pointer.data(), &Pointer::left); QVERIFY(pointerLeftSpy.isValid()); QSignalSpy buttonSpy(pointer.data(), &Pointer::buttonStateChanged); QVERIFY(buttonSpy.isValid()); QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); // move pointer into center of geometry const QRect startGeometry = c->geometry(); Cursor::setPos(startGeometry.center()); QVERIFY(pointerEnteredSpy.wait()); QCOMPARE(pointerEnteredSpy.first().last().toPoint(), QPoint(49, 24)); // simulate press quint32 timestamp = 1; kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QVERIFY(buttonSpy.wait()); QSignalSpy moveStartSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(moveStartSpy.isValid()); if (auto s = qobject_cast(shellSurface.data())) { s->requestMove(Test::waylandSeat(), buttonSpy.first().first().value()); } else if (auto s = qobject_cast(shellSurface.data())) { s->requestMove(Test::waylandSeat(), buttonSpy.first().first().value()); } QVERIFY(moveStartSpy.wait()); QCOMPARE(c->isMove(), true); QVERIFY(pointerLeftSpy.wait()); // move a bit QSignalSpy clientMoveStepSpy(c, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientMoveStepSpy.isValid()); const QPoint startPoint = startGeometry.center(); const int dragDistance = QApplication::startDragDistance(); // Why? kwinApp()->platform()->pointerMotion(startPoint + QPoint(dragDistance, dragDistance) + QPoint(6, 6), timestamp++); QCOMPARE(clientMoveStepSpy.count(), 1); // and release again kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QVERIFY(pointerEnteredSpy.wait()); QCOMPARE(c->isMove(), false); QCOMPARE(c->geometry(), startGeometry.translated(QPoint(dragDistance, dragDistance) + QPoint(6, 6))); QCOMPARE(pointerEnteredSpy.last().last().toPoint(), QPoint(49, 24)); } void MoveResizeWindowTest::testPlasmaShellSurfaceMovable_data() { QTest::addColumn("role"); QTest::addColumn("movable"); QTest::addColumn("movableAcrossScreens"); QTest::addColumn("resizable"); QTest::newRow("normal") << KWayland::Client::PlasmaShellSurface::Role::Normal << true << true << true; QTest::newRow("desktop") << KWayland::Client::PlasmaShellSurface::Role::Desktop << false << false << false; QTest::newRow("panel") << KWayland::Client::PlasmaShellSurface::Role::Panel << false << false << false; QTest::newRow("osd") << KWayland::Client::PlasmaShellSurface::Role::OnScreenDisplay << false << false << false; } void MoveResizeWindowTest::testPlasmaShellSurfaceMovable() { // this test verifies that certain window types from PlasmaShellSurface are not moveable or resizable using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); // and a PlasmaShellSurface QScopedPointer plasmaSurface(Test::waylandPlasmaShell()->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); QFETCH(KWayland::Client::PlasmaShellSurface::Role, role); plasmaSurface->setRole(role); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QTEST(c->isMovable(), "movable"); QTEST(c->isMovableAcrossScreens(), "movableAcrossScreens"); QTEST(c->isResizable(), "resizable"); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(c)); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void MoveResizeWindowTest::testNetMove() { // this test verifies that a move request for an X11 window through NET API works // create an xcb window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, 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.data(), w, &hints); // let's set a no-border NETWinInfo winInfo(c.data(), w, rootWindow(), NET::WMWindowType, NET::Properties2()); winInfo.setWindowType(NET::Override); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); const QRect origGeo = client->geometry(); // let's move the cursor outside the window Cursor::setPos(screens()->geometry(0).center()); QVERIFY(!origGeo.contains(Cursor::pos())); QSignalSpy moveStartSpy(client, &Client::clientStartUserMovedResized); QVERIFY(moveStartSpy.isValid()); QSignalSpy moveEndSpy(client, &Client::clientFinishUserMovedResized); QVERIFY(moveEndSpy.isValid()); QSignalSpy moveStepSpy(client, &Client::clientStepUserMovedResized); QVERIFY(moveStepSpy.isValid()); QVERIFY(!workspace()->getMovingClient()); // use NETRootInfo to trigger a move request NETRootInfo root(c.data(), NET::Properties()); root.moveResizeRequest(w, origGeo.center().x(), origGeo.center().y(), NET::Move); xcb_flush(c.data()); QVERIFY(moveStartSpy.wait()); QCOMPARE(workspace()->getMovingClient(), client); QVERIFY(client->isMove()); QCOMPARE(client->geometryRestore(), origGeo); QCOMPARE(Cursor::pos(), origGeo.center()); // let's move a step Cursor::setPos(Cursor::pos() + QPoint(10, 10)); QCOMPARE(moveStepSpy.count(), 1); QCOMPARE(moveStepSpy.first().last().toRect(), origGeo.translated(10, 10)); // let's cancel the move resize again through the net API root.moveResizeRequest(w, client->geometry().center().x(), client->geometry().center().y(), NET::MoveResizeCancel); xcb_flush(c.data()); QVERIFY(moveEndSpy.wait()); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); c.reset(); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); } void MoveResizeWindowTest::testAdjustClientGeometryOfAutohidingX11Panel_data() { QTest::addColumn("panelGeometry"); QTest::addColumn("targetPoint"); QTest::addColumn("expectedAdjustedPoint"); QTest::addColumn("hideLocation"); QTest::newRow("top") << QRect(0, 0, 100, 20) << QPoint(50, 25) << QPoint(50, 20) << 0u; QTest::newRow("bottom") << QRect(0, 1024-20, 100, 20) << QPoint(50, 1024 - 25 - 50) << QPoint(50, 1024 - 20 - 50) << 2u; QTest::newRow("left") << QRect(0, 0, 20, 100) << QPoint(25, 50) << QPoint(20, 50) << 3u; QTest::newRow("right") << QRect(1280 - 20, 0, 20, 100) << QPoint(1280 - 25 - 100, 50) << QPoint(1280 - 20 - 100, 50) << 1u; } void MoveResizeWindowTest::testAdjustClientGeometryOfAutohidingX11Panel() { // this test verifies that auto hiding panels are ignored when adjusting client geometry // see BUG 365892 // first create our panel QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); xcb_window_t w = xcb_generate_id(c.data()); QFETCH(QRect, panelGeometry); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), panelGeometry.x(), panelGeometry.y(), panelGeometry.width(), panelGeometry.height(), 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, panelGeometry.x(), panelGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, panelGeometry.width(), panelGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo winInfo(c.data(), w, rootWindow(), NET::WMWindowType, NET::Properties2()); winInfo.setWindowType(NET::Dock); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *panel = windowCreatedSpy.first().first().value(); QVERIFY(panel); QCOMPARE(panel->window(), w); QCOMPARE(panel->geometry(), panelGeometry); QVERIFY(panel->isDock()); // let's create a window using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); auto testWindow = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(testWindow); QVERIFY(testWindow->isMovable()); // panel is not yet hidden, we should snap against it QFETCH(QPoint, targetPoint); QTEST(Workspace::self()->adjustClientPosition(testWindow, targetPoint, false), "expectedAdjustedPoint"); // now let's hide the panel QSignalSpy panelHiddenSpy(panel, &AbstractClient::windowHidden); QVERIFY(panelHiddenSpy.isValid()); QFETCH(quint32, hideLocation); xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 32, 1, &hideLocation); xcb_flush(c.data()); QVERIFY(panelHiddenSpy.wait()); // now try to snap again QCOMPARE(Workspace::self()->adjustClientPosition(testWindow, targetPoint, false), targetPoint); // and destroy the panel again xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); c.reset(); QSignalSpy panelClosedSpy(panel, &Client::windowClosed); QVERIFY(panelClosedSpy.isValid()); QVERIFY(panelClosedSpy.wait()); // snap once more QCOMPARE(Workspace::self()->adjustClientPosition(testWindow, targetPoint, false), targetPoint); // and close QSignalSpy windowClosedSpy(testWindow, &ShellClient::windowClosed); QVERIFY(windowClosedSpy.isValid()); shellSurface.reset(); surface.reset(); QVERIFY(windowClosedSpy.wait()); } void MoveResizeWindowTest::testAdjustClientGeometryOfAutohidingWaylandPanel_data() { QTest::addColumn("panelGeometry"); QTest::addColumn("targetPoint"); QTest::addColumn("expectedAdjustedPoint"); QTest::newRow("top") << QRect(0, 0, 100, 20) << QPoint(50, 25) << QPoint(50, 20); QTest::newRow("bottom") << QRect(0, 1024-20, 100, 20) << QPoint(50, 1024 - 25 - 50) << QPoint(50, 1024 - 20 - 50); QTest::newRow("left") << QRect(0, 0, 20, 100) << QPoint(25, 50) << QPoint(20, 50); QTest::newRow("right") << QRect(1280 - 20, 0, 20, 100) << QPoint(1280 - 25 - 100, 50) << QPoint(1280 - 20 - 100, 50); } void MoveResizeWindowTest::testAdjustClientGeometryOfAutohidingWaylandPanel() { // this test verifies that auto hiding panels are ignored when adjusting client geometry // see BUG 365892 // first create our panel using namespace KWayland::Client; QScopedPointer panelSurface(Test::createSurface()); QVERIFY(!panelSurface.isNull()); QScopedPointer panelShellSurface(Test::createShellSurface(panelSurface.data())); QVERIFY(!panelShellSurface.isNull()); QScopedPointer plasmaSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.data())); QVERIFY(!plasmaSurface.isNull()); plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); plasmaSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide); QFETCH(QRect, panelGeometry); plasmaSurface->setPosition(panelGeometry.topLeft()); // let's render auto panel = Test::renderAndWaitForShown(panelSurface.data(), panelGeometry.size(), Qt::blue); QVERIFY(panel); QCOMPARE(panel->geometry(), panelGeometry); QVERIFY(panel->isDock()); // let's create a window QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); auto testWindow = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(testWindow); QVERIFY(testWindow->isMovable()); // panel is not yet hidden, we should snap against it QFETCH(QPoint, targetPoint); QTEST(Workspace::self()->adjustClientPosition(testWindow, targetPoint, false), "expectedAdjustedPoint"); // now let's hide the panel QSignalSpy panelHiddenSpy(panel, &AbstractClient::windowHidden); QVERIFY(panelHiddenSpy.isValid()); plasmaSurface->requestHideAutoHidingPanel(); QVERIFY(panelHiddenSpy.wait()); // now try to snap again QCOMPARE(Workspace::self()->adjustClientPosition(testWindow, targetPoint, false), targetPoint); // and destroy the panel again QSignalSpy panelClosedSpy(panel, &ShellClient::windowClosed); QVERIFY(panelClosedSpy.isValid()); plasmaSurface.reset(); panelShellSurface.reset(); panelSurface.reset(); QVERIFY(panelClosedSpy.wait()); // snap once more QCOMPARE(Workspace::self()->adjustClientPosition(testWindow, targetPoint, false), targetPoint); // and close QSignalSpy windowClosedSpy(testWindow, &ShellClient::windowClosed); QVERIFY(windowClosedSpy.isValid()); shellSurface.reset(); surface.reset(); QVERIFY(windowClosedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::MoveResizeWindowTest) #include "move_resize_window_test.moc" diff --git a/autotests/integration/plasma_surface_test.cpp b/autotests/integration/plasma_surface_test.cpp index 05436c88c..7f58936b0 100644 --- a/autotests/integration/plasma_surface_test.cpp +++ b/autotests/integration/plasma_surface_test.cpp @@ -1,405 +1,405 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "cursor.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include using namespace KWin; using namespace KWayland::Client; Q_DECLARE_METATYPE(KWin::Layer) static const QString s_socketName = QStringLiteral("wayland_test_kwin_plasma_surface-0"); class PlasmaSurfaceTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testRoleOnAllDesktops_data(); void testRoleOnAllDesktops(); void testAcceptsFocus_data(); void testAcceptsFocus(); void testDesktopIsOpaque(); void testPanelWindowsCanCover_data(); void testPanelWindowsCanCover(); void testOSDPlacement(); void testPanelTypeHasStrut_data(); void testPanelTypeHasStrut(); void testPanelActivate_data(); void testPanelActivate(); private: ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; Shell *m_shell = nullptr; PlasmaShell *m_plasmaShell = nullptr; }; void PlasmaSurfaceTest::initTestCase() { qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); } void PlasmaSurfaceTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::PlasmaShell)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell)); m_compositor = Test::waylandCompositor(); m_shell = Test::waylandShell(); m_plasmaShell = Test::waylandPlasmaShell(); KWin::Cursor::setPos(640, 512); } void PlasmaSurfaceTest::cleanup() { Test::destroyWaylandConnection(); } void PlasmaSurfaceTest::testRoleOnAllDesktops_data() { QTest::addColumn("role"); QTest::addColumn("expectedOnAllDesktops"); QTest::newRow("Desktop") << PlasmaShellSurface::Role::Desktop << true; QTest::newRow("Panel") << PlasmaShellSurface::Role::Panel << true; QTest::newRow("OSD") << PlasmaShellSurface::Role::OnScreenDisplay << true; QTest::newRow("Normal") << PlasmaShellSurface::Role::Normal << false; QTest::newRow("Notification") << PlasmaShellSurface::Role::Notification << true; QTest::newRow("ToolTip") << PlasmaShellSurface::Role::ToolTip << true; } void PlasmaSurfaceTest::testRoleOnAllDesktops() { // this test verifies that a ShellClient is set on all desktops when the role changes QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); // now render to map the window AbstractClient *c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // currently the role is not yet set, so the window should not be on all desktops QCOMPARE(c->isOnAllDesktops(), false); // now let's try to change that QSignalSpy onAllDesktopsSpy(c, &AbstractClient::desktopChanged); QVERIFY(onAllDesktopsSpy.isValid()); QFETCH(PlasmaShellSurface::Role, role); plasmaSurface->setRole(role); QFETCH(bool, expectedOnAllDesktops); QCOMPARE(onAllDesktopsSpy.wait(), expectedOnAllDesktops); QCOMPARE(c->isOnAllDesktops(), expectedOnAllDesktops); // let's create a second window where we init a little bit different // first creating the PlasmaSurface then the Shell Surface QScopedPointer surface2(Test::createSurface()); QVERIFY(!surface2.isNull()); QScopedPointer plasmaSurface2(m_plasmaShell->createSurface(surface2.data())); QVERIFY(!plasmaSurface2.isNull()); plasmaSurface2->setRole(role); QScopedPointer shellSurface2(Test::createShellSurface(surface2.data())); QVERIFY(!shellSurface2.isNull()); Test::render(surface2.data(), QSize(100, 50), Qt::blue); QVERIFY(Test::waitForWaylandWindowShown()); QVERIFY(workspace()->activeClient() != c); c = workspace()->activeClient(); QEXPECT_FAIL("Desktop", "PS before WS not supported", Continue); QEXPECT_FAIL("Panel", "PS before WS not supported", Continue); QEXPECT_FAIL("OSD", "PS before WS not supported", Continue); QEXPECT_FAIL("Notification", "PS before WS not supported", Continue); QEXPECT_FAIL("ToolTip", "PS before WS not supported", Continue); QCOMPARE(c->isOnAllDesktops(), expectedOnAllDesktops); } void PlasmaSurfaceTest::testAcceptsFocus_data() { QTest::addColumn("role"); QTest::addColumn("wantsInput"); QTest::addColumn("active"); QTest::newRow("Desktop") << PlasmaShellSurface::Role::Desktop << true << true; QTest::newRow("Panel") << PlasmaShellSurface::Role::Panel << true << false; QTest::newRow("OSD") << PlasmaShellSurface::Role::OnScreenDisplay << false << false; QTest::newRow("Normal") << PlasmaShellSurface::Role::Normal << true << true; QTest::newRow("Notification") << PlasmaShellSurface::Role::Notification << false << false; QTest::newRow("ToolTip") << PlasmaShellSurface::Role::ToolTip << false << false; } void PlasmaSurfaceTest::testAcceptsFocus() { // this test verifies that some surface roles don't get focus QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); QFETCH(PlasmaShellSurface::Role, role); plasmaSurface->setRole(role); // now render to map the window auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QTEST(c->wantsInput(), "wantsInput"); QTEST(c->isActive(), "active"); } void PlasmaSurfaceTest::testDesktopIsOpaque() { QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); plasmaSurface->setRole(PlasmaShellSurface::Role::Desktop); // now render to map the window auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->windowType(), NET::Desktop); QVERIFY(c->isDesktop()); QVERIFY(!c->hasAlpha()); QCOMPARE(c->depth(), 24); } void PlasmaSurfaceTest::testOSDPlacement() { QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); plasmaSurface->setRole(PlasmaShellSurface::Role::OnScreenDisplay); // now render and map the window auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->windowType(), NET::OnScreenDisplay); QVERIFY(c->isOnScreenDisplay()); QCOMPARE(c->geometry(), QRect(590, 649, 100, 50)); // change the screen size QSignalSpy screensChangedSpy(screens(), &Screens::changed); QVERIFY(screensChangedSpy.isValid()); const QVector geometries{QRect(0, 0, 1280, 1024), QRect(1280, 0, 1280, 1024)}; QMetaObject::invokeMethod(kwinApp()->platform(), "outputGeometriesChanged", Qt::DirectConnection, Q_ARG(QVector, geometries)); QVERIFY(screensChangedSpy.wait()); QCOMPARE(screensChangedSpy.count(), 1); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), geometries.at(0)); QCOMPARE(screens()->geometry(1), geometries.at(1)); QCOMPARE(c->geometry(), QRect(590, 649, 100, 50)); } void PlasmaSurfaceTest::testPanelTypeHasStrut_data() { QTest::addColumn("type"); QTest::addColumn("panelBehavior"); QTest::addColumn("expectedStrut"); QTest::addColumn("expectedMaxArea"); QTest::addColumn("expectedLayer"); QTest::newRow("always visible - wlShell") << Test::ShellSurfaceType::WlShell << PlasmaShellSurface::PanelBehavior::AlwaysVisible << true << QRect(0, 50, 1280, 974) << KWin::DockLayer; QTest::newRow("always visible - xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << PlasmaShellSurface::PanelBehavior::AlwaysVisible << true << QRect(0, 50, 1280, 974) << KWin::DockLayer; QTest::newRow("autohide - wlShell") << Test::ShellSurfaceType::WlShell << PlasmaShellSurface::PanelBehavior::AutoHide << false << QRect(0, 0, 1280, 1024) << KWin::AboveLayer; QTest::newRow("autohide - xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << PlasmaShellSurface::PanelBehavior::AutoHide << false << QRect(0, 0, 1280, 1024) << KWin::AboveLayer; QTest::newRow("windows can cover - wlShell") << Test::ShellSurfaceType::WlShell << PlasmaShellSurface::PanelBehavior::WindowsCanCover << false << QRect(0, 0, 1280, 1024) << KWin::NormalLayer; QTest::newRow("windows can cover - xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << PlasmaShellSurface::PanelBehavior::WindowsCanCover << false << QRect(0, 0, 1280, 1024) << KWin::NormalLayer; QTest::newRow("windows go below - wlShell") << Test::ShellSurfaceType::WlShell << PlasmaShellSurface::PanelBehavior::WindowsGoBelow << false << QRect(0, 0, 1280, 1024) << KWin::DockLayer; QTest::newRow("windows go below - xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << PlasmaShellSurface::PanelBehavior::WindowsGoBelow << false << QRect(0, 0, 1280, 1024) << KWin::DockLayer; } void PlasmaSurfaceTest::testPanelTypeHasStrut() { QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QVERIFY(!shellSurface.isNull()); QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); plasmaSurface->setPosition(QPoint(0, 0)); QFETCH(PlasmaShellSurface::PanelBehavior, panelBehavior); plasmaSurface->setPanelBehavior(panelBehavior); // now render and map the window auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->windowType(), NET::Dock); QVERIFY(c->isDock()); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QTEST(c->hasStrut(), "expectedStrut"); QTEST(workspace()->clientArea(MaximizeArea, 0, 0), "expectedMaxArea"); QTEST(c->layer(), "expectedLayer"); } void PlasmaSurfaceTest::testPanelWindowsCanCover_data() { QTest::addColumn("panelGeometry"); QTest::addColumn("windowGeometry"); QTest::addColumn("triggerPoint"); QTest::newRow("top-full-edge") << QRect(0, 0, 1280, 30) << QRect(0, 0, 200, 300) << QPoint(100, 0); QTest::newRow("top-left-edge") << QRect(0, 0, 1000, 30) << QRect(0, 0, 200, 300) << QPoint(100, 0); QTest::newRow("top-right-edge") << QRect(280, 0, 1000, 30) << QRect(1000, 0, 200, 300) << QPoint(1000, 0); QTest::newRow("bottom-full-edge") << QRect(0, 994, 1280, 30) << QRect(0, 724, 200, 300) << QPoint(100, 1023); QTest::newRow("bottom-left-edge") << QRect(0, 994, 1000, 30) << QRect(0, 724, 200, 300) << QPoint(100, 1023); QTest::newRow("bottom-right-edge") << QRect(280, 994, 1000, 30) << QRect(1000, 724, 200, 300) << QPoint(1000, 1023); QTest::newRow("left-full-edge") << QRect(0, 0, 30, 1024) << QRect(0, 0, 200, 300) << QPoint(0, 100); QTest::newRow("left-top-edge") << QRect(0, 0, 30, 800) << QRect(0, 0, 200, 300) << QPoint(0, 100); QTest::newRow("left-bottom-edge") << QRect(0, 200, 30, 824) << QRect(0, 0, 200, 300) << QPoint(0, 250); QTest::newRow("right-full-edge") << QRect(1250, 0, 30, 1024) << QRect(1080, 0, 200, 300) << QPoint(1279, 100); QTest::newRow("right-top-edge") << QRect(1250, 0, 30, 800) << QRect(1080, 0, 200, 300) << QPoint(1279, 100); QTest::newRow("right-bottom-edge") << QRect(1250, 200, 30, 824) << QRect(1080, 0, 200, 300) << QPoint(1279, 250); } void PlasmaSurfaceTest::testPanelWindowsCanCover() { // this test verifies the behavior of a panel with windows can cover // triggering the screen edge should raise the panel. QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface.data())); QVERIFY(!shellSurface.isNull()); QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); QFETCH(QRect, panelGeometry); plasmaSurface->setPosition(panelGeometry.topLeft()); plasmaSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::WindowsCanCover); // now render and map the window auto panel = Test::renderAndWaitForShown(surface.data(), panelGeometry.size(), Qt::blue); QVERIFY(panel); QCOMPARE(panel->windowType(), NET::Dock); QVERIFY(panel->isDock()); QCOMPARE(panel->geometry(), panelGeometry); QCOMPARE(panel->hasStrut(), false); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 0), QRect(0, 0, 1280, 1024)); QCOMPARE(panel->layer(), KWin::NormalLayer); // create a Window QScopedPointer surface2(Test::createSurface()); QVERIFY(!surface2.isNull()); QScopedPointer shellSurface2(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface2.data())); QVERIFY(!shellSurface2.isNull()); QFETCH(QRect, windowGeometry); auto c = Test::renderAndWaitForShown(surface2.data(), windowGeometry.size(), Qt::red); QVERIFY(c); QCOMPARE(c->windowType(), NET::Normal); QVERIFY(c->isActive()); QCOMPARE(c->layer(), KWin::NormalLayer); c->move(windowGeometry.topLeft()); QCOMPARE(c->geometry(), windowGeometry); auto stackingOrder = workspace()->stackingOrder(); QCOMPARE(stackingOrder.count(), 2); QCOMPARE(stackingOrder.first(), panel); QCOMPARE(stackingOrder.last(), c); QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); QVERIFY(stackingOrderChangedSpy.isValid()); // trigger screenedge QFETCH(QPoint, triggerPoint); KWin::Cursor::setPos(triggerPoint); QCOMPARE(stackingOrderChangedSpy.count(), 1); stackingOrder = workspace()->stackingOrder(); QCOMPARE(stackingOrder.count(), 2); QCOMPARE(stackingOrder.first(), c); QCOMPARE(stackingOrder.last(), panel); } void PlasmaSurfaceTest::testPanelActivate_data() { QTest::addColumn("wantsFocus"); QTest::addColumn("active"); QTest::newRow("no focus") << false << false; QTest::newRow("focus") << true << true; } void PlasmaSurfaceTest::testPanelActivate() { QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface.data())); QVERIFY(!shellSurface.isNull()); QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); QFETCH(bool, wantsFocus); plasmaSurface->setPanelTakesFocus(wantsFocus); auto panel = Test::renderAndWaitForShown(surface.data(), QSize(100, 200), Qt::blue); QVERIFY(panel); QCOMPARE(panel->windowType(), NET::Dock); QVERIFY(panel->isDock()); QFETCH(bool, active); QCOMPARE(panel->dockWantsInput(), active); QCOMPARE(panel->isActive(), active); } WAYLANDTEST_MAIN(PlasmaSurfaceTest) #include "plasma_surface_test.moc" diff --git a/autotests/integration/plasmawindow_test.cpp b/autotests/integration/plasmawindow_test.cpp index 45345afb0..80b57ea36 100644 --- a/autotests/integration/plasmawindow_test.cpp +++ b/autotests/integration/plasmawindow_test.cpp @@ -1,366 +1,366 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "client.h" #include "cursor.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include //screenlocker #include #include #include #include #include using namespace KWayland::Client; namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_plasma-window-0"); class PlasmaWindowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testCreateDestroyX11PlasmaWindow(); void testInternalWindowNoPlasmaWindow(); void testPopupWindowNoPlasmaWindow(); void testLockScreenNoPlasmaWindow(); void testDestroyedButNotUnmapped(); private: PlasmaWindowManagement *m_windowManagement = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; Shell *m_shell = nullptr; }; void PlasmaWindowTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); setenv("QMLSCENE_DEVICE", "softwarecontext", true); waylandServer()->initWorkspace(); } void PlasmaWindowTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::WindowManagement)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::WindowManagement)); m_windowManagement = Test::waylandWindowManagement(); m_compositor = Test::waylandCompositor(); m_shell = Test::waylandShell(); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void PlasmaWindowTest::cleanup() { Test::destroyWaylandConnection(); } void PlasmaWindowTest::testCreateDestroyX11PlasmaWindow() { // this test verifies that a PlasmaWindow gets unmapped on Client side when an X11 client is destroyed QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); QVERIFY(plasmaWindowCreatedSpy.isValid()); // create an xcb window struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 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, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QVERIFY(client->isDecorated()); QVERIFY(client->isActive()); // verify that it gets the keyboard focus QVERIFY(!client->surface()); // we don't have a surface yet, so focused keyboard surface if set is not ours QVERIFY(!waylandServer()->seat()->focusedKeyboardSurface()); QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); QVERIFY(surfaceChangedSpy.isValid()); QVERIFY(surfaceChangedSpy.wait()); QVERIFY(client->surface()); QCOMPARE(waylandServer()->seat()->focusedKeyboardSurface(), client->surface()); // now that should also give it to us on client side QVERIFY(plasmaWindowCreatedSpy.wait()); QCOMPARE(plasmaWindowCreatedSpy.count(), 1); QCOMPARE(m_windowManagement->windows().count(), 1); auto pw = m_windowManagement->windows().first(); QCOMPARE(pw->geometry(), client->geometry()); QSignalSpy geometryChangedSpy(pw, &PlasmaWindow::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy unmappedSpy(m_windowManagement->windows().first(), &PlasmaWindow::unmapped); QVERIFY(unmappedSpy.isValid()); QSignalSpy destroyedSpy(m_windowManagement->windows().first(), &QObject::destroyed); QVERIFY(destroyedSpy.isValid()); // now shade the window const QRect geoBeforeShade = client->geometry(); QVERIFY(geoBeforeShade.isValid()); QVERIFY(!geoBeforeShade.isEmpty()); workspace()->slotWindowShade(); QVERIFY(client->isShade()); QVERIFY(client->geometry() != geoBeforeShade); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(pw->geometry(), client->geometry()); // and unshade again workspace()->slotWindowShade(); QVERIFY(!client->isShade()); QCOMPARE(client->geometry(), geoBeforeShade); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(pw->geometry(), geoBeforeShade); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); xcb_destroy_window(c.data(), w); c.reset(); QVERIFY(unmappedSpy.wait()); QCOMPARE(unmappedSpy.count(), 1); QVERIFY(destroyedSpy.wait()); } class HelperWindow : public QRasterWindow { Q_OBJECT public: HelperWindow(); ~HelperWindow(); protected: void paintEvent(QPaintEvent *event) override; }; HelperWindow::HelperWindow() : QRasterWindow(nullptr) { } HelperWindow::~HelperWindow() = default; void HelperWindow::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter p(this); p.fillRect(0, 0, width(), height(), Qt::red); } void PlasmaWindowTest::testInternalWindowNoPlasmaWindow() { // this test verifies that an internal window is not added as a PlasmaWindow to the client QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); QVERIFY(plasmaWindowCreatedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(!plasmaWindowCreatedSpy.wait()); } void PlasmaWindowTest::testPopupWindowNoPlasmaWindow() { // this test verifies that for a popup window no PlasmaWindow is sent to the client QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); QVERIFY(plasmaWindowCreatedSpy.isValid()); // first create the parent window QScopedPointer parentSurface(Test::createSurface()); QScopedPointer parentShellSurface(Test::createShellSurface(parentSurface.data())); // map that window Test::render(parentSurface.data(), QSize(100, 50), Qt::blue); // this should create a plasma window QVERIFY(plasmaWindowCreatedSpy.wait()); // now let's create a popup window for it QScopedPointer popupSurface(Test::createSurface()); QScopedPointer popupShellSurface(Test::createShellSurface(popupSurface.data())); popupShellSurface->setTransient(parentSurface.data(), QPoint(0, 0), ShellSurface::TransientFlag::NoFocus); // let's map it Test::render(popupSurface.data(), QSize(100, 50), Qt::blue); // this should not create a plasma window QVERIFY(!plasmaWindowCreatedSpy.wait()); // now the same with an already mapped surface when we create the shell surface QScopedPointer popup2Surface(Test::createSurface()); Test::render(popup2Surface.data(), QSize(100, 50), Qt::blue); QScopedPointer popup2ShellSurface(Test::createShellSurface(popup2Surface.data())); popup2ShellSurface->setTransient(popupSurface.data(), QPoint(0, 0), ShellSurface::TransientFlag::NoFocus); // this should not create a plasma window QEXPECT_FAIL("", "The call to setTransient comes to late the window is already mapped then", Continue); QVERIFY(!plasmaWindowCreatedSpy.wait()); // let's destroy the windows QCOMPARE(waylandServer()->clients().count(), 3); QSignalSpy destroyed1Spy(waylandServer()->clients().last(), &QObject::destroyed); QVERIFY(destroyed1Spy.isValid()); popup2Surface->attachBuffer(Buffer::Ptr()); popup2Surface->commit(Surface::CommitFlag::None); popup2ShellSurface.reset(); popup2Surface.reset(); QVERIFY(destroyed1Spy.wait()); QCOMPARE(waylandServer()->clients().count(), 2); QSignalSpy destroyed2Spy(waylandServer()->clients().last(), &QObject::destroyed); QVERIFY(destroyed2Spy.isValid()); popupSurface->attachBuffer(Buffer::Ptr()); popupSurface->commit(Surface::CommitFlag::None); popupShellSurface.reset(); popupSurface.reset(); QVERIFY(destroyed2Spy.wait()); QCOMPARE(waylandServer()->clients().count(), 1); QSignalSpy destroyed3Spy(waylandServer()->clients().last(), &QObject::destroyed); QVERIFY(destroyed3Spy.isValid()); parentSurface->attachBuffer(Buffer::Ptr()); parentSurface->commit(Surface::CommitFlag::None); parentShellSurface.reset(); parentSurface.reset(); QVERIFY(destroyed3Spy.wait()); } void PlasmaWindowTest::testLockScreenNoPlasmaWindow() { if (!QFile::exists(QStringLiteral("/dev/dri/card0"))) { QSKIP("Needs a dri device"); } // this test verifies that lock screen windows are not exposed to PlasmaWindow QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); QVERIFY(plasmaWindowCreatedSpy.isValid()); // this time we use a QSignalSpy on ShellClient as it'a a little bit more complex setup QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); // lock ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); QVERIFY(clientAddedSpy.wait()); QVERIFY(clientAddedSpy.first().first().value()->isLockScreen()); // should not be sent to the client QVERIFY(plasmaWindowCreatedSpy.isEmpty()); QVERIFY(!plasmaWindowCreatedSpy.wait()); // fake unlock QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); QVERIFY(lockStateChangedSpy.isValid()); const auto children = ScreenLocker::KSldApp::self()->children(); for (auto it = children.begin(); it != children.end(); ++it) { if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) { continue; } QMetaObject::invokeMethod(*it, "requestUnlock"); break; } QVERIFY(lockStateChangedSpy.wait()); QVERIFY(!waylandServer()->isScreenLocked()); } void PlasmaWindowTest::testDestroyedButNotUnmapped() { // this test verifies that also when a ShellSurface gets destroyed without a prior unmap // the PlasmaWindow gets destroyed on Client side QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); QVERIFY(plasmaWindowCreatedSpy.isValid()); // first create the parent window QScopedPointer parentSurface(Test::createSurface()); QScopedPointer parentShellSurface(Test::createShellSurface(parentSurface.data())); // map that window Test::render(parentSurface.data(), QSize(100, 50), Qt::blue); // this should create a plasma window QVERIFY(plasmaWindowCreatedSpy.wait()); QCOMPARE(plasmaWindowCreatedSpy.count(), 1); auto window = plasmaWindowCreatedSpy.first().first().value(); QVERIFY(window); QSignalSpy destroyedSpy(window, &QObject::destroyed); QVERIFY(destroyedSpy.isValid()); // now destroy without an unmap parentShellSurface.reset(); parentSurface.reset(); QVERIFY(destroyedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::PlasmaWindowTest) #include "plasmawindow_test.moc" diff --git a/autotests/integration/pointer_constraints_test.cpp b/autotests/integration/pointer_constraints_test.cpp index 547572da5..848dc6672 100644 --- a/autotests/integration/pointer_constraints_test.cpp +++ b/autotests/integration/pointer_constraints_test.cpp @@ -1,379 +1,379 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "keyboard_input.h" #include "platform.h" #include "pointer_input.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KWin; using namespace KWayland::Client; typedef std::function PointerFunc; Q_DECLARE_METATYPE(PointerFunc) static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_constraints-0"); class TestPointerConstraints : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testConfinedPointer_data(); void testConfinedPointer(); void testLockedPointer_data(); void testLockedPointer(); void testBreakConstrainedPointer_data(); void testBreakConstrainedPointer(); }; void TestPointerConstraints::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TestPointerConstraints::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::PointerConstraints)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::PointerConstraints)); QVERIFY(Test::waitForWaylandPointer()); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(1280, 512)); } void TestPointerConstraints::cleanup() { Test::destroyWaylandConnection(); } void TestPointerConstraints::testConfinedPointer_data() { QTest::addColumn("type"); QTest::addColumn("positionFunction"); QTest::addColumn("xOffset"); QTest::addColumn("yOffset"); PointerFunc bottomLeft = &QRect::bottomLeft; PointerFunc bottomRight = &QRect::bottomRight; PointerFunc topRight = &QRect::topRight; PointerFunc topLeft = &QRect::topLeft; QTest::newRow("wlShell - bottomLeft") << Test::ShellSurfaceType::WlShell << bottomLeft << -1 << 1; QTest::newRow("wlShell - bottomRight") << Test::ShellSurfaceType::WlShell << bottomRight << 1 << 1; QTest::newRow("wlShell - topLeft") << Test::ShellSurfaceType::WlShell << topLeft << -1 << -1; QTest::newRow("wlShell - topRight") << Test::ShellSurfaceType::WlShell << topRight << 1 << -1; QTest::newRow("XdgShellV5 - bottomLeft") << Test::ShellSurfaceType::XdgShellV5 << bottomLeft << -1 << 1; QTest::newRow("XdgShellV5 - bottomRight") << Test::ShellSurfaceType::XdgShellV5 << bottomRight << 1 << 1; QTest::newRow("XdgShellV5 - topLeft") << Test::ShellSurfaceType::XdgShellV5 << topLeft << -1 << -1; QTest::newRow("XdgShellV5 - topRight") << Test::ShellSurfaceType::XdgShellV5 << topRight << 1 << -1; } void TestPointerConstraints::testConfinedPointer() { // this test sets up a Surface with a confined pointer // simple interaction test to verify that the pointer gets confined QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QScopedPointer pointer(Test::waylandSeat()->createPointer()); QScopedPointer confinedPointer(Test::waylandPointerConstraints()->confinePointer(surface.data(), pointer.data(), nullptr, PointerConstraints::LifeTime::OneShot)); QSignalSpy confinedSpy(confinedPointer.data(), &ConfinedPointer::confined); QVERIFY(confinedSpy.isValid()); QSignalSpy unconfinedSpy(confinedPointer.data(), &ConfinedPointer::unconfined); QVERIFY(unconfinedSpy.isValid()); // now map the window auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 100), Qt::blue); QVERIFY(c); if (c->geometry().topLeft() == QPoint(0, 0)) { c->move(QPoint(1, 1)); } QVERIFY(!c->geometry().contains(KWin::Cursor::pos())); // now let's confine QCOMPARE(input()->pointer()->isConstrained(), false); KWin::Cursor::setPos(c->geometry().center()); QCOMPARE(input()->pointer()->isConstrained(), true); QVERIFY(confinedSpy.wait()); // picking a position outside the window geometry should not move pointer QSignalSpy pointerPositionChangedSpy(input(), &InputRedirection::globalPointerChanged); QVERIFY(pointerPositionChangedSpy.isValid()); KWin::Cursor::setPos(QPoint(1280, 512)); QVERIFY(pointerPositionChangedSpy.isEmpty()); QCOMPARE(KWin::Cursor::pos(), c->geometry().center()); // TODO: test relative motion QFETCH(PointerFunc, positionFunction); const QPoint position = positionFunction(c->geometry()); KWin::Cursor::setPos(position); QCOMPARE(pointerPositionChangedSpy.count(), 1); QCOMPARE(KWin::Cursor::pos(), position); // moving one to right should not be possible QFETCH(int, xOffset); KWin::Cursor::setPos(position + QPoint(xOffset, 0)); QCOMPARE(pointerPositionChangedSpy.count(), 1); QCOMPARE(KWin::Cursor::pos(), position); // moving one to bottom should not be possible QFETCH(int, yOffset); KWin::Cursor::setPos(position + QPoint(0, yOffset)); QCOMPARE(pointerPositionChangedSpy.count(), 1); QCOMPARE(KWin::Cursor::pos(), position); // let's break the constraint explicitly input()->pointer()->breakPointerConstraints(); QCOMPARE(input()->pointer()->isConstrained(), false); QVERIFY(unconfinedSpy.wait()); confinedPointer.reset(Test::waylandPointerConstraints()->confinePointer(surface.data(), pointer.data(), nullptr, PointerConstraints::LifeTime::Persistent)); QSignalSpy confinedSpy2(confinedPointer.data(), &ConfinedPointer::confined); QVERIFY(confinedSpy2.isValid()); QSignalSpy unconfinedSpy2(confinedPointer.data(), &ConfinedPointer::unconfined); QVERIFY(unconfinedSpy2.isValid()); // should get confined QVERIFY(confinedSpy2.wait()); QCOMPARE(input()->pointer()->isConstrained(), true); // now let's unconfine again, any pointer movement should confine again input()->pointer()->breakPointerConstraints(); QCOMPARE(input()->pointer()->isConstrained(), false); QVERIFY(unconfinedSpy2.wait()); KWin::Cursor::setPos(c->geometry().center()); QCOMPARE(KWin::Cursor::pos(), c->geometry().center()); QCOMPARE(input()->pointer()->isConstrained(), true); QVERIFY(confinedSpy2.wait()); // let's use the other break constraint and block input()->pointer()->breakPointerConstraints(); input()->pointer()->blockPointerConstraints(); QCOMPARE(input()->pointer()->isConstrained(), false); KWin::Cursor::setPos(c->geometry().center() + QPoint(1, 1)); QCOMPARE(input()->pointer()->isConstrained(), false); QVERIFY(!confinedSpy2.wait()); // now move outside and back in again, that should confine KWin::Cursor::setPos(c->geometry().bottomRight() + QPoint(1, 1)); KWin::Cursor::setPos(c->geometry().center() + QPoint(1, 1)); QCOMPARE(input()->pointer()->isConstrained(), true); QVERIFY(confinedSpy2.wait()); // create a second window and move it above our constrained window QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(Test::createShellSurface(type, surface2.data())); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(1280, 1024), Qt::blue); QVERIFY(c2); QVERIFY(unconfinedSpy2.wait()); // and unmapping the second window should confine again shellSurface2.reset(); surface2.reset(); QVERIFY(confinedSpy2.wait()); // let's set a region which results in unconfined auto r = Test::waylandCompositor()->createRegion(QRegion(0, 0, 1, 1)); confinedPointer->setRegion(r.get()); surface->commit(Surface::CommitFlag::None); QVERIFY(unconfinedSpy2.wait()); QCOMPARE(input()->pointer()->isConstrained(), false); // and set a full region again, that should confine confinedPointer->setRegion(nullptr); surface->commit(Surface::CommitFlag::None); QVERIFY(confinedSpy2.wait()); QCOMPARE(input()->pointer()->isConstrained(), true); // and now unmap shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(c)); QCOMPARE(input()->pointer()->isConstrained(), false); } void TestPointerConstraints::testLockedPointer_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void TestPointerConstraints::testLockedPointer() { // this test sets up a Surface with a locked pointer // simple interaction test to verify that the pointer gets locked // the various ways to unlock are not tested as that's already verified by testConfinedPointer QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QScopedPointer pointer(Test::waylandSeat()->createPointer()); QScopedPointer lockedPointer(Test::waylandPointerConstraints()->lockPointer(surface.data(), pointer.data(), nullptr, PointerConstraints::LifeTime::OneShot)); QSignalSpy lockedSpy(lockedPointer.data(), &LockedPointer::locked); QVERIFY(lockedSpy.isValid()); QSignalSpy unlockedSpy(lockedPointer.data(), &LockedPointer::unlocked); QVERIFY(unlockedSpy.isValid()); // now map the window auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 100), Qt::blue); QVERIFY(c); QVERIFY(!c->geometry().contains(KWin::Cursor::pos())); // now let's lock QCOMPARE(input()->pointer()->isConstrained(), false); KWin::Cursor::setPos(c->geometry().center()); QCOMPARE(KWin::Cursor::pos(), c->geometry().center()); QCOMPARE(input()->pointer()->isConstrained(), true); QVERIFY(lockedSpy.wait()); // try to move the pointer // TODO: add relative pointer KWin::Cursor::setPos(c->geometry().center() + QPoint(1, 1)); QCOMPARE(KWin::Cursor::pos(), c->geometry().center()); // now unlock again input()->pointer()->breakPointerConstraints(); QCOMPARE(input()->pointer()->isConstrained(), false); QVERIFY(unlockedSpy.wait()); // moving cursor should be allowed again KWin::Cursor::setPos(c->geometry().center() + QPoint(1, 1)); QCOMPARE(KWin::Cursor::pos(), c->geometry().center() + QPoint(1, 1)); } void TestPointerConstraints::testBreakConstrainedPointer_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void TestPointerConstraints::testBreakConstrainedPointer() { // this test verifies the breaking of Pointer constraints through the keyboard event filter QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QScopedPointer pointer(Test::waylandSeat()->createPointer()); QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); QSignalSpy keyboardEnteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(keyboardEnteredSpy.isValid()); QSignalSpy keyboardLeftSpy(keyboard.data(), &Keyboard::left); QVERIFY(keyboardLeftSpy.isValid()); QSignalSpy keyChangedSpy(keyboard.data(), &Keyboard::keyChanged); QVERIFY(keyChangedSpy.isValid()); QScopedPointer lockedPointer(Test::waylandPointerConstraints()->lockPointer(surface.data(), pointer.data(), nullptr, PointerConstraints::LifeTime::Persistent)); QSignalSpy lockedSpy(lockedPointer.data(), &LockedPointer::locked); QVERIFY(lockedSpy.isValid()); QSignalSpy unlockedSpy(lockedPointer.data(), &LockedPointer::unlocked); QVERIFY(unlockedSpy.isValid()); // now map the window auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 100), Qt::blue); QVERIFY(c); QVERIFY(!c->geometry().contains(KWin::Cursor::pos())); QVERIFY(keyboardEnteredSpy.wait()); // now let's lock QCOMPARE(input()->pointer()->isConstrained(), false); KWin::Cursor::setPos(c->geometry().center()); QCOMPARE(KWin::Cursor::pos(), c->geometry().center()); QCOMPARE(input()->pointer()->isConstrained(), true); QVERIFY(lockedSpy.wait()); // now try to break quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++); QVERIFY(keyboardLeftSpy.wait()); // and just waiting should break constrain QVERIFY(unlockedSpy.wait()); QCOMPARE(input()->pointer()->isConstrained(), false); // and should enter again QTRY_COMPARE(keyboardEnteredSpy.count(), 2); QCOMPARE(waylandServer()->seat()->focusedKeyboardSurface(), c->surface()); kwinApp()->platform()->keyboardKeyReleased(KEY_ESC, timestamp++); QVERIFY(!keyChangedSpy.wait()); QVERIFY(keyChangedSpy.isEmpty()); // now lock again // need to move out and in KWin::Cursor::setPos(c->geometry().bottomRight() + QPoint(1, 1)); KWin::Cursor::setPos(c->geometry().center()); QCOMPARE(KWin::Cursor::pos(), c->geometry().center()); QCOMPARE(input()->pointer()->isConstrained(), true); QVERIFY(lockedSpy.wait()); // and just do a key press/release on esc kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_ESC, timestamp++); QCOMPARE(input()->pointer()->isConstrained(), true); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.last().at(0).value(), quint32(KEY_ESC)); // and another variant which won't break kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); QCOMPARE(input()->pointer()->isConstrained(), true); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.last().at(0).value(), quint32(KEY_LEFTSHIFT)); QVERIFY(!unlockedSpy.wait()); kwinApp()->platform()->keyboardKeyReleased(KEY_ESC, timestamp++); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.last().at(0).value(), quint32(KEY_ESC)); // and now break for real kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++); QVERIFY(unlockedSpy.wait()); kwinApp()->platform()->keyboardKeyReleased(KEY_ESC, timestamp++); } WAYLANDTEST_MAIN(TestPointerConstraints) #include "pointer_constraints_test.moc" diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp index 37f0a401b..0026c1edc 100644 --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -1,924 +1,924 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "deleted.h" #include "pointer_input.h" #include "options.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_input-0"); class PointerInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testWarpingUpdatesFocus(); void testWarpingGeneratesPointerMotion(); void testUpdateFocusAfterScreenChange(); void testModifierClickUnrestrictedMove_data(); void testModifierClickUnrestrictedMove(); void testModifierScrollOpacity_data(); void testModifierScrollOpacity(); void testScrollAction(); void testFocusFollowsMouse(); void testMouseActionInactiveWindow_data(); void testMouseActionInactiveWindow(); void testMouseActionActiveWindow_data(); void testMouseActionActiveWindow(); void testCursorImage(); void testEffectOverrideCursorImage(); private: void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50)); KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::Shell *m_shell = nullptr; }; void PointerInputTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void PointerInputTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandPointer()); m_compositor = Test::waylandCompositor(); m_shell = Test::waylandShell(); m_seat = Test::waylandSeat(); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void PointerInputTest::cleanup() { Test::destroyWaylandConnection(); } void PointerInputTest::render(KWayland::Client::Surface *surface, const QSize &size) { Test::render(surface, size, Qt::blue); Test::flushWaylandConnection(); } void PointerInputTest::testWarpingUpdatesFocus() { // this test verifies that warping the pointer creates pointer enter and leave events using namespace KWayland::Client; // create pointer and signal spy for enter and leave signals 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()); // create a window 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); // currently there should not be a focused pointer surface QVERIFY(!waylandServer()->seat()->focusedPointerSurface()); QVERIFY(!pointer->enteredSurface()); // enter Cursor::setPos(QPoint(25, 25)); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 1); QCOMPARE(enteredSpy.first().at(1).toPointF(), QPointF(25, 25)); // window should have focus QCOMPARE(pointer->enteredSurface(), surface); // also on the server QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface()); // and out again Cursor::setPos(QPoint(250, 250));; QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.count(), 1); // there should not be a focused pointer surface anymore QVERIFY(!waylandServer()->seat()->focusedPointerSurface()); QVERIFY(!pointer->enteredSurface()); } void PointerInputTest::testWarpingGeneratesPointerMotion() { // this test verifies that warping the pointer creates pointer motion events using namespace KWayland::Client; // create pointer and signal spy for enter and motion auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy movedSpy(pointer, &Pointer::motion); QVERIFY(movedSpy.isValid()); // create a window 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); // enter kwinApp()->platform()->pointerMotion(QPointF(25, 25), 1); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.first().at(1).toPointF(), QPointF(25, 25)); // now warp Cursor::setPos(QPoint(26, 26)); QVERIFY(movedSpy.wait()); QCOMPARE(movedSpy.count(), 1); QCOMPARE(movedSpy.last().first().toPointF(), QPointF(26, 26)); } void PointerInputTest::testUpdateFocusAfterScreenChange() { // this test verifies that a pointer enter event is generated when the cursor changes to another // screen due to removal of screen using namespace KWayland::Client; // ensure cursor is on second screen Cursor::setPos(1500, 300); // create pointer and signal spy for enter and motion auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); // create a window 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, QSize(1280, 1024)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); QVERIFY(!window->geometry().contains(Cursor::pos())); QSignalSpy screensChangedSpy(screens(), &Screens::changed); QVERIFY(screensChangedSpy.isValid()); // now let's remove the screen containing the cursor QMetaObject::invokeMethod(kwinApp()->platform(), "outputGeometriesChanged", Qt::DirectConnection, Q_ARG(QVector, QVector{QRect(0, 0, 1280, 1024)})); QVERIFY(screensChangedSpy.wait()); QCOMPARE(screens()->count(), 1); // this should have warped the cursor QCOMPARE(Cursor::pos(), QPoint(639, 511)); QVERIFY(window->geometry().contains(Cursor::pos())); // and we should get an enter event QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 1); } void PointerInputTest::testModifierClickUnrestrictedMove_data() { QTest::addColumn("modifierKey"); QTest::addColumn("mouseButton"); QTest::addColumn("modKey"); QTest::addColumn("capsLock"); const QString alt = QStringLiteral("Alt"); const QString meta = QStringLiteral("Meta"); QTest::newRow("Left Alt + Left Click") << KEY_LEFTALT << BTN_LEFT << alt << false; QTest::newRow("Left Alt + Right Click") << KEY_LEFTALT << BTN_RIGHT << alt << false; QTest::newRow("Left Alt + Middle Click") << KEY_LEFTALT << BTN_MIDDLE << alt << false; QTest::newRow("Right Alt + Left Click") << KEY_RIGHTALT << BTN_LEFT << alt << false; QTest::newRow("Right Alt + Right Click") << KEY_RIGHTALT << BTN_RIGHT << alt << false; QTest::newRow("Right Alt + Middle Click") << KEY_RIGHTALT << BTN_MIDDLE << alt << false; // now everything with meta QTest::newRow("Left Meta + Left Click") << KEY_LEFTMETA << BTN_LEFT << meta << false; QTest::newRow("Left Meta + Right Click") << KEY_LEFTMETA << BTN_RIGHT << meta << false; QTest::newRow("Left Meta + Middle Click") << KEY_LEFTMETA << BTN_MIDDLE << meta << false; QTest::newRow("Right Meta + Left Click") << KEY_RIGHTMETA << BTN_LEFT << meta << false; QTest::newRow("Right Meta + Right Click") << KEY_RIGHTMETA << BTN_RIGHT << meta << false; QTest::newRow("Right Meta + Middle Click") << KEY_RIGHTMETA << BTN_MIDDLE << meta << false; // and with capslock QTest::newRow("Left Alt + Left Click/CapsLock") << KEY_LEFTALT << BTN_LEFT << alt << true; QTest::newRow("Left Alt + Right Click/CapsLock") << KEY_LEFTALT << BTN_RIGHT << alt << true; QTest::newRow("Left Alt + Middle Click/CapsLock") << KEY_LEFTALT << BTN_MIDDLE << alt << true; QTest::newRow("Right Alt + Left Click/CapsLock") << KEY_RIGHTALT << BTN_LEFT << alt << true; QTest::newRow("Right Alt + Right Click/CapsLock") << KEY_RIGHTALT << BTN_RIGHT << alt << true; QTest::newRow("Right Alt + Middle Click/CapsLock") << KEY_RIGHTALT << BTN_MIDDLE << alt << true; // now everything with meta QTest::newRow("Left Meta + Left Click/CapsLock") << KEY_LEFTMETA << BTN_LEFT << meta << true; QTest::newRow("Left Meta + Right Click/CapsLock") << KEY_LEFTMETA << BTN_RIGHT << meta << true; QTest::newRow("Left Meta + Middle Click/CapsLock") << KEY_LEFTMETA << BTN_MIDDLE << meta << true; QTest::newRow("Right Meta + Left Click/CapsLock") << KEY_RIGHTMETA << BTN_LEFT << meta << true; QTest::newRow("Right Meta + Right Click/CapsLock") << KEY_RIGHTMETA << BTN_RIGHT << meta << true; QTest::newRow("Right Meta + Middle Click/CapsLock") << KEY_RIGHTMETA << BTN_MIDDLE << meta << true; } void PointerInputTest::testModifierClickUnrestrictedMove() { // this test ensures that Alt+mouse button press triggers unrestricted move using namespace KWayland::Client; // create pointer and signal spy for button events auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy buttonSpy(pointer, &Pointer::buttonStateChanged); QVERIFY(buttonSpy.isValid()); // first modify the config for this run QFETCH(QString, modKey); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", modKey); group.writeEntry("CommandAll1", "Move"); group.writeEntry("CommandAll2", "Move"); group.writeEntry("CommandAll3", "Move"); group.sync(); workspace()->slotReconfigure(); QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier); QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); // create a window 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 on window Cursor::setPos(window->geometry().center()); // simulate modifier+click quint32 timestamp = 1; QFETCH(bool, capsLock); if (capsLock) { kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); } QFETCH(int, modifierKey); QFETCH(int, mouseButton); kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++); QVERIFY(!window->isMove()); kwinApp()->platform()->pointerButtonPressed(mouseButton, timestamp++); QVERIFY(window->isMove()); // release modifier should not change it kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); QVERIFY(window->isMove()); // but releasing the key should end move/resize kwinApp()->platform()->pointerButtonReleased(mouseButton, timestamp++); QVERIFY(!window->isMove()); if (capsLock) { kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); } // all of that should not have triggered button events on the surface QCOMPARE(buttonSpy.count(), 0); // also waiting shouldn't give us the event QVERIFY(!buttonSpy.wait(100)); } void PointerInputTest::testModifierScrollOpacity_data() { QTest::addColumn("modifierKey"); QTest::addColumn("modKey"); QTest::addColumn("capsLock"); const QString alt = QStringLiteral("Alt"); const QString meta = QStringLiteral("Meta"); QTest::newRow("Left Alt") << KEY_LEFTALT << alt << false; QTest::newRow("Right Alt") << KEY_RIGHTALT << alt << false; QTest::newRow("Left Meta") << KEY_LEFTMETA << meta << false; QTest::newRow("Right Meta") << KEY_RIGHTMETA << meta << false; QTest::newRow("Left Alt/CapsLock") << KEY_LEFTALT << alt << true; QTest::newRow("Right Alt/CapsLock") << KEY_RIGHTALT << alt << true; QTest::newRow("Left Meta/CapsLock") << KEY_LEFTMETA << meta << true; QTest::newRow("Right Meta/CapsLock") << KEY_RIGHTMETA << meta << true; } void PointerInputTest::testModifierScrollOpacity() { // this test verifies that mod+wheel performs a window operation and does not // pass the wheel to the window using namespace KWayland::Client; // create pointer and signal spy for button events auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy axisSpy(pointer, &Pointer::axisChanged); QVERIFY(axisSpy.isValid()); // first modify the config for this run QFETCH(QString, modKey); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", modKey); group.writeEntry("CommandAllWheel", "change opacity"); group.sync(); workspace()->slotReconfigure(); // create a window 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); // set the opacity to 0.5 window->setOpacity(0.5); QCOMPARE(window->opacity(), 0.5); // move cursor on window Cursor::setPos(window->geometry().center()); // simulate modifier+wheel quint32 timestamp = 1; QFETCH(bool, capsLock); if (capsLock) { kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); } QFETCH(int, modifierKey); kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++); kwinApp()->platform()->pointerAxisVertical(-5, timestamp++); QCOMPARE(window->opacity(), 0.6); kwinApp()->platform()->pointerAxisVertical(5, timestamp++); QCOMPARE(window->opacity(), 0.5); kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); if (capsLock) { kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); } // axis should have been filtered out QCOMPARE(axisSpy.count(), 0); QVERIFY(!axisSpy.wait(100)); } void PointerInputTest::testScrollAction() { // this test verifies that scroll on inactive window performs a mouse action using namespace KWayland::Client; auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy axisSpy(pointer, &Pointer::axisChanged); QVERIFY(axisSpy.isValid()); // first modify the config for this run KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandWindowWheel", "activate and scroll"); group.sync(); workspace()->slotReconfigure(); // create two windows QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = Test::createSurface(m_compositor); QVERIFY(surface1); ShellSurface *shellSurface1 = Test::createShellSurface(surface1, surface1); QVERIFY(shellSurface1); render(surface1); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = Test::createSurface(m_compositor); QVERIFY(surface2); ShellSurface *shellSurface2 = Test::createShellSurface(surface2, surface2); QVERIFY(shellSurface2); render(surface2); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); // move cursor to the inactive window Cursor::setPos(window1->geometry().center()); quint32 timestamp = 1; QVERIFY(!window1->isActive()); kwinApp()->platform()->pointerAxisVertical(5, timestamp++); QVERIFY(window1->isActive()); // but also the wheel event should be passed to the window QVERIFY(axisSpy.wait()); // we need to wait a little bit, otherwise the test crashes in effectshandler, needs fixing QTest::qWait(100); } void PointerInputTest::testFocusFollowsMouse() { using namespace KWayland::Client; // need to create a pointer, otherwise it doesn't accept focus auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); // move cursor out of the way of first window to be created Cursor::setPos(900, 900); // first modify the config for this run KConfigGroup group = kwinApp()->config()->group("Windows"); group.writeEntry("AutoRaise", true); group.writeEntry("AutoRaiseInterval", 20); group.writeEntry("DelayFocusInterval", 200); group.writeEntry("FocusPolicy", "FocusFollowsMouse"); group.sync(); workspace()->slotReconfigure(); // verify the settings QCOMPARE(options->focusPolicy(), Options::FocusFollowsMouse); QVERIFY(options->isAutoRaise()); QCOMPARE(options->autoRaiseInterval(), 20); QCOMPARE(options->delayFocusInterval(), 200); // create two windows QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = Test::createSurface(m_compositor); QVERIFY(surface1); ShellSurface *shellSurface1 = Test::createShellSurface(surface1, surface1); QVERIFY(shellSurface1); render(surface1, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = Test::createSurface(m_compositor); QVERIFY(surface2); ShellSurface *shellSurface2 = Test::createShellSurface(surface2, surface2); QVERIFY(shellSurface2); render(surface2, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); // geometry of the two windows should be overlapping QVERIFY(window1->geometry().intersects(window2->geometry())); // signal spies for active window changed and stacking order changed QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::clientActivated); QVERIFY(activeWindowChangedSpy.isValid()); QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); QVERIFY(stackingOrderChangedSpy.isValid()); QVERIFY(!window1->isActive()); QVERIFY(window2->isActive()); // move on top of first window QVERIFY(window1->geometry().contains(10, 10)); QVERIFY(!window2->geometry().contains(10, 10)); Cursor::setPos(10, 10); QVERIFY(stackingOrderChangedSpy.wait()); QCOMPARE(stackingOrderChangedSpy.count(), 1); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); QTRY_VERIFY(window1->isActive()); // move on second window, but move away before active window change delay hits Cursor::setPos(810, 810); QVERIFY(stackingOrderChangedSpy.wait()); QCOMPARE(stackingOrderChangedSpy.count(), 2); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); Cursor::setPos(10, 10); QVERIFY(!activeWindowChangedSpy.wait(250)); QVERIFY(window1->isActive()); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); // as we moved back on window 1 that should been raised in the mean time QCOMPARE(stackingOrderChangedSpy.count(), 3); // quickly move on window 2 and back on window 1 should not raise window 2 Cursor::setPos(810, 810); Cursor::setPos(10, 10); QVERIFY(!stackingOrderChangedSpy.wait(250)); } void PointerInputTest::testMouseActionInactiveWindow_data() { QTest::addColumn("button"); QTest::newRow("Left") << quint32(BTN_LEFT); QTest::newRow("Middle") << quint32(BTN_MIDDLE); QTest::newRow("Right") << quint32(BTN_RIGHT); } void PointerInputTest::testMouseActionInactiveWindow() { // this test performs the mouse button window action on an inactive window // it should activate the window and raise it using namespace KWayland::Client; // first modify the config for this run - disable FocusFollowsMouse KConfigGroup group = kwinApp()->config()->group("Windows"); group.writeEntry("FocusPolicy", "ClickToFocus"); group.sync(); group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandWindow1", "Activate, raise and pass click"); group.writeEntry("CommandWindow2", "Activate, raise and pass click"); group.writeEntry("CommandWindow3", "Activate, raise and pass click"); group.sync(); workspace()->slotReconfigure(); // create two windows QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = Test::createSurface(m_compositor); QVERIFY(surface1); ShellSurface *shellSurface1 = Test::createShellSurface(surface1, surface1); QVERIFY(shellSurface1); render(surface1, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = Test::createSurface(m_compositor); QVERIFY(surface2); ShellSurface *shellSurface2 = Test::createShellSurface(surface2, surface2); QVERIFY(shellSurface2); render(surface2, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); // geometry of the two windows should be overlapping QVERIFY(window1->geometry().intersects(window2->geometry())); // signal spies for active window changed and stacking order changed QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::clientActivated); QVERIFY(activeWindowChangedSpy.isValid()); QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); QVERIFY(stackingOrderChangedSpy.isValid()); QVERIFY(!window1->isActive()); QVERIFY(window2->isActive()); // move on top of first window QVERIFY(window1->geometry().contains(10, 10)); QVERIFY(!window2->geometry().contains(10, 10)); Cursor::setPos(10, 10); // no focus follows mouse QVERIFY(!stackingOrderChangedSpy.wait(200)); QVERIFY(stackingOrderChangedSpy.isEmpty()); QVERIFY(activeWindowChangedSpy.isEmpty()); QVERIFY(window2->isActive()); // and click quint32 timestamp = 1; QFETCH(quint32, button); kwinApp()->platform()->pointerButtonPressed(button, timestamp++); // should raise window1 and activate it QCOMPARE(stackingOrderChangedSpy.count(), 1); QVERIFY(!activeWindowChangedSpy.isEmpty()); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); QVERIFY(window1->isActive()); QVERIFY(!window2->isActive()); // release again kwinApp()->platform()->pointerButtonReleased(button, timestamp++); } void PointerInputTest::testMouseActionActiveWindow_data() { QTest::addColumn("clickRaise"); QTest::addColumn("button"); for (quint32 i=BTN_LEFT; i < BTN_JOYSTICK; i++) { QByteArray number = QByteArray::number(i, 16); QTest::newRow(QByteArrayLiteral("click raise/").append(number).constData()) << true << i; QTest::newRow(QByteArrayLiteral("no click raise/").append(number).constData()) << false << i; } } void PointerInputTest::testMouseActionActiveWindow() { // this test verifies the mouse action performed on an active window // for all buttons it should trigger a window raise depending on the // click raise option using namespace KWayland::Client; // create a button spy - all clicks should be passed through auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy buttonSpy(pointer, &Pointer::buttonStateChanged); QVERIFY(buttonSpy.isValid()); // adjust config for this run QFETCH(bool, clickRaise); KConfigGroup group = kwinApp()->config()->group("Windows"); group.writeEntry("ClickRaise", clickRaise); group.sync(); workspace()->slotReconfigure(); QCOMPARE(options->isClickRaise(), clickRaise); // create two windows QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = Test::createSurface(m_compositor); QVERIFY(surface1); ShellSurface *shellSurface1 = Test::createShellSurface(surface1, surface1); QVERIFY(shellSurface1); render(surface1, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); QSignalSpy window1DestroyedSpy(window1, &QObject::destroyed); QVERIFY(window1DestroyedSpy.isValid()); Surface *surface2 = Test::createSurface(m_compositor); QVERIFY(surface2); ShellSurface *shellSurface2 = Test::createShellSurface(surface2, surface2); QVERIFY(shellSurface2); render(surface2, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); QSignalSpy window2DestroyedSpy(window2, &QObject::destroyed); QVERIFY(window2DestroyedSpy.isValid()); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); // geometry of the two windows should be overlapping QVERIFY(window1->geometry().intersects(window2->geometry())); // lower the currently active window workspace()->lowerClient(window2); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); // signal spy for stacking order spy QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); QVERIFY(stackingOrderChangedSpy.isValid()); // move on top of second window QVERIFY(!window1->geometry().contains(900, 900)); QVERIFY(window2->geometry().contains(900, 900)); Cursor::setPos(900, 900); // and click quint32 timestamp = 1; QFETCH(quint32, button); kwinApp()->platform()->pointerButtonPressed(button, timestamp++); QVERIFY(buttonSpy.wait()); if (clickRaise) { QCOMPARE(stackingOrderChangedSpy.count(), 1); QTRY_COMPARE_WITH_TIMEOUT(workspace()->topClientOnDesktop(1, -1), window2, 200); } else { QCOMPARE(stackingOrderChangedSpy.count(), 0); QVERIFY(!stackingOrderChangedSpy.wait(100)); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); } // release again kwinApp()->platform()->pointerButtonReleased(button, timestamp++); delete surface1; QVERIFY(window1DestroyedSpy.wait()); delete surface2; QVERIFY(window2DestroyedSpy.wait()); } void PointerInputTest::testCursorImage() { // this test verifies that the pointer image gets updated correctly from the client provided data using namespace KWayland::Client; // we need a pointer to get the enter event auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); // move cursor somewhere the new window won't open Cursor::setPos(800, 800); auto p = input()->pointer(); // at the moment it should be the fallback cursor QVERIFY(!p->cursorImage().isNull()); // create a window 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 to center of window, this should first set a null pointer Cursor::setPos(window->geometry().center()); QCOMPARE(p->window().data(), window); QVERIFY(p->cursorImage().isNull()); QVERIFY(enteredSpy.wait()); // create a cursor on the pointer Surface *cursorSurface = Test::createSurface(m_compositor); QVERIFY(cursorSurface); QSignalSpy cursorRenderedSpy(cursorSurface, &Surface::frameRendered); QVERIFY(cursorRenderedSpy.isValid()); QImage red = QImage(QSize(10, 10), QImage::Format_ARGB32); red.fill(Qt::red); cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(red)); cursorSurface->damage(QRect(0, 0, 10, 10)); cursorSurface->commit(); pointer->setCursor(cursorSurface, QPoint(5, 5)); QVERIFY(cursorRenderedSpy.wait()); QCOMPARE(p->cursorImage(), red); QCOMPARE(p->cursorHotSpot(), QPoint(5, 5)); // change hotspot pointer->setCursor(cursorSurface, QPoint(6, 6)); Test::flushWaylandConnection(); QTRY_COMPARE(p->cursorHotSpot(), QPoint(6, 6)); QCOMPARE(p->cursorImage(), red); // change the buffer QImage blue = QImage(QSize(10, 10), QImage::Format_ARGB32); blue.fill(Qt::blue); auto b = Test::waylandShmPool()->createBuffer(blue); cursorSurface->attachBuffer(b); cursorSurface->damage(QRect(0, 0, 10, 10)); cursorSurface->commit(); QVERIFY(cursorRenderedSpy.wait()); QTRY_COMPARE(p->cursorImage(), blue); QCOMPARE(p->cursorHotSpot(), QPoint(6, 6)); // hide the cursor pointer->setCursor(nullptr); Test::flushWaylandConnection(); QTRY_VERIFY(p->cursorImage().isNull()); // move cursor somewhere else, should reset to fallback cursor Cursor::setPos(window->geometry().bottomLeft() + QPoint(20, 20)); QVERIFY(p->window().isNull()); QVERIFY(!p->cursorImage().isNull()); } class HelperEffect : public Effect { Q_OBJECT public: HelperEffect() {} ~HelperEffect() {} }; void PointerInputTest::testEffectOverrideCursorImage() { // this test verifies the effect cursor override handling using namespace KWayland::Client; // we need a pointer to get the enter event and set a cursor 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()); // move cursor somewhere the new window won't open Cursor::setPos(800, 800); auto p = input()->pointer(); // here we should have the fallback cursor const QImage fallback = p->cursorImage(); QVERIFY(!fallback.isNull()); // now let's create a window 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); // and move cursor to the window QVERIFY(!window->geometry().contains(QPoint(800, 800))); Cursor::setPos(window->geometry().center()); QVERIFY(enteredSpy.wait()); // cursor image should be null QVERIFY(p->cursorImage().isNull()); // now create an effect and set an override cursor QScopedPointer effect(new HelperEffect); effects->startMouseInterception(effect.data(), Qt::SizeAllCursor); const QImage sizeAll = p->cursorImage(); QVERIFY(!sizeAll.isNull()); QVERIFY(sizeAll != fallback); QVERIFY(leftSpy.wait()); // let's change to arrow cursor, this should be our fallback effects->defineCursor(Qt::ArrowCursor); QCOMPARE(p->cursorImage(), fallback); // back to size all effects->defineCursor(Qt::SizeAllCursor); QCOMPARE(p->cursorImage(), sizeAll); // move cursor outside the window area Cursor::setPos(800, 800); // and end the override, which should switch to fallback effects->stopMouseInterception(effect.data()); QCOMPARE(p->cursorImage(), fallback); // start mouse interception again effects->startMouseInterception(effect.data(), Qt::SizeAllCursor); QCOMPARE(p->cursorImage(), sizeAll); // move cursor to area of window Cursor::setPos(window->geometry().center()); // this should not result in an enter event QVERIFY(!enteredSpy.wait(100)); // after ending the interception we should get an enter event effects->stopMouseInterception(effect.data()); QVERIFY(enteredSpy.wait()); QVERIFY(p->cursorImage().isNull()); } } WAYLANDTEST_MAIN(KWin::PointerInputTest) #include "pointer_input.moc" diff --git a/autotests/integration/quick_tiling_test.cpp b/autotests/integration/quick_tiling_test.cpp index 94544331b..9456ee655 100644 --- a/autotests/integration/quick_tiling_test.cpp +++ b/autotests/integration/quick_tiling_test.cpp @@ -1,412 +1,412 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include Q_DECLARE_METATYPE(KWin::AbstractClient::QuickTileMode) Q_DECLARE_METATYPE(KWin::MaximizeMode) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0"); class QuickTilingTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testQuickTiling_data(); void testQuickTiling(); void testQuickMaximizing_data(); void testQuickMaximizing(); void testQuickTilingKeyboardMove_data(); void testQuickTilingKeyboardMove(); void testQuickTilingPointerMove_data(); void testQuickTilingPointerMove(); private: KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Shell *m_shell = nullptr; }; void QuickTilingTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("MaximizeMode"); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // set custom config which disables the Outline KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup group = config->group("Outline"); group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); group.sync(); kwinApp()->setConfig(config); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); } void QuickTilingTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); m_connection = Test::waylandConnection(); m_compositor = Test::waylandCompositor(); m_shell = Test::waylandShell(); screens()->setCurrent(0); } void QuickTilingTest::cleanup() { Test::destroyWaylandConnection(); } void QuickTilingTest::testQuickTiling_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); QTest::addColumn("secondScreen"); #define FLAG(name) AbstractClient::QuickTileMode(AbstractClient::QuickTile##name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024) << QRect(1280, 0, 640, 1024); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512) << QRect(1280, 0, 1280, 512); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024) << QRect(1920, 0, 640, 1024); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512) << QRect(1280, 512, 1280, 512); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512) << QRect(1280, 0, 640, 512); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512) << QRect(1920, 0, 640, 512); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512) << QRect(1280, 512, 640, 512); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512) << QRect(1920, 512, 640, 512); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024); #undef FLAG } void QuickTilingTest::testQuickTiling() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QFETCH(AbstractClient::QuickTileMode, mode); QFETCH(QRect, expectedGeometry); c->setQuickTileMode(mode, true); QCOMPARE(quickTileChangedSpy.count(), 1); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), mode); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), expectedGeometry.size()); // attach a new image Test::render(surface.data(), expectedGeometry.size(), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(c->geometry(), expectedGeometry); // send window to other screen QCOMPARE(c->screen(), 0); c->sendToScreen(1); QCOMPARE(c->screen(), 1); // quick tile should not be changed QCOMPARE(c->quickTileMode(), mode); QTEST(c->geometry(), "secondScreen"); } void QuickTilingTest::testQuickMaximizing_data() { QTest::addColumn("mode"); #define FLAG(name) AbstractClient::QuickTileMode(AbstractClient::QuickTile##name) QTest::newRow("maximize") << FLAG(Maximize); QTest::newRow("none") << FLAG(None); #undef FLAG } void QuickTilingTest::testQuickMaximizing() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy maximizeChangedSpy1(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,MaximizeMode))); QVERIFY(maximizeChangedSpy1.isValid()); QSignalSpy maximizeChangedSpy2(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,bool,bool))); QVERIFY(maximizeChangedSpy2.isValid()); c->setQuickTileMode(AbstractClient::QuickTileMaximize, true); QCOMPARE(quickTileChangedSpy.count(), 1); QCOMPARE(maximizeChangedSpy1.count(), 1); QCOMPARE(maximizeChangedSpy1.first().first().value(), c); QCOMPARE(maximizeChangedSpy1.first().last().value(), MaximizeFull); QCOMPARE(maximizeChangedSpy2.count(), 1); QCOMPARE(maximizeChangedSpy2.first().first().value(), c); QCOMPARE(maximizeChangedSpy2.first().at(1).toBool(), true); QCOMPARE(maximizeChangedSpy2.first().at(2).toBool(), true); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileMaximize); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), QSize(1280, 1024)); // attach a new image Test::render(surface.data(), QSize(1280, 1024), Qt::red); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 2); QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // go back to quick tile none QFETCH(AbstractClient::QuickTileMode, mode); c->setQuickTileMode(mode, true); QCOMPARE(quickTileChangedSpy.count(), 2); QCOMPARE(maximizeChangedSpy1.count(), 2); QCOMPARE(maximizeChangedSpy1.last().first().value(), c); QCOMPARE(maximizeChangedSpy1.last().last().value(), MaximizeRestore); QCOMPARE(maximizeChangedSpy2.count(), 2); QCOMPARE(maximizeChangedSpy2.last().first().value(), c); QCOMPARE(maximizeChangedSpy2.last().at(1).toBool(), false); QCOMPARE(maximizeChangedSpy2.last().at(2).toBool(), false); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); // geometry not yet changed QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 2); QCOMPARE(sizeChangeSpy.last().first().toSize(), QSize(100, 50)); // render again Test::render(surface.data(), QSize(100, 50), Qt::yellow); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 4); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); } void QuickTilingTest::testQuickTilingKeyboardMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileRight); QTest::newRow("right") << QPoint(2559, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileRight); QTest::newRow("bottomRight") << QPoint(2559, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileRight); QTest::newRow("bottomLeft") << QPoint(0, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileLeft); QTest::newRow("Left") << QPoint(0, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileLeft); QTest::newRow("topLeft") << QPoint(0, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileLeft); } void QuickTilingTest::testQuickTilingKeyboardMove() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); while (Cursor::pos().x() > targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_LEFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFT, timestamp++); } while (Cursor::pos().x() < targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); } while (Cursor::pos().y() < targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_DOWN, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_DOWN, timestamp++); } while (Cursor::pos().y() > targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_UP, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_UP, timestamp++); } kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_ENTER, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_ENTER, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->getMovingClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); } void QuickTilingTest::testQuickTilingPointerMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileRight); QTest::newRow("right") << QPoint(2559, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileRight); QTest::newRow("bottomRight") << QPoint(2559, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileRight); QTest::newRow("bottomLeft") << QPoint(0, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileLeft); QTest::newRow("Left") << QPoint(0, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileLeft); QTest::newRow("topLeft") << QPoint(0, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileLeft); } void QuickTilingTest::testQuickTilingPointerMove() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(targetPos, timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->getMovingClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); } } WAYLANDTEST_MAIN(KWin::QuickTilingTest) #include "quick_tiling_test.moc" diff --git a/autotests/integration/scene_qpainter_test.cpp b/autotests/integration/scene_qpainter_test.cpp index 73158b3f2..680c8864b 100644 --- a/autotests/integration/scene_qpainter_test.cpp +++ b/autotests/integration/scene_qpainter_test.cpp @@ -1,245 +1,245 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "composite.h" #include "effectloader.h" #include "cursor.h" #include "platform.h" #include "scene_qpainter.h" #include "shell_client.h" #include "wayland_server.h" #include "effect_builtins.h" #include #include #include #include #include #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_kwin_scene_qpainter-0"); class SceneQPainterTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanup(); void testStartFrame(); void testCursorMoving(); void testWindow_data(); void testWindow(); void testCompositorRestart_data(); void testCompositorRestart(); }; void SceneQPainterTest::cleanup() { Test::destroyWaylandConnection(); } void SceneQPainterTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // disable all effects - we don't want to have it interact with the rendering auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup plugins(config, QStringLiteral("Plugins")); ScriptedEffectLoader loader; const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); for (QString name : builtinNames) { plugins.writeEntry(name + QStringLiteral("Enabled"), false); } config->sync(); kwinApp()->setConfig(config); qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QVERIFY(Compositor::self()); } void SceneQPainterTest::testStartFrame() { // this test verifies that the initial rendering is correct Compositor::self()->addRepaintFull(); auto scene = qobject_cast(Compositor::self()->scene()); QVERIFY(scene); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); QVERIFY(frameRenderedSpy.wait()); // now let's render a reference image for comparison QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); referenceImage.fill(Qt::black); QPainter p(&referenceImage); const QImage cursorImage = kwinApp()->platform()->softwareCursor(); QVERIFY(!cursorImage.isNull()); p.drawImage(KWin::Cursor::pos() - kwinApp()->platform()->softwareCursorHotspot(), cursorImage); QCOMPARE(referenceImage, *scene->backend()->buffer()); } void SceneQPainterTest::testCursorMoving() { // this test verifies that rendering is correct also after moving the cursor a few times auto scene = qobject_cast(Compositor::self()->scene()); QVERIFY(scene); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); KWin::Cursor::setPos(0, 0); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(10, 0); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(10, 12); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(12, 14); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(50, 60); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(45, 45); QVERIFY(frameRenderedSpy.wait()); // now let's render a reference image for comparison QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); referenceImage.fill(Qt::black); QPainter p(&referenceImage); const QImage cursorImage = kwinApp()->platform()->softwareCursor(); QVERIFY(!cursorImage.isNull()); p.drawImage(QPoint(45, 45) - kwinApp()->platform()->softwareCursorHotspot(), cursorImage); QCOMPARE(referenceImage, *scene->backend()->buffer()); } void SceneQPainterTest::testWindow_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void SceneQPainterTest::testWindow() { KWin::Cursor::setPos(45, 45); // this test verifies that a window is rendered correctly using namespace KWayland::Client; - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandPointer()); QScopedPointer s(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer ss(Test::createShellSurface(type, s.data())); QScopedPointer p(Test::waylandSeat()->createPointer()); auto scene = qobject_cast(KWin::Compositor::self()->scene()); QVERIFY(scene); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); // now let's map the window QVERIFY(Test::renderAndWaitForShown(s.data(), QSize(200, 300), Qt::blue)); // which should trigger a frame if (frameRenderedSpy.isEmpty()) { QVERIFY(frameRenderedSpy.wait()); } // we didn't set a cursor image on the surface yet, so it should be just black + window QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); referenceImage.fill(Qt::black); QPainter painter(&referenceImage); painter.fillRect(0, 0, 200, 300, Qt::blue); QCOMPARE(referenceImage, *scene->backend()->buffer()); // now let's set a cursor image QScopedPointer cs(Test::createSurface()); QVERIFY(!cs.isNull()); Test::render(cs.data(), QSize(10, 10), Qt::red); p->setCursor(cs.data(), QPoint(5, 5)); QVERIFY(frameRenderedSpy.wait()); painter.fillRect(KWin::Cursor::pos().x() - 5, KWin::Cursor::pos().y() - 5, 10, 10, Qt::red); QCOMPARE(referenceImage, *scene->backend()->buffer()); // let's move the cursor again KWin::Cursor::setPos(10, 10); QVERIFY(frameRenderedSpy.wait()); painter.fillRect(0, 0, 200, 300, Qt::blue); painter.fillRect(5, 5, 10, 10, Qt::red); QCOMPARE(referenceImage, *scene->backend()->buffer()); } void SceneQPainterTest::testCompositorRestart_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void SceneQPainterTest::testCompositorRestart() { // this test verifies that the compositor/SceneQPainter survive a restart of the compositor and still render correctly KWin::Cursor::setPos(400, 400); // first create a window using namespace KWayland::Client; - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); QScopedPointer s(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer ss(Test::createShellSurface(type, s.data())); QVERIFY(Test::renderAndWaitForShown(s.data(), QSize(200, 300), Qt::blue)); // now let's try to reinitialize the compositing scene auto oldScene = KWin::Compositor::self()->scene(); QVERIFY(oldScene); QSignalSpy sceneCreatedSpy(KWin::Compositor::self(), &KWin::Compositor::sceneCreated); QVERIFY(sceneCreatedSpy.isValid()); KWin::Compositor::self()->slotReinitialize(); if (sceneCreatedSpy.isEmpty()) { QVERIFY(sceneCreatedSpy.wait()); } QCOMPARE(sceneCreatedSpy.count(), 1); auto scene = qobject_cast(KWin::Compositor::self()->scene()); QVERIFY(scene); // this should directly trigger a frame KWin::Compositor::self()->addRepaintFull(); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); QVERIFY(frameRenderedSpy.wait()); // render reference image QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); referenceImage.fill(Qt::black); QPainter painter(&referenceImage); painter.fillRect(0, 0, 200, 300, Qt::blue); const QImage cursorImage = kwinApp()->platform()->softwareCursor(); QVERIFY(!cursorImage.isNull()); painter.drawImage(QPoint(400, 400) - kwinApp()->platform()->softwareCursorHotspot(), cursorImage); QCOMPARE(referenceImage, *scene->backend()->buffer()); } WAYLANDTEST_MAIN(SceneQPainterTest) #include "scene_qpainter_test.moc" diff --git a/autotests/integration/screen_changes_test.cpp b/autotests/integration/screen_changes_test.cpp index 46b1e33ab..bf33d6c31 100644 --- a/autotests/integration/screen_changes_test.cpp +++ b/autotests/integration/screen_changes_test.cpp @@ -1,176 +1,176 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "platform.h" #include "screens.h" #include "wayland_server.h" #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_screen_changes-0"); class ScreenChangesTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testScreenAddRemove(); }; void ScreenChangesTest::initTestCase() { QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void ScreenChangesTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(640, 512)); } void ScreenChangesTest::cleanup() { Test::destroyWaylandConnection(); } void ScreenChangesTest::testScreenAddRemove() { // this test verifies that when a new screen is added it gets synced to Wayland // first create a registry to get signals about Outputs announced/removed Registry registry; QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QSignalSpy outputAnnouncedSpy(®istry, &Registry::outputAnnounced); QVERIFY(outputAnnouncedSpy.isValid()); QSignalSpy outputRemovedSpy(®istry, &Registry::outputRemoved); QVERIFY(outputRemovedSpy.isValid()); registry.create(Test::waylandConnection()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); // should be one output QCOMPARE(screens()->count(), 1); QCOMPARE(outputAnnouncedSpy.count(), 1); const quint32 firstOutputId = outputAnnouncedSpy.first().first().value(); QVERIFY(firstOutputId != 0u); outputAnnouncedSpy.clear(); // let's announce a new output QSignalSpy screensChangedSpy(screens(), &Screens::changed); QVERIFY(screensChangedSpy.isValid()); const QVector geometries{QRect(0, 0, 1280, 1024), QRect(1280, 0, 1280, 1024)}; QMetaObject::invokeMethod(kwinApp()->platform(), "outputGeometriesChanged", Qt::DirectConnection, Q_ARG(QVector, geometries)); QVERIFY(screensChangedSpy.wait()); QCOMPARE(screensChangedSpy.count(), 1); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), geometries.at(0)); QCOMPARE(screens()->geometry(1), geometries.at(1)); // this should result in it getting announced, two new outputs are added... QVERIFY(outputAnnouncedSpy.wait()); if (outputAnnouncedSpy.count() < 2) { QVERIFY(outputAnnouncedSpy.wait()); } QCOMPARE(outputAnnouncedSpy.count(), 2); // ... and afterward the previous output gets removed if (outputRemovedSpy.isEmpty()) { QVERIFY(outputRemovedSpy.wait()); } QCOMPARE(outputRemovedSpy.count(), 1); QCOMPARE(outputRemovedSpy.first().first().value(), firstOutputId); // let's wait a little bit to ensure we don't get more events QTest::qWait(100); QCOMPARE(outputAnnouncedSpy.count(), 2); QCOMPARE(outputRemovedSpy.count(), 1); // let's create the output objects to ensure they are correct QScopedPointer o1(registry.createOutput(outputAnnouncedSpy.first().first().value(), outputAnnouncedSpy.first().last().value())); QVERIFY(o1->isValid()); QSignalSpy o1ChangedSpy(o1.data(), &Output::changed); QVERIFY(o1ChangedSpy.isValid()); QVERIFY(o1ChangedSpy.wait()); QCOMPARE(o1->geometry(), geometries.at(0)); QScopedPointer o2(registry.createOutput(outputAnnouncedSpy.last().first().value(), outputAnnouncedSpy.last().last().value())); QVERIFY(o2->isValid()); QSignalSpy o2ChangedSpy(o2.data(), &Output::changed); QVERIFY(o2ChangedSpy.isValid()); QVERIFY(o2ChangedSpy.wait()); QCOMPARE(o2->geometry(), geometries.at(1)); // now let's try to remove one output again outputAnnouncedSpy.clear(); outputRemovedSpy.clear(); screensChangedSpy.clear(); QSignalSpy o1RemovedSpy(o1.data(), &Output::removed); QVERIFY(o1RemovedSpy.isValid()); QSignalSpy o2RemovedSpy(o2.data(), &Output::removed); QVERIFY(o2RemovedSpy.isValid()); const QVector geometries2{QRect(0, 0, 1280, 1024)}; QMetaObject::invokeMethod(kwinApp()->platform(), "outputGeometriesChanged", Qt::DirectConnection, Q_ARG(QVector, geometries2)); QVERIFY(screensChangedSpy.wait()); QCOMPARE(screensChangedSpy.count(), 1); QCOMPARE(screens()->count(), 1); QCOMPARE(screens()->geometry(0), geometries2.at(0)); QVERIFY(outputAnnouncedSpy.wait()); QCOMPARE(outputAnnouncedSpy.count(), 1); if (o1RemovedSpy.isEmpty()) { QVERIFY(o1RemovedSpy.wait()); } if (o2RemovedSpy.isEmpty()) { QVERIFY(o2RemovedSpy.wait()); } // now wait a bit to ensure we don't get more events QTest::qWait(100); QCOMPARE(outputAnnouncedSpy.count(), 1); QCOMPARE(o1RemovedSpy.count(), 1); QCOMPARE(o2RemovedSpy.count(), 1); QCOMPARE(outputRemovedSpy.count(), 2); } WAYLANDTEST_MAIN(ScreenChangesTest) #include "screen_changes_test.moc" diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp index db480c3b7..251372812 100644 --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -1,695 +1,695 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "effects.h" #include "platform.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include // system #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_shell_client-0"); class TestShellClient : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMapUnmapMap_data(); void testMapUnmapMap(); void testDesktopPresenceChanged(); void testTransientPositionAfterRemap(); void testMinimizeActiveWindow_data(); void testMinimizeActiveWindow(); void testFullscreen_data(); void testFullscreen(); void testMaximizedToFullscreen_data(); void testMaximizedToFullscreen(); void testWindowOpensLargerThanScreen_data(); void testWindowOpensLargerThanScreen(); void testHidden_data(); void testHidden(); void testDesktopFileName(); void testCaptionSimplified(); void testKillWindow_data(); void testKillWindow(); }; void TestShellClient::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TestShellClient::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Decoration)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(1280, 512)); } void TestShellClient::cleanup() { Test::destroyWaylandConnection(); } void TestShellClient::testMapUnmapMap_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void TestShellClient::testMapUnmapMap() { // this test verifies that mapping a previously mapped window works correctly QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QSignalSpy effectsWindowShownSpy(effects, &EffectsHandler::windowShown); QVERIFY(effectsWindowShownSpy.isValid()); QSignalSpy effectsWindowHiddenSpy(effects, &EffectsHandler::windowHidden); QVERIFY(effectsWindowHiddenSpy.isValid()); QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); // now let's render Test::render(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(clientAddedSpy.isEmpty()); QVERIFY(clientAddedSpy.wait()); auto client = clientAddedSpy.first().first().value(); QVERIFY(client); QVERIFY(client->isShown(true)); QCOMPARE(client->isHiddenInternal(), false); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->depth(), 32); QVERIFY(client->hasAlpha()); QCOMPARE(workspace()->activeClient(), client); QVERIFY(effectsWindowShownSpy.isEmpty()); QVERIFY(client->isMaximizable()); QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QVERIFY(client->isResizable()); QVERIFY(client->property("maximizable").toBool()); QVERIFY(client->property("moveable").toBool()); QVERIFY(client->property("moveableAcrossScreens").toBool()); QVERIFY(client->property("resizeable").toBool()); // now unmap QSignalSpy hiddenSpy(client, &ShellClient::windowHidden); QVERIFY(hiddenSpy.isValid()); QSignalSpy windowClosedSpy(client, &ShellClient::windowClosed); QVERIFY(windowClosedSpy.isValid()); surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(hiddenSpy.wait()); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), true); QVERIFY(windowClosedSpy.isEmpty()); QVERIFY(!workspace()->activeClient()); QCOMPARE(effectsWindowHiddenSpy.count(), 1); QCOMPARE(effectsWindowHiddenSpy.first().first().value(), client->effectWindow()); QSignalSpy windowShownSpy(client, &ShellClient::windowShown); QVERIFY(windowShownSpy.isValid()); Test::render(surface.data(), QSize(100, 50), Qt::blue, QImage::Format_RGB32); QCOMPARE(clientAddedSpy.count(), 1); QVERIFY(windowShownSpy.wait()); QCOMPARE(windowShownSpy.count(), 1); QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), false); QCOMPARE(client->depth(), 24); QVERIFY(!client->hasAlpha()); QCOMPARE(workspace()->activeClient(), client); QCOMPARE(effectsWindowShownSpy.count(), 1); QCOMPARE(effectsWindowShownSpy.first().first().value(), client->effectWindow()); // let's unmap again surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(hiddenSpy.wait()); QCOMPARE(hiddenSpy.count(), 2); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), true); QVERIFY(windowClosedSpy.isEmpty()); QCOMPARE(effectsWindowHiddenSpy.count(), 2); QCOMPARE(effectsWindowHiddenSpy.last().first().value(), client->effectWindow()); shellSurface.reset(); surface.reset(); QVERIFY(windowClosedSpy.wait()); QCOMPARE(windowClosedSpy.count(), 1); QCOMPARE(effectsWindowHiddenSpy.count(), 2); } void TestShellClient::testDesktopPresenceChanged() { // this test verifies that the desktop presence changed signals are properly emitted QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktop(), 1); effects->setNumberOfDesktops(4); QSignalSpy desktopPresenceChangedClientSpy(c, &ShellClient::desktopPresenceChanged); QVERIFY(desktopPresenceChangedClientSpy.isValid()); QSignalSpy desktopPresenceChangedWorkspaceSpy(workspace(), &Workspace::desktopPresenceChanged); QVERIFY(desktopPresenceChangedWorkspaceSpy.isValid()); QSignalSpy desktopPresenceChangedEffectsSpy(effects, &EffectsHandler::desktopPresenceChanged); QVERIFY(desktopPresenceChangedEffectsSpy.isValid()); // let's change the desktop workspace()->sendClientToDesktop(c, 2, false); QCOMPARE(c->desktop(), 2); QCOMPARE(desktopPresenceChangedClientSpy.count(), 1); QCOMPARE(desktopPresenceChangedWorkspaceSpy.count(), 1); // effects is delayed by one cycle QCOMPARE(desktopPresenceChangedEffectsSpy.count(), 0); QVERIFY(desktopPresenceChangedEffectsSpy.wait()); QCOMPARE(desktopPresenceChangedEffectsSpy.count(), 1); // verify the arguments QCOMPARE(desktopPresenceChangedClientSpy.first().at(0).value(), c); QCOMPARE(desktopPresenceChangedClientSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(0).value(), c); QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(0).value(), c->effectWindow()); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(2).toInt(), 2); } void TestShellClient::testTransientPositionAfterRemap() { // this test simulates the situation that a transient window gets reused and the parent window // moved between the two usages QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); // create the Transient window QScopedPointer transientSurface(Test::createSurface()); QScopedPointer transientShellSurface(Test::createShellSurface(transientSurface.data())); transientShellSurface->setTransient(surface.data(), QPoint(5, 10)); auto transient = Test::renderAndWaitForShown(transientSurface.data(), QSize(50, 40), Qt::blue); QVERIFY(transient); QCOMPARE(transient->geometry(), QRect(c->geometry().topLeft() + QPoint(5, 10), QSize(50, 40))); // unmap the transient QSignalSpy windowHiddenSpy(transient, &ShellClient::windowHidden); QVERIFY(windowHiddenSpy.isValid()); transientSurface->attachBuffer(Buffer::Ptr()); transientSurface->commit(Surface::CommitFlag::None); QVERIFY(windowHiddenSpy.wait()); // now move the parent surface c->setGeometry(c->geometry().translated(5, 10)); // now map the transient again QSignalSpy windowShownSpy(transient, &ShellClient::windowShown); QVERIFY(windowShownSpy.isValid()); Test::render(transientSurface.data(), QSize(50, 40), Qt::blue); QVERIFY(windowShownSpy.wait()); QCOMPARE(transient->geometry(), QRect(c->geometry().topLeft() + QPoint(5, 10), QSize(50, 40))); } void TestShellClient::testMinimizeActiveWindow_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void TestShellClient::testMinimizeActiveWindow() { // this test verifies that when minimizing the active window it gets deactivated QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); workspace()->slotWindowMinimize(); QVERIFY(!c->isShown(true)); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(!c->isActive()); QVERIFY(!workspace()->activeClient()); QVERIFY(c->isMinimized()); // unminimize again c->unminimize(); QVERIFY(!c->isMinimized()); QVERIFY(c->isActive()); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); QCOMPARE(workspace()->activeClient(), c); } void TestShellClient::testFullscreen_data() { QTest::addColumn("type"); QTest::addColumn("decoMode"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; } void TestShellClient::testFullscreen() { // this test verifies that a window can be properly fullscreened QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->layer(), NormalLayer); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QCOMPARE(c->sizeForClientSize(c->clientSize()), c->geometry().size()); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); // fullscreen the window switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setFullscreen(); break; case Test::ShellSurfaceType::XdgShellV5: qobject_cast(shellSurface.data())->setFullscreen(true); break; default: Q_UNREACHABLE(); break; } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); QCOMPARE(sizeChangeRequestedSpy.first().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QVERIFY(geometryChangedSpy.isEmpty()); // render at the new size Test::render(surface.data(), sizeChangeRequestedSpy.first().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 1); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.first().first().toSize())); QCOMPARE(c->layer(), ActiveLayer); // swap back to normal switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setToplevel(); break; case Test::ShellSurfaceType::XdgShellV5: qobject_cast(shellSurface.data())->setFullscreen(false); break; default: Q_UNREACHABLE(); break; } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->layer(), NormalLayer); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } void TestShellClient::testMaximizedToFullscreen_data() { QTest::addColumn("type"); QTest::addColumn("decoMode"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; } void TestShellClient::testMaximizedToFullscreen() { // this test verifies that a window can be properly fullscreened after maximizing QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); // change to maximize switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setMaximized(); break; case Test::ShellSurfaceType::XdgShellV5: qobject_cast(shellSurface.data())->setMaximized(true); break; default: Q_UNREACHABLE(); break; } QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(geometryChangedSpy.isEmpty(), false); geometryChangedSpy.clear(); // fullscreen the window switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setFullscreen(); break; case Test::ShellSurfaceType::XdgShellV5: qobject_cast(shellSurface.data())->setFullscreen(true); break; default: Q_UNREACHABLE(); break; } QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QVERIFY(geometryChangedSpy.isEmpty()); // render at the new size Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 1); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.last().first().toSize())); sizeChangeRequestedSpy.clear(); // swap back to normal switch (type) { case Test::ShellSurfaceType::WlShell: qobject_cast(shellSurface.data())->setToplevel(); break; case Test::ShellSurfaceType::XdgShellV5: qobject_cast(shellSurface.data())->setFullscreen(false); break; default: Q_UNREACHABLE(); break; } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); QEXPECT_FAIL("wlShell - deco", "With decoration incorrect geometry requested", Continue); QEXPECT_FAIL("xdgShellV5 - deco", "With decoration incorrect geometry requested", Continue); QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } void TestShellClient::testWindowOpensLargerThanScreen_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void TestShellClient::testWindowOpensLargerThanScreen() { // this test creates a window which is as large as the screen, but is decorated // the window should get resized to fit into the screen, BUG: 366632 QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), ServerSideDecoration::Mode::Server); auto c = Test::renderAndWaitForShown(surface.data(), screens()->size(0), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->clientSize(), screens()->size(0)); QVERIFY(c->isDecorated()); QEXPECT_FAIL("", "BUG 366632", Continue); QVERIFY(sizeChangeRequestedSpy.wait()); } void TestShellClient::testHidden_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void TestShellClient::testHidden() { // this test verifies that when hiding window it doesn't get shown QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); c->hideClient(true); QVERIFY(!c->isShown(true)); QVERIFY(!c->isActive()); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); // unhide again c->hideClient(false); QVERIFY(c->isShown(true)); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); //QCOMPARE(workspace()->activeClient(), c); } void TestShellClient::testDesktopFileName() { QIcon::setThemeName(QStringLiteral("breeze")); // this test verifies that desktop file name is passed correctly to the window QScopedPointer surface(Test::createSurface()); // only xdg-shell as ShellSurface misses the setter QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); shellSurface->setAppId(QByteArrayLiteral("org.kde.foo")); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.foo")); // the desktop file does not exist, so icon should be generic Wayland QCOMPARE(c->icon().name(), QStringLiteral("wayland")); QSignalSpy desktopFileNameChangedSpy(c, &AbstractClient::desktopFileNameChanged); QVERIFY(desktopFileNameChangedSpy.isValid()); QSignalSpy iconChangedSpy(c, &ShellClient::iconChanged); QVERIFY(iconChangedSpy.isValid()); shellSurface->setAppId(QByteArrayLiteral("org.kde.bar")); QVERIFY(desktopFileNameChangedSpy.wait()); QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.bar")); // icon should still be wayland QCOMPARE(c->icon().name(), QStringLiteral("wayland")); QVERIFY(iconChangedSpy.isEmpty()); const QString dfPath = QFINDTESTDATA("data/example.desktop"); shellSurface->setAppId(dfPath.toUtf8()); QVERIFY(desktopFileNameChangedSpy.wait()); QCOMPARE(iconChangedSpy.count(), 1); QCOMPARE(QString::fromUtf8(c->desktopFileName()), dfPath); QCOMPARE(c->icon().name(), QStringLiteral("kwin")); } void TestShellClient::testCaptionSimplified() { // this test verifies that caption is properly trimmed // see BUG 323798 comment #12 QScopedPointer surface(Test::createSurface()); // only done for xdg-shell as ShellSurface misses the setter QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); const QString origTitle = QString::fromUtf8(QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox")); shellSurface->setTitle(origTitle); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->caption() != origTitle); QCOMPARE(c->caption(), origTitle.simplified()); } void TestShellClient::testKillWindow_data() { QTest::addColumn("socketMode"); QTest::newRow("display") << false; QTest::newRow("socket") << true; } void TestShellClient::testKillWindow() { // this test verifies that killWindow properly terminates a process // for this an external binary is launched const QString kill = QFINDTESTDATA(QStringLiteral("helper/kill")); QVERIFY(!kill.isEmpty()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); QScopedPointer process(new QProcess); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QFETCH(bool, socketMode); if (socketMode) { int sx[2]; QVERIFY(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) >= 0); waylandServer()->display()->createClient(sx[0]); int socket = dup(sx[1]); QVERIFY(socket != -1); env.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); env.remove("WAYLAND_DISPLAY"); } else { env.insert("WAYLAND_DISPLAY", s_socketName); } process->setProcessEnvironment(env); process->setProcessChannelMode(QProcess::ForwardedChannels); process->setProgram(kill); process->start(); QVERIFY(process->waitForStarted()); AbstractClient *killClient = nullptr; QVERIFY(shellClientAddedSpy.wait()); killClient = shellClientAddedSpy.first().first().value(); QVERIFY(killClient); QSignalSpy finishedSpy(process.data(), static_cast(&QProcess::finished)); QVERIFY(finishedSpy.isValid()); killClient->killWindow(); QVERIFY(finishedSpy.wait()); QVERIFY(!finishedSpy.isEmpty()); } WAYLANDTEST_MAIN(TestShellClient) #include "shell_client_test.moc" diff --git a/autotests/integration/start_test.cpp b/autotests/integration/start_test.cpp index a0503d829..5e4befef8 100644 --- a/autotests/integration/start_test.cpp +++ b/autotests/integration/start_test.cpp @@ -1,163 +1,163 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_start_test-0"); class StartTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanup(); void testScreens(); void testNoWindowsAtStart(); void testCreateWindow(); void testHideShowCursor(); }; void StartTest::initTestCase() { QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); } void StartTest::cleanup() { Test::destroyWaylandConnection(); } void StartTest::testScreens() { QCOMPARE(screens()->count(), 1); QCOMPARE(screens()->size(), QSize(1280, 1024)); QCOMPARE(screens()->geometry(), QRect(0, 0, 1280, 1024)); } void StartTest::testNoWindowsAtStart() { QVERIFY(workspace()->clientList().isEmpty()); QVERIFY(workspace()->desktopList().isEmpty()); QVERIFY(workspace()->allClientList().isEmpty()); QVERIFY(workspace()->deletedList().isEmpty()); QVERIFY(workspace()->unmanagedList().isEmpty()); QVERIFY(waylandServer()->clients().isEmpty()); } void StartTest::testCreateWindow() { // first we need to connect to the server using namespace KWayland::Client; - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); QSignalSpy shellClientRemovedSpy(waylandServer(), &WaylandServer::shellClientRemoved); QVERIFY(shellClientRemovedSpy.isValid()); { QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QSignalSpy surfaceRenderedSpy(surface.data(), &Surface::frameRendered); QVERIFY(surfaceRenderedSpy.isValid()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); Test::flushWaylandConnection(); QVERIFY(waylandServer()->clients().isEmpty()); // now dispatch should give us the client waylandServer()->dispatch(); QCOMPARE(waylandServer()->clients().count(), 1); // but still not yet in workspace QVERIFY(workspace()->allClientList().isEmpty()); // icon geometry accesses windowManagementInterface which only exists after window became visible // verify that accessing doesnt't crash QVERIFY(waylandServer()->clients().first()->iconGeometry().isNull()); // let's render Test::render(surface.data(), QSize(100, 50), Qt::blue); surface->commit(); Test::flushWaylandConnection(); QVERIFY(shellClientAddedSpy.wait()); QCOMPARE(workspace()->allClientList().count(), 1); QCOMPARE(workspace()->allClientList().first(), waylandServer()->clients().first()); QVERIFY(workspace()->activeClient()); QCOMPARE(workspace()->activeClient()->pos(), QPoint(0, 0)); QCOMPARE(workspace()->activeClient()->size(), QSize(100, 50)); QCOMPARE(workspace()->activeClient()->geometry(), QRect(0, 0, 100, 50)); // and kwin will render it QVERIFY(surfaceRenderedSpy.wait()); } // this should tear down everything again QVERIFY(shellClientRemovedSpy.wait()); QVERIFY(waylandServer()->clients().isEmpty()); } void StartTest::testHideShowCursor() { QCOMPARE(kwinApp()->platform()->isCursorHidden(), false); kwinApp()->platform()->hideCursor(); QCOMPARE(kwinApp()->platform()->isCursorHidden(), true); kwinApp()->platform()->showCursor(); QCOMPARE(kwinApp()->platform()->isCursorHidden(), false); kwinApp()->platform()->hideCursor(); QCOMPARE(kwinApp()->platform()->isCursorHidden(), true); kwinApp()->platform()->hideCursor(); kwinApp()->platform()->hideCursor(); kwinApp()->platform()->hideCursor(); QCOMPARE(kwinApp()->platform()->isCursorHidden(), true); kwinApp()->platform()->showCursor(); QCOMPARE(kwinApp()->platform()->isCursorHidden(), true); kwinApp()->platform()->showCursor(); QCOMPARE(kwinApp()->platform()->isCursorHidden(), true); kwinApp()->platform()->showCursor(); QCOMPARE(kwinApp()->platform()->isCursorHidden(), true); kwinApp()->platform()->showCursor(); QCOMPARE(kwinApp()->platform()->isCursorHidden(), false); } } WAYLANDTEST_MAIN(KWin::StartTest) #include "start_test.moc" diff --git a/autotests/integration/struts_test.cpp b/autotests/integration/struts_test.cpp index 1f4b22c00..92e0672be 100644 --- a/autotests/integration/struts_test.cpp +++ b/autotests/integration/struts_test.cpp @@ -1,814 +1,814 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "client.h" #include "cursor.h" #include "deleted.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_struts-0"); class StrutsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testWaylandStruts_data(); void testWaylandStruts(); void testMoveWaylandPanel(); void testWaylandMobilePanel(); void testX11Struts_data(); void testX11Struts(); void test363804(); void testLeftScreenSmallerBottomAligned(); private: KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::PlasmaShell *m_plasmaShell = nullptr; }; void StrutsTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void StrutsTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::PlasmaShell)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell)); m_compositor = Test::waylandCompositor(); m_plasmaShell = Test::waylandPlasmaShell(); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); QVERIFY(waylandServer()->clients().isEmpty()); } void StrutsTest::cleanup() { Test::destroyWaylandConnection(); } void StrutsTest::testWaylandStruts_data() { QTest::addColumn>("windowGeometries"); QTest::addColumn("screen0Maximized"); QTest::addColumn("screen1Maximized"); QTest::addColumn("workArea"); QTest::newRow("bottom/0") << QVector{QRect(0, 992, 1280, 32)} << QRect(0, 0, 1280, 992) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 992); QTest::newRow("bottom/1") << QVector{QRect(1280, 992, 1280, 32)} << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 992) << QRect(0, 0, 2560, 992); QTest::newRow("top/0") << QVector{QRect(0, 0, 1280, 32)} << QRect(0, 32, 1280, 992) << QRect(1280, 0, 1280, 1024) << QRect(0, 32, 2560, 992); QTest::newRow("top/1") << QVector{QRect(1280, 0, 1280, 32)} << QRect(0, 0, 1280, 1024) << QRect(1280, 32, 1280, 992) << QRect(0, 32, 2560, 992); QTest::newRow("left/0") << QVector{QRect(0, 0, 32, 1024)} << QRect(32, 0, 1248, 1024) << QRect(1280, 0, 1280, 1024) << QRect(32, 0, 2528, 1024); QTest::newRow("left/1") << QVector{QRect(1280, 0, 32, 1024)} << QRect(0, 0, 1280, 1024) << QRect(1312, 0, 1248, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("right/0") << QVector{QRect(1248, 0, 32, 1024)} << QRect(0, 0, 1248, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("right/1") << QVector{QRect(2528, 0, 32, 1024)} << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1248, 1024) << QRect(0, 0, 2528, 1024); // same with partial panels not covering the whole area QTest::newRow("part bottom/0") << QVector{QRect(100, 992, 1080, 32)} << QRect(0, 0, 1280, 992) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 992); QTest::newRow("part bottom/1") << QVector{QRect(1380, 992, 1080, 32)} << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 992) << QRect(0, 0, 2560, 992); QTest::newRow("part top/0") << QVector{QRect(100, 0, 1080, 32)} << QRect(0, 32, 1280, 992) << QRect(1280, 0, 1280, 1024) << QRect(0, 32, 2560, 992); QTest::newRow("part top/1") << QVector{QRect(1380, 0, 1080, 32)} << QRect(0, 0, 1280, 1024) << QRect(1280, 32, 1280, 992) << QRect(0, 32, 2560, 992); QTest::newRow("part left/0") << QVector{QRect(0, 100, 32, 824)} << QRect(32, 0, 1248, 1024) << QRect(1280, 0, 1280, 1024) << QRect(32, 0, 2528, 1024); QTest::newRow("part left/1") << QVector{QRect(1280, 100, 32, 824)} << QRect(0, 0, 1280, 1024) << QRect(1312, 0, 1248, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("part right/0") << QVector{QRect(1248, 100, 32, 824)} << QRect(0, 0, 1248, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("part right/1") << QVector{QRect(2528, 100, 32, 824)} << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1248, 1024) << QRect(0, 0, 2528, 1024); // multiple panels QTest::newRow("two bottom panels") << QVector{QRect(100, 992, 1080, 32), QRect(1380, 984, 1080, 40)} << QRect(0, 0, 1280, 992) << QRect(1280, 0, 1280, 984) << QRect(0, 0, 2560, 984); QTest::newRow("two left panels") << QVector{QRect(0, 10, 32, 390), QRect(0, 450, 40, 100)} << QRect(40, 0, 1240, 1024) << QRect(1280, 0, 1280, 1024) << QRect(40, 0, 2520, 1024); } void StrutsTest::testWaylandStruts() { // this test verifies that struts on Wayland panels are handled correctly using namespace KWayland::Client; // no, struts yet QVERIFY(waylandServer()->clients().isEmpty()); // first screen QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); // second screen QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); // combined QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 2560, 1024)); QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024)); QFETCH(QVector, windowGeometries); // create the panels QHash clients; for (auto it = windowGeometries.constBegin(), end = windowGeometries.constEnd(); it != end; it++) { const QRect windowGeometry = *it; Surface *surface = Test::createSurface(m_compositor); ShellSurface *shellSurface = Test::createShellSurface(surface, surface); Q_UNUSED(shellSurface) PlasmaShellSurface *plasmaSurface = m_plasmaShell->createSurface(surface, surface); plasmaSurface->setPosition(windowGeometry.topLeft()); plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); // map the window auto c = Test::renderAndWaitForShown(surface, windowGeometry.size(), Qt::red, QImage::Format_RGB32); QVERIFY(c); QVERIFY(!c->isActive()); QCOMPARE(c->geometry(), windowGeometry); QVERIFY(c->isDock()); QVERIFY(c->hasStrut()); clients.insert(surface, c); } // some props are independent of struts - those first // screen 0 QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); // screen 1 QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); // combined QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024)); // now verify the actual updated client areas QTEST(workspace()->clientArea(PlacementArea, 0, 1), "screen0Maximized"); QTEST(workspace()->clientArea(MaximizeArea, 0, 1), "screen0Maximized"); QTEST(workspace()->clientArea(PlacementArea, 1, 1), "screen1Maximized"); QTEST(workspace()->clientArea(MaximizeArea, 1, 1), "screen1Maximized"); QTEST(workspace()->clientArea(WorkArea, 0, 1), "workArea"); // delete all surfaces for (auto it = clients.begin(); it != clients.end(); it++) { QSignalSpy destroyedSpy(it.value(), &QObject::destroyed); QVERIFY(destroyedSpy.isValid()); delete it.key(); QVERIFY(destroyedSpy.wait()); } } void StrutsTest::testMoveWaylandPanel() { // this test verifies that repositioning a Wayland panel updates the client area using namespace KWayland::Client; const QRect windowGeometry(0, 1000, 1280, 24); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); Q_UNUSED(shellSurface) QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); plasmaSurface->setPosition(windowGeometry.topLeft()); plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); // map the window auto c = Test::renderAndWaitForShown(surface.data(), windowGeometry.size(), Qt::red, QImage::Format_RGB32); QVERIFY(c); QVERIFY(!c->isActive()); QCOMPARE(c->geometry(), windowGeometry); QVERIFY(c->isDock()); QVERIFY(c->hasStrut()); QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 0, 1280, 1000)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 0, 1280, 1000)); QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 2560, 1000)); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryShapeChanged); QVERIFY(geometryChangedSpy.isValid()); plasmaSurface->setPosition(QPoint(1280, 1000)); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(c->geometry(), QRect(1280, 1000, 1280, 24)); QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1000)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1000)); QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 2560, 1000)); } void StrutsTest::testWaylandMobilePanel() { using namespace KWayland::Client; //First enable maxmizing policy KConfigGroup group = kwinApp()->config()->group("Windows"); group.writeEntry("Placement", "Maximizing"); group.sync(); workspace()->slotReconfigure(); // create first top panel const QRect windowGeometry(0, 0, 1280, 60); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); Q_UNUSED(shellSurface) QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); plasmaSurface->setPosition(windowGeometry.topLeft()); plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); // map the first panel auto c = Test::renderAndWaitForShown(surface.data(), windowGeometry.size(), Qt::red, QImage::Format_RGB32); QVERIFY(c); QVERIFY(!c->isActive()); QCOMPARE(c->geometry(), windowGeometry); QVERIFY(c->isDock()); QVERIFY(c->hasStrut()); QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 60, 1280, 964)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 60, 1280, 964)); QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 60, 2560, 964)); // create another bottom panel const QRect windowGeometry2(0, 874, 1280, 150); QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(Test::createShellSurface(surface2.data())); Q_UNUSED(shellSurface2) QScopedPointer plasmaSurface2(m_plasmaShell->createSurface(surface2.data())); plasmaSurface2->setPosition(windowGeometry2.topLeft()); plasmaSurface2->setRole(PlasmaShellSurface::Role::Panel); auto c1 = Test::renderAndWaitForShown(surface2.data(), windowGeometry2.size(), Qt::blue, QImage::Format_RGB32); QVERIFY(c1); QVERIFY(!c1->isActive()); QCOMPARE(c1->geometry(), windowGeometry2); QVERIFY(c1->isDock()); QVERIFY(c1->hasStrut()); QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 60, 1280, 814)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 60, 1280, 814)); QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 60, 2560, 814)); } void StrutsTest::testX11Struts_data() { QTest::addColumn("windowGeometry"); QTest::addColumn("leftStrut"); QTest::addColumn("rightStrut"); QTest::addColumn("topStrut"); QTest::addColumn("bottomStrut"); QTest::addColumn("leftStrutStart"); QTest::addColumn("leftStrutEnd"); QTest::addColumn("rightStrutStart"); QTest::addColumn("rightStrutEnd"); QTest::addColumn("topStrutStart"); QTest::addColumn("topStrutEnd"); QTest::addColumn("bottomStrutStart"); QTest::addColumn("bottomStrutEnd"); QTest::addColumn("screen0Maximized"); QTest::addColumn("screen1Maximized"); QTest::addColumn("workArea"); QTest::newRow("bottom panel/no strut") << QRect(0, 980, 1280, 44) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("bottom panel/strut") << QRect(0, 980, 1280, 44) << 0 << 0 << 0 << 44 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1279 << QRect(0, 0, 1280, 980) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 980); QTest::newRow("top panel/no strut") << QRect(0, 0, 1280, 44) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("top panel/strut") << QRect(0, 0, 1280, 44) << 0 << 0 << 44 << 0 << 0 << 0 << 0 << 0 << 0 << 1279 << 0 << 0 << QRect(0, 44, 1280, 980) << QRect(1280, 0, 1280, 1024) << QRect(0, 44, 2560, 980); QTest::newRow("left panel/no strut") << QRect(0, 0, 60, 1024) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("left panel/strut") << QRect(0, 0, 60, 1024) << 60 << 0 << 0 << 0 << 0 << 1023 << 0 << 0 << 0 << 0 << 0 << 0 << QRect(60, 0, 1220, 1024) << QRect(1280, 0, 1280, 1024) << QRect(60, 0, 2500, 1024); QTest::newRow("right panel/no strut") << QRect(0, 1220, 60, 1024) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("right panel/strut") << QRect(0, 1220, 60, 1024) << 0 << 1340 << 0 << 0 << 0 << 0 << 0 << 1023 << 0 << 0 << 0 << 0 << QRect(0, 0, 1220, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); // second screen QTest::newRow("bottom panel 1/no strut") << QRect(1280, 980, 1280, 44) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("bottom panel 1/strut") << QRect(1280, 980, 1280, 44) << 0 << 0 << 0 << 44 << 0 << 0 << 0 << 0 << 0 << 0 << 1280 << 2559 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 980) << QRect(0, 0, 2560, 980); QTest::newRow("top panel 1/no strut") << QRect(1280, 0, 1280, 44) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("top panel 1 /strut") << QRect(1280, 0, 1280, 44) << 0 << 0 << 44 << 0 << 0 << 0 << 0 << 0 << 1280 << 2559 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 44, 1280, 980) << QRect(0, 44, 2560, 980); QTest::newRow("left panel 1/no strut") << QRect(1280, 0, 60, 1024) << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("left panel 1/strut") << QRect(1280, 0, 60, 1024) << 1340 << 0 << 0 << 0 << 0 << 1023 << 0 << 0 << 0 << 0 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1340, 0, 1220, 1024) << QRect(0, 0, 2560, 1024); // invalid struts QTest::newRow("bottom panel/ invalid strut") << QRect(0, 980, 1280, 44) << 1280 << 0 << 0 << 44 << 980 << 1024 << 0 << 0 << 0 << 0 << 0 << 1279 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("top panel/ invalid strut") << QRect(0, 0, 1280, 44) << 1280 << 0 << 44 << 0 << 0 << 44 << 0 << 0 << 0 << 1279 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); QTest::newRow("top panel/invalid strut 2") << QRect(0, 0, 1280, 44) << 0 << 0 << 1024 << 0 << 0 << 0 << 0 << 0 << 0 << 1279 << 0 << 0 << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void StrutsTest::testX11Struts() { // this test verifies that struts are applied correctly for X11 windows // no, struts yet // first screen QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); // second screen QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); // combined QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 2560, 1024)); QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024)); // create an xcb window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); xcb_window_t w = xcb_generate_id(c.data()); QFETCH(QRect, windowGeometry); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 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, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo info(c.data(), w, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); info.setWindowType(NET::Dock); // set the extended strut QFETCH(int, leftStrut); QFETCH(int, rightStrut); QFETCH(int, topStrut); QFETCH(int, bottomStrut); QFETCH(int, leftStrutStart); QFETCH(int, leftStrutEnd); QFETCH(int, rightStrutStart); QFETCH(int, rightStrutEnd); QFETCH(int, topStrutStart); QFETCH(int, topStrutEnd); QFETCH(int, bottomStrutStart); QFETCH(int, bottomStrutEnd); NETExtendedStrut strut; strut.left_start = leftStrutStart; strut.left_end = leftStrutEnd; strut.left_width = leftStrut; strut.right_start = rightStrutStart; strut.right_end = rightStrutEnd; strut.right_width = rightStrut; strut.top_start = topStrutStart; strut.top_end = topStrutEnd; strut.top_width = topStrut; strut.bottom_start = bottomStrutStart; strut.bottom_end = bottomStrutEnd; strut.bottom_width = bottomStrut; info.setExtendedStrut(strut); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QVERIFY(!client->isDecorated()); QCOMPARE(client->windowType(), NET::Dock); QCOMPARE(client->geometry(), windowGeometry); // this should have affected the client area // some props are independent of struts - those first // screen 0 QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); // screen 1 QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); // combined QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024)); // now verify the actual updated client areas QTEST(workspace()->clientArea(PlacementArea, 0, 1), "screen0Maximized"); QTEST(workspace()->clientArea(MaximizeArea, 0, 1), "screen0Maximized"); QTEST(workspace()->clientArea(PlacementArea, 1, 1), "screen1Maximized"); QTEST(workspace()->clientArea(MaximizeArea, 1, 1), "screen1Maximized"); QTEST(workspace()->clientArea(WorkArea, 0, 1), "workArea"); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); c.reset(); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); // now struts should be removed again QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024)); // second screen QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024)); // combined QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 2560, 1024)); QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024)); } void StrutsTest::test363804() { // this test verifies the condition described in BUG 363804 // two screens in a vertical setup, aligned to right border with panel on the bottom screen const QVector geometries{QRect(0, 0, 1920, 1080), QRect(554, 1080, 1366, 768)}; QMetaObject::invokeMethod(kwinApp()->platform(), "outputGeometriesChanged", Qt::DirectConnection, Q_ARG(QVector, geometries)); QCOMPARE(screens()->geometry(0), geometries.at(0)); QCOMPARE(screens()->geometry(1), geometries.at(1)); QCOMPARE(screens()->geometry(), QRect(0, 0, 1920, 1848)); // create an xcb window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); xcb_window_t w = xcb_generate_id(c.data()); const QRect windowGeometry(554, 1812, 1366, 36); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 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, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo info(c.data(), w, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); info.setWindowType(NET::Dock); NETExtendedStrut strut; strut.left_start = 0; strut.left_end = 0; strut.left_width = 0; strut.right_start = 0; strut.right_end = 0; strut.right_width = 0; strut.top_start = 0; strut.top_end = 0; strut.top_width = 0; strut.bottom_start = 554; strut.bottom_end = 1919; strut.bottom_width = 36; info.setExtendedStrut(strut); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QVERIFY(!client->isDecorated()); QCOMPARE(client->windowType(), NET::Dock); QCOMPARE(client->geometry(), windowGeometry); // now verify the actual updated client areas QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), geometries.at(0)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), geometries.at(0)); QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(554, 1080, 1366, 732)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(554, 1080, 1366, 732)); QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 1920, 1812)); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); c.reset(); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); } void StrutsTest::testLeftScreenSmallerBottomAligned() { // this test verifies a two screen setup with the left screen smaller than the right and bottom aligned // the panel is on the top of the left screen, thus not at 0/0 // what this test in addition tests is whether a window larger than the left screen is not placed into // the dead area const QVector geometries{QRect(0, 282, 1366, 768), QRect(1366, 0, 1680, 1050)}; QMetaObject::invokeMethod(kwinApp()->platform(), "outputGeometriesChanged", Qt::DirectConnection, Q_ARG(QVector, geometries)); QCOMPARE(screens()->geometry(0), geometries.at(0)); QCOMPARE(screens()->geometry(1), geometries.at(1)); QCOMPARE(screens()->geometry(), QRect(0, 0, 3046, 1050)); // create the panel QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); xcb_window_t w = xcb_generate_id(c.data()); const QRect windowGeometry(0, 282, 1366, 24); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 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, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo info(c.data(), w, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); info.setWindowType(NET::Dock); NETExtendedStrut strut; strut.left_start = 0; strut.left_end = 0; strut.left_width = 0; strut.right_start = 0; strut.right_end = 0; strut.right_width = 0; strut.top_start = 0; strut.top_end = 1365; strut.top_width = 306; strut.bottom_start = 0; strut.bottom_end = 0; strut.bottom_width = 0; info.setExtendedStrut(strut); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QVERIFY(!client->isDecorated()); QCOMPARE(client->windowType(), NET::Dock); QCOMPARE(client->geometry(), windowGeometry); // now verify the actual updated client areas QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 306, 1366, 744)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 306, 1366, 744)); QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), geometries.at(1)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), geometries.at(1)); QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 3046, 1050)); // now create a window which is larger than screen 0 xcb_window_t w2 = xcb_generate_id(c.data()); const QRect windowGeometry2(0, 26, 1280, 774); xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w2, rootWindow(), windowGeometry2.x(), windowGeometry2.y(), windowGeometry2.width(), windowGeometry2.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_size_hints_t hints2; memset(&hints2, 0, sizeof(hints2)); xcb_icccm_size_hints_set_min_size(&hints2, 868, 431); xcb_icccm_set_wm_normal_hints(c.data(), w2, &hints2); xcb_map_window(c.data(), w2); xcb_flush(c.data()); QVERIFY(windowCreatedSpy.wait()); Client *client2 = windowCreatedSpy.last().first().value(); QVERIFY(client2); QVERIFY(client2 != client); QVERIFY(client2->isDecorated()); QCOMPARE(client2->geometry(), QRect(0, 306, 1366, 744)); QCOMPARE(client2->maximizeMode(), KWin::MaximizeFull); // destroy window again QSignalSpy normalWindowClosedSpy(client2, &Client::windowClosed); QVERIFY(normalWindowClosedSpy.isValid()); xcb_unmap_window(c.data(), w2); xcb_destroy_window(c.data(), w2); xcb_flush(c.data()); QVERIFY(normalWindowClosedSpy.wait()); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); c.reset(); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::StrutsTest) #include "struts_test.moc" diff --git a/autotests/integration/tabbox_test.cpp b/autotests/integration/tabbox_test.cpp index 5577d3bc1..627447f04 100644 --- a/autotests/integration/tabbox_test.cpp +++ b/autotests/integration/tabbox_test.cpp @@ -1,256 +1,256 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "input.h" #include "platform.h" #include "screens.h" #include "shell_client.h" #include "tabbox/tabbox.h" #include "wayland_server.h" #include "workspace.h" #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_tabbox-0"); class TabBoxTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMoveForward(); void testMoveBackward(); void testCapsLock(); }; void TabBoxTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); KSharedConfigPtr c = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); c->group("TabBox").writeEntry("ShowTabBox", false); c->sync(); kwinApp()->setConfig(c); qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); waylandServer()->initWorkspace(); } void TabBoxTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(640, 512)); } void TabBoxTest::cleanup() { Test::destroyWaylandConnection(); } void TabBoxTest::testCapsLock() { // this test verifies that Alt+tab works correctly also when Capslock is on // bug 368590 // first create three windows QScopedPointer surface1(Test::createSurface()); QScopedPointer shellSurface1(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface1.data())); auto c1 = Test::renderAndWaitForShown(surface1.data(), QSize(100, 50), Qt::blue); QVERIFY(c1); QVERIFY(c1->isActive()); QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface2.data())); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::red); QVERIFY(c2); QVERIFY(c2->isActive()); QScopedPointer surface3(Test::createSurface()); QScopedPointer shellSurface3(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface3.data())); auto c3 = Test::renderAndWaitForShown(surface3.data(), QSize(100, 50), Qt::red); QVERIFY(c3); QVERIFY(c3->isActive()); // Setup tabbox signal spies QSignalSpy tabboxAddedSpy(TabBox::TabBox::self(), &TabBox::TabBox::tabBoxAdded); QVERIFY(tabboxAddedSpy.isValid()); QSignalSpy tabboxClosedSpy(TabBox::TabBox::self(), &TabBox::TabBox::tabBoxClosed); QVERIFY(tabboxClosedSpy.isValid()); // enable capslock quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); // press alt+tab kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::AltModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_TAB, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_TAB, timestamp++); QVERIFY(tabboxAddedSpy.wait()); QVERIFY(TabBox::TabBox::self()->isGrabbed()); // release alt kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); QCOMPARE(tabboxClosedSpy.count(), 1); QCOMPARE(TabBox::TabBox::self()->isGrabbed(), false); // release caps lock kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::NoModifier); QCOMPARE(tabboxClosedSpy.count(), 1); QCOMPARE(TabBox::TabBox::self()->isGrabbed(), false); QCOMPARE(workspace()->activeClient(), c2); surface3.reset(); QVERIFY(Test::waitForWindowDestroyed(c3)); surface2.reset(); QVERIFY(Test::waitForWindowDestroyed(c2)); surface1.reset(); QVERIFY(Test::waitForWindowDestroyed(c1)); } void TabBoxTest::testMoveForward() { // this test verifies that Alt+tab works correctly moving forward // first create three windows QScopedPointer surface1(Test::createSurface()); QScopedPointer shellSurface1(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface1.data())); auto c1 = Test::renderAndWaitForShown(surface1.data(), QSize(100, 50), Qt::blue); QVERIFY(c1); QVERIFY(c1->isActive()); QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface2.data())); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::red); QVERIFY(c2); QVERIFY(c2->isActive()); QScopedPointer surface3(Test::createSurface()); QScopedPointer shellSurface3(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface3.data())); auto c3 = Test::renderAndWaitForShown(surface3.data(), QSize(100, 50), Qt::red); QVERIFY(c3); QVERIFY(c3->isActive()); // Setup tabbox signal spies QSignalSpy tabboxAddedSpy(TabBox::TabBox::self(), &TabBox::TabBox::tabBoxAdded); QVERIFY(tabboxAddedSpy.isValid()); QSignalSpy tabboxClosedSpy(TabBox::TabBox::self(), &TabBox::TabBox::tabBoxClosed); QVERIFY(tabboxClosedSpy.isValid()); // press alt+tab quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::AltModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_TAB, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_TAB, timestamp++); QVERIFY(tabboxAddedSpy.wait()); QVERIFY(TabBox::TabBox::self()->isGrabbed()); // release alt kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); QCOMPARE(tabboxClosedSpy.count(), 1); QCOMPARE(TabBox::TabBox::self()->isGrabbed(), false); QCOMPARE(workspace()->activeClient(), c2); surface3.reset(); QVERIFY(Test::waitForWindowDestroyed(c3)); surface2.reset(); QVERIFY(Test::waitForWindowDestroyed(c2)); surface1.reset(); QVERIFY(Test::waitForWindowDestroyed(c1)); } void TabBoxTest::testMoveBackward() { // this test verifies that Alt+Shift+tab works correctly moving backward // first create three windows QScopedPointer surface1(Test::createSurface()); QScopedPointer shellSurface1(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface1.data())); auto c1 = Test::renderAndWaitForShown(surface1.data(), QSize(100, 50), Qt::blue); QVERIFY(c1); QVERIFY(c1->isActive()); QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface2.data())); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::red); QVERIFY(c2); QVERIFY(c2->isActive()); QScopedPointer surface3(Test::createSurface()); QScopedPointer shellSurface3(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface3.data())); auto c3 = Test::renderAndWaitForShown(surface3.data(), QSize(100, 50), Qt::red); QVERIFY(c3); QVERIFY(c3->isActive()); // Setup tabbox signal spies QSignalSpy tabboxAddedSpy(TabBox::TabBox::self(), &TabBox::TabBox::tabBoxAdded); QVERIFY(tabboxAddedSpy.isValid()); QSignalSpy tabboxClosedSpy(TabBox::TabBox::self(), &TabBox::TabBox::tabBoxClosed); QVERIFY(tabboxClosedSpy.isValid()); // press alt+shift+tab quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::AltModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::AltModifier | Qt::ShiftModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_TAB, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_TAB, timestamp++); QVERIFY(tabboxAddedSpy.wait()); QVERIFY(TabBox::TabBox::self()->isGrabbed()); // release alt kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); QCOMPARE(tabboxClosedSpy.count(), 0); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); QCOMPARE(tabboxClosedSpy.count(), 1); QCOMPARE(TabBox::TabBox::self()->isGrabbed(), false); QCOMPARE(workspace()->activeClient(), c1); surface3.reset(); QVERIFY(Test::waitForWindowDestroyed(c3)); surface2.reset(); QVERIFY(Test::waitForWindowDestroyed(c2)); surface1.reset(); QVERIFY(Test::waitForWindowDestroyed(c1)); } WAYLANDTEST_MAIN(TabBoxTest) #include "tabbox_test.moc" diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index 7d95b6cf4..703ea4679 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -1,453 +1,465 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "shell_client.h" #include "screenlockerwatcher.h" #include "wayland_server.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include //screenlocker #include #include +// system +#include +#include +#include + using namespace KWayland::Client; namespace KWin { namespace Test { static struct { ConnectionThread *connection = nullptr; EventQueue *queue = nullptr; Compositor *compositor = nullptr; ServerSideDecorationManager *decoration = nullptr; Shell *shell = nullptr; XdgShell *xdgShellV5 = nullptr; ShmPool *shm = nullptr; Seat *seat = nullptr; PlasmaShell *plasmaShell = nullptr; PlasmaWindowManagement *windowManagement = nullptr; PointerConstraints *pointerConstraints = nullptr; QThread *thread = nullptr; } s_waylandConnection; -bool setupWaylandConnection(const QString &socketName, AdditionalWaylandInterfaces flags) +bool setupWaylandConnection(AdditionalWaylandInterfaces flags) { if (s_waylandConnection.connection) { return false; } + + int sx[2]; + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { + return false; + } + KWin::waylandServer()->display()->createClient(sx[0]); // setup connection s_waylandConnection.connection = new ConnectionThread; QSignalSpy connectedSpy(s_waylandConnection.connection, &ConnectionThread::connected); if (!connectedSpy.isValid()) { return false; } - s_waylandConnection.connection->setSocketName(socketName); + s_waylandConnection.connection->setSocketFd(sx[1]); s_waylandConnection.thread = new QThread(kwinApp()); s_waylandConnection.connection->moveToThread(s_waylandConnection.thread); s_waylandConnection.thread->start(); s_waylandConnection.connection->initConnection(); if (!connectedSpy.wait()) { return false; } s_waylandConnection.queue = new EventQueue; s_waylandConnection.queue->setup(s_waylandConnection.connection); if (!s_waylandConnection.queue->isValid()) { return false; } Registry registry; registry.setEventQueue(s_waylandConnection.queue); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); if (!allAnnounced.isValid()) { return false; } registry.create(s_waylandConnection.connection); if (!registry.isValid()) { return false; } registry.setup(); if (!allAnnounced.wait()) { return false; } s_waylandConnection.compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, registry.interface(Registry::Interface::Compositor).version); if (!s_waylandConnection.compositor->isValid()) { return false; } s_waylandConnection.shm = registry.createShmPool(registry.interface(Registry::Interface::Shm).name, registry.interface(Registry::Interface::Shm).version); if (!s_waylandConnection.shm->isValid()) { return false; } s_waylandConnection.shell = registry.createShell(registry.interface(Registry::Interface::Shell).name, registry.interface(Registry::Interface::Shell).version); if (!s_waylandConnection.shell->isValid()) { return false; } s_waylandConnection.xdgShellV5 = registry.createXdgShell(registry.interface(Registry::Interface::XdgShellUnstableV5).name, registry.interface(Registry::Interface::XdgShellUnstableV5).version); if (!s_waylandConnection.xdgShellV5->isValid()) { return false; } if (flags.testFlag(AdditionalWaylandInterface::Seat)) { s_waylandConnection.seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version); if (!s_waylandConnection.seat->isValid()) { return false; } } if (flags.testFlag(AdditionalWaylandInterface::Decoration)) { s_waylandConnection.decoration = registry.createServerSideDecorationManager(registry.interface(Registry::Interface::ServerSideDecorationManager).name, registry.interface(Registry::Interface::ServerSideDecorationManager).version); if (!s_waylandConnection.decoration->isValid()) { return false; } } if (flags.testFlag(AdditionalWaylandInterface::PlasmaShell)) { s_waylandConnection.plasmaShell = registry.createPlasmaShell(registry.interface(Registry::Interface::PlasmaShell).name, registry.interface(Registry::Interface::PlasmaShell).version); if (!s_waylandConnection.plasmaShell->isValid()) { return false; } } if (flags.testFlag(AdditionalWaylandInterface::WindowManagement)) { s_waylandConnection.windowManagement = registry.createPlasmaWindowManagement(registry.interface(Registry::Interface::PlasmaWindowManagement).name, registry.interface(Registry::Interface::PlasmaWindowManagement).version); if (!s_waylandConnection.windowManagement->isValid()) { return false; } } if (flags.testFlag(AdditionalWaylandInterface::PointerConstraints)) { s_waylandConnection.pointerConstraints = registry.createPointerConstraints(registry.interface(Registry::Interface::PointerConstraintsUnstableV1).name, registry.interface(Registry::Interface::PointerConstraintsUnstableV1).version); if (!s_waylandConnection.pointerConstraints->isValid()) { return false; } } return true; } void destroyWaylandConnection() { delete s_waylandConnection.compositor; s_waylandConnection.compositor = nullptr; delete s_waylandConnection.windowManagement; s_waylandConnection.windowManagement = nullptr; delete s_waylandConnection.plasmaShell; s_waylandConnection.plasmaShell = nullptr; delete s_waylandConnection.decoration; s_waylandConnection.decoration = nullptr; delete s_waylandConnection.decoration; s_waylandConnection.decoration = nullptr; delete s_waylandConnection.seat; s_waylandConnection.seat = nullptr; delete s_waylandConnection.pointerConstraints; s_waylandConnection.pointerConstraints = nullptr; delete s_waylandConnection.xdgShellV5; s_waylandConnection.xdgShellV5 = nullptr; delete s_waylandConnection.shell; s_waylandConnection.shell = nullptr; delete s_waylandConnection.shm; s_waylandConnection.shm = nullptr; delete s_waylandConnection.queue; s_waylandConnection.queue = nullptr; if (s_waylandConnection.thread) { QSignalSpy spy(s_waylandConnection.connection, &QObject::destroyed); s_waylandConnection.connection->deleteLater(); if (spy.isEmpty()) { QVERIFY(spy.wait()); } s_waylandConnection.thread->quit(); s_waylandConnection.thread->wait(); delete s_waylandConnection.thread; s_waylandConnection.thread = nullptr; s_waylandConnection.connection = nullptr; } } ConnectionThread *waylandConnection() { return s_waylandConnection.connection; } Compositor *waylandCompositor() { return s_waylandConnection.compositor; } Shell *waylandShell() { return s_waylandConnection.shell; } ShmPool *waylandShmPool() { return s_waylandConnection.shm; } Seat *waylandSeat() { return s_waylandConnection.seat; } ServerSideDecorationManager *waylandServerSideDecoration() { return s_waylandConnection.decoration; } PlasmaShell *waylandPlasmaShell() { return s_waylandConnection.plasmaShell; } PlasmaWindowManagement *waylandWindowManagement() { return s_waylandConnection.windowManagement; } PointerConstraints *waylandPointerConstraints() { return s_waylandConnection.pointerConstraints; } bool waitForWaylandPointer() { if (!s_waylandConnection.seat) { return false; } QSignalSpy hasPointerSpy(s_waylandConnection.seat, &Seat::hasPointerChanged); if (!hasPointerSpy.isValid()) { return false; } return hasPointerSpy.wait(); } bool waitForWaylandTouch() { if (!s_waylandConnection.seat) { return false; } QSignalSpy hasTouchSpy(s_waylandConnection.seat, &Seat::hasTouchChanged); if (!hasTouchSpy.isValid()) { return false; } return hasTouchSpy.wait(); } bool waitForWaylandKeyboard() { if (!s_waylandConnection.seat) { return false; } QSignalSpy hasKeyboardSpy(s_waylandConnection.seat, &Seat::hasKeyboardChanged); if (!hasKeyboardSpy.isValid()) { return false; } return hasKeyboardSpy.wait(); } void render(Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format) { QImage img(size, format); img.fill(color); surface->attachBuffer(s_waylandConnection.shm->createBuffer(img)); surface->damage(QRect(QPoint(0, 0), size)); surface->commit(Surface::CommitFlag::None); } ShellClient *waitForWaylandWindowShown(int timeout) { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); if (!clientAddedSpy.isValid()) { return nullptr; } if (!clientAddedSpy.wait(timeout)) { return nullptr; } return clientAddedSpy.first().first().value(); } ShellClient *renderAndWaitForShown(Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format, int timeout) { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); if (!clientAddedSpy.isValid()) { return nullptr; } render(surface, size, color, format); flushWaylandConnection(); if (!clientAddedSpy.wait(timeout)) { return nullptr; } return clientAddedSpy.first().first().value(); } void flushWaylandConnection() { if (s_waylandConnection.connection) { s_waylandConnection.connection->flush(); } } Surface *createSurface(QObject *parent) { if (!s_waylandConnection.compositor) { return nullptr; } auto s = s_waylandConnection.compositor->createSurface(parent); if (!s->isValid()) { delete s; return nullptr; } return s; } ShellSurface *createShellSurface(Surface *surface, QObject *parent) { if (!s_waylandConnection.shell) { return nullptr; } auto s = s_waylandConnection.shell->createSurface(surface, parent); if (!s->isValid()) { delete s; return nullptr; } return s; } XdgShellSurface *createXdgShellV5Surface(Surface *surface, QObject *parent) { if (!s_waylandConnection.xdgShellV5) { return nullptr; } auto s = s_waylandConnection.xdgShellV5->createSurface(surface, parent); if (!s->isValid()) { delete s; return nullptr; } return s; } QObject *createShellSurface(ShellSurfaceType type, KWayland::Client::Surface *surface, QObject *parent) { switch (type) { case ShellSurfaceType::WlShell: return createShellSurface(surface, parent); case ShellSurfaceType::XdgShellV5: return createXdgShellV5Surface(surface, parent); default: Q_UNREACHABLE(); return nullptr; } } bool waitForWindowDestroyed(AbstractClient *client) { QSignalSpy destroyedSpy(client, &QObject::destroyed); if (!destroyedSpy.isValid()) { return false; } return destroyedSpy.wait(); } bool lockScreen() { if (waylandServer()->isScreenLocked()) { return false; } QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); if (!lockStateChangedSpy.isValid()) { return false; } ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); if (lockStateChangedSpy.count() != 1) { return false; } if (!waylandServer()->isScreenLocked()) { return false; } if (!ScreenLockerWatcher::self()->isLocked()) { QSignalSpy lockedSpy(ScreenLockerWatcher::self(), &ScreenLockerWatcher::locked); if (!lockedSpy.isValid()) { return false; } if (!lockedSpy.wait()) { return false; } if (!ScreenLockerWatcher::self()->isLocked()) { return false; } } return true; } bool unlockScreen() { QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); if (!lockStateChangedSpy.isValid()) { return false; } using namespace ScreenLocker; const auto children = KSldApp::self()->children(); for (auto it = children.begin(); it != children.end(); ++it) { if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) { continue; } QMetaObject::invokeMethod(*it, "requestUnlock"); break; } if (waylandServer()->isScreenLocked()) { lockStateChangedSpy.wait(); } if (waylandServer()->isScreenLocked()) { return true; } if (ScreenLockerWatcher::self()->isLocked()) { QSignalSpy lockedSpy(ScreenLockerWatcher::self(), &ScreenLockerWatcher::locked); if (!lockedSpy.isValid()) { return false; } if (!lockedSpy.wait()) { return false; } if (ScreenLockerWatcher::self()->isLocked()) { return false; } } return true; } } } diff --git a/autotests/integration/touch_input_test.cpp b/autotests/integration/touch_input_test.cpp index f4406757b..b3acf0431 100644 --- a/autotests/integration/touch_input_test.cpp +++ b/autotests/integration/touch_input_test.cpp @@ -1,239 +1,239 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "cursor.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_touch_input-0"); class TouchInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMultipleTouchPoints(); void testCancel(); void testTouchMouseAction(); private: AbstractClient *showWindow(); KWayland::Client::Touch *m_touch = nullptr; }; void TouchInputTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TouchInputTest::init() { using namespace KWayland::Client; - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandTouch()); m_touch = Test::waylandSeat()->createTouch(Test::waylandSeat()); QVERIFY(m_touch); QVERIFY(m_touch->isValid()); screens()->setCurrent(0); Cursor::setPos(QPoint(1280, 512)); } void TouchInputTest::cleanup() { delete m_touch; m_touch = nullptr; Test::destroyWaylandConnection(); } AbstractClient *TouchInputTest::showWindow() { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; Surface *surface = Test::createSurface(Test::waylandCompositor()); VERIFY(surface); ShellSurface *shellSurface = Test::createShellSurface(surface, surface); VERIFY(shellSurface); // let's render auto c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); VERIFY(c); COMPARE(workspace()->activeClient(), c); #undef VERIFY #undef COMPARE return c; } void TouchInputTest::testMultipleTouchPoints() { using namespace KWayland::Client; AbstractClient *c = showWindow(); c->move(100, 100); QVERIFY(c); QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted); QVERIFY(sequenceStartedSpy.isValid()); QSignalSpy pointAddedSpy(m_touch, &Touch::pointAdded); QVERIFY(pointAddedSpy.isValid()); QSignalSpy pointMovedSpy(m_touch, &Touch::pointMoved); QVERIFY(pointMovedSpy.isValid()); QSignalSpy pointRemovedSpy(m_touch, &Touch::pointRemoved); QVERIFY(pointRemovedSpy.isValid()); QSignalSpy endedSpy(m_touch, &Touch::sequenceEnded); QVERIFY(endedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(1, QPointF(125, 125), timestamp++); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); QCOMPARE(m_touch->sequence().count(), 1); QCOMPARE(m_touch->sequence().first()->isDown(), true); QCOMPARE(m_touch->sequence().first()->position(), QPointF(25, 25)); QCOMPARE(pointAddedSpy.count(), 0); QCOMPARE(pointMovedSpy.count(), 0); // a point outside the window kwinApp()->platform()->touchDown(2, QPointF(0, 0), timestamp++); QVERIFY(pointAddedSpy.wait()); QCOMPARE(pointAddedSpy.count(), 1); QCOMPARE(m_touch->sequence().count(), 2); QCOMPARE(m_touch->sequence().at(1)->isDown(), true); QCOMPARE(m_touch->sequence().at(1)->position(), QPointF(-100, -100)); QCOMPARE(pointMovedSpy.count(), 0); // let's move that one kwinApp()->platform()->touchMotion(2, QPointF(100, 100), timestamp++); QVERIFY(pointMovedSpy.wait()); QCOMPARE(pointMovedSpy.count(), 1); QCOMPARE(m_touch->sequence().count(), 2); QCOMPARE(m_touch->sequence().at(1)->isDown(), true); QCOMPARE(m_touch->sequence().at(1)->position(), QPointF(0, 0)); kwinApp()->platform()->touchUp(1, timestamp++); QVERIFY(pointRemovedSpy.wait()); QCOMPARE(pointRemovedSpy.count(), 1); QCOMPARE(m_touch->sequence().count(), 2); QCOMPARE(m_touch->sequence().first()->isDown(), false); QCOMPARE(endedSpy.count(), 0); kwinApp()->platform()->touchUp(2, timestamp++); QVERIFY(pointRemovedSpy.wait()); QCOMPARE(pointRemovedSpy.count(), 2); QCOMPARE(m_touch->sequence().count(), 2); QCOMPARE(m_touch->sequence().first()->isDown(), false); QCOMPARE(m_touch->sequence().at(1)->isDown(), false); QCOMPARE(endedSpy.count(), 1); } void TouchInputTest::testCancel() { using namespace KWayland::Client; AbstractClient *c = showWindow(); c->move(100, 100); QVERIFY(c); QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted); QVERIFY(sequenceStartedSpy.isValid()); QSignalSpy cancelSpy(m_touch, &Touch::sequenceCanceled); QVERIFY(cancelSpy.isValid()); QSignalSpy pointRemovedSpy(m_touch, &Touch::pointRemoved); QVERIFY(pointRemovedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(1, QPointF(125, 125), timestamp++); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); // cancel kwinApp()->platform()->touchCancel(); QVERIFY(cancelSpy.wait()); QCOMPARE(cancelSpy.count(), 1); kwinApp()->platform()->touchUp(1, timestamp++); QVERIFY(!pointRemovedSpy.wait(100)); QCOMPARE(pointRemovedSpy.count(), 0); } void TouchInputTest::testTouchMouseAction() { // this test verifies that a touch down on an inactive client will activate it using namespace KWayland::Client; // create two windows AbstractClient *c1 = showWindow(); QVERIFY(c1); AbstractClient *c2 = showWindow(); QVERIFY(c2); QVERIFY(!c1->isActive()); QVERIFY(c2->isActive()); // also create a sequence started spy as the touch event should be passed through QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted); QVERIFY(sequenceStartedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(1, c1->geometry().center(), timestamp++); QVERIFY(c1->isActive()); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); // cleanup kwinApp()->platform()->touchCancel(); } } WAYLANDTEST_MAIN(KWin::TouchInputTest) #include "touch_input_test.moc" diff --git a/autotests/integration/transient_no_input_test.cpp b/autotests/integration/transient_no_input_test.cpp index b6e0d7a85..62c58d5ac 100644 --- a/autotests/integration/transient_no_input_test.cpp +++ b/autotests/integration/transient_no_input_test.cpp @@ -1,116 +1,116 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_transient_no_input-0"); class TransientNoInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testTransientNoFocus(); }; void TransientNoInputTest::initTestCase() { qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); } void TransientNoInputTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName)); + QVERIFY(Test::setupWaylandConnection()); } void TransientNoInputTest::cleanup() { Test::destroyWaylandConnection(); } void TransientNoInputTest::testTransientNoFocus() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(!shellSurface.isNull()); // let's render Test::render(surface.data(), QSize(100, 50), Qt::blue); Test::flushWaylandConnection(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); // let's create a transient with no input QScopedPointer transientSurface(Test::createSurface()); QVERIFY(!transientSurface.isNull()); QScopedPointer transientShellSurface(Test::createShellSurface(transientSurface.data())); QVERIFY(!transientShellSurface.isNull()); transientShellSurface->setTransient(surface.data(), QPoint(10, 20), ShellSurface::TransientFlag::NoFocus); Test::flushWaylandConnection(); // let's render Test::render(transientSurface.data(), QSize(200, 20), Qt::red); Test::flushWaylandConnection(); QVERIFY(clientAddedSpy.wait()); // get the latest ShellClient auto transientClient = clientAddedSpy.last().first().value(); QVERIFY(transientClient != c); QCOMPARE(transientClient->geometry(), QRect(c->x() + 10, c->y() + 20, 200, 20)); QVERIFY(transientClient->isTransient()); QCOMPARE(transientClient->transientPlacementHint(), QPoint(10, 20)); QVERIFY(!transientClient->wantsInput()); // workspace's active window should not have changed QCOMPARE(workspace()->activeClient(), c); } } WAYLANDTEST_MAIN(KWin::TransientNoInputTest) #include "transient_no_input_test.moc" diff --git a/autotests/integration/transient_placement.cpp b/autotests/integration/transient_placement.cpp index a41e35aa3..f51082c02 100644 --- a/autotests/integration/transient_placement.cpp +++ b/autotests/integration/transient_placement.cpp @@ -1,225 +1,225 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_transient_placement-0"); class TransientPlacementTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testSimplePosition_data(); void testSimplePosition(); void testDecorationPosition_data(); void testDecorationPosition(); private: AbstractClient *showWindow(const QSize &size, bool decorated = false, KWayland::Client::Surface *parent = nullptr, const QPoint &offset = QPoint()); KWayland::Client::Surface *surfaceForClient(AbstractClient *c) const; }; void TransientPlacementTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void TransientPlacementTest::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Decoration)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void TransientPlacementTest::cleanup() { Test::destroyWaylandConnection(); } AbstractClient *TransientPlacementTest::showWindow(const QSize &size, bool decorated, KWayland::Client::Surface *parent, const QPoint &offset) { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; Surface *surface = Test::createSurface(Test::waylandCompositor()); VERIFY(surface); ShellSurface *shellSurface = Test::createShellSurface(surface, surface); VERIFY(shellSurface); if (parent) { shellSurface->setTransient(parent, offset); } if (decorated) { auto deco = Test::waylandServerSideDecoration()->create(surface, surface); QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged); VERIFY(decoSpy.isValid()); VERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); VERIFY(decoSpy.wait()); COMPARE(deco->mode(), ServerSideDecoration::Mode::Server); } // let's render auto c = Test::renderAndWaitForShown(surface, size, Qt::blue); VERIFY(c); COMPARE(workspace()->activeClient(), c); #undef VERIFY #undef COMPARE return c; } KWayland::Client::Surface *TransientPlacementTest::surfaceForClient(AbstractClient *c) const { const auto &surfaces = KWayland::Client::Surface::all(); auto it = std::find_if(surfaces.begin(), surfaces.end(), [c] (KWayland::Client::Surface *s) { return s->id() == c->surface()->id(); }); if (it != surfaces.end()) { return *it; } return nullptr; } void TransientPlacementTest::testSimplePosition_data() { QTest::addColumn("parentSize"); QTest::addColumn("parentPosition"); QTest::addColumn("transientSize"); QTest::addColumn("transientOffset"); QTest::addColumn("expectedGeometry"); QTest::newRow("0/0") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(0, 0) << QRect(0, 0, 10, 100); QTest::newRow("bottomRight") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(639, 511) << QRect(639, 511, 10, 100); QTest::newRow("offset") << QSize(640, 512) << QPoint(200, 300) << QSize(100, 10) << QPoint(320, 256) << QRect(520, 556, 100, 10); QTest::newRow("right border") << QSize(1280, 1024) << QPoint(0, 0) << QSize(10, 100) << QPoint(1279, 50) << QRect(1269, 50, 10, 100); QTest::newRow("bottom border") << QSize(1280, 1024) << QPoint(0, 0) << QSize(10, 100) << QPoint(512, 1020) << QRect(512, 920, 10, 100); QTest::newRow("bottom right") << QSize(1280, 1024) << QPoint(0, 0) << QSize(10, 100) << QPoint(1279, 1020) << QRect(1269, 920, 10, 100); QTest::newRow("top border") << QSize(1280, 1024) << QPoint(0, -100) << QSize(10, 100) << QPoint(512, 50) << QRect(512, 0, 10, 100); QTest::newRow("left border") << QSize(1280, 1024) << QPoint(-100, 0) << QSize(100, 10) << QPoint(50, 512) << QRect(0, 512, 100, 10); QTest::newRow("top left") << QSize(1280, 1024) << QPoint(-100, -100) << QSize(100, 100) << QPoint(50, 50) << QRect(0, 0, 100, 100); QTest::newRow("bottom left") << QSize(1280, 1024) << QPoint(-100, 0) << QSize(100, 100) << QPoint(50, 1000) << QRect(0, 900, 100, 100); } void TransientPlacementTest::testSimplePosition() { // this test verifies that the position of a transient window is taken from the passed position // there are no further constraints like window too large to fit screen, cascading transients, etc // some test cases also verify that the transient fits on the screen QFETCH(QSize, parentSize); AbstractClient *parent = showWindow(parentSize); QVERIFY(parent->clientPos().isNull()); QVERIFY(!parent->isDecorated()); QFETCH(QPoint, parentPosition); parent->move(parentPosition); QFETCH(QSize, transientSize); QFETCH(QPoint, transientOffset); AbstractClient *transient = showWindow(transientSize, false, surfaceForClient(parent), transientOffset); QVERIFY(transient); QVERIFY(!transient->isDecorated()); QVERIFY(transient->hasTransientPlacementHint()); QTEST(transient->geometry(), "expectedGeometry"); } void TransientPlacementTest::testDecorationPosition_data() { QTest::addColumn("parentSize"); QTest::addColumn("parentPosition"); QTest::addColumn("transientSize"); QTest::addColumn("transientOffset"); QTest::addColumn("expectedGeometry"); QTest::newRow("0/0") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(0, 0) << QRect(0, 0, 10, 100); QTest::newRow("bottomRight") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(639, 511) << QRect(639, 511, 10, 100); QTest::newRow("offset") << QSize(640, 512) << QPoint(200, 300) << QSize(100, 10) << QPoint(320, 256) << QRect(520, 556, 100, 10); } void TransientPlacementTest::testDecorationPosition() { // this test verifies that a transient window is correctly placed if the parent window has a // server side decoration QFETCH(QSize, parentSize); AbstractClient *parent = showWindow(parentSize, true); QVERIFY(!parent->clientPos().isNull()); QVERIFY(parent->isDecorated()); QFETCH(QPoint, parentPosition); parent->move(parentPosition); QFETCH(QSize, transientSize); QFETCH(QPoint, transientOffset); AbstractClient *transient = showWindow(transientSize, false, surfaceForClient(parent), transientOffset); QVERIFY(transient); QVERIFY(!transient->isDecorated()); QVERIFY(transient->hasTransientPlacementHint()); QFETCH(QRect, expectedGeometry); expectedGeometry.translate(parent->clientPos()); QCOMPARE(transient->geometry(), expectedGeometry); } } WAYLANDTEST_MAIN(KWin::TransientPlacementTest) #include "transient_placement.moc" diff --git a/autotests/integration/window_selection_test.cpp b/autotests/integration/window_selection_test.cpp index 18472f6df..3796da5ce 100644 --- a/autotests/integration/window_selection_test.cpp +++ b/autotests/integration/window_selection_test.cpp @@ -1,455 +1,455 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "keyboard_input.h" #include "platform.h" #include "pointer_input.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_window_selection-0"); class TestWindowSelection : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testSelectOnWindowPointer(); void testSelectOnWindowKeyboard_data(); void testSelectOnWindowKeyboard(); void testCancelOnWindowPointer(); void testCancelOnWindowKeyboard(); void testSelectPointPointer(); }; void TestWindowSelection::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TestWindowSelection::init() { - QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandPointer()); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(1280, 512)); } void TestWindowSelection::cleanup() { Test::destroyWaylandConnection(); } void TestWindowSelection::testSelectOnWindowPointer() { // this test verifies window selection through pointer works QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QScopedPointer pointer(Test::waylandSeat()->createPointer()); QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); QSignalSpy pointerEnteredSpy(pointer.data(), &Pointer::entered); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy pointerLeftSpy(pointer.data(), &Pointer::left); QVERIFY(pointerLeftSpy.isValid()); QSignalSpy keyboardEnteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(keyboardEnteredSpy.isValid()); QSignalSpy keyboardLeftSpy(keyboard.data(), &Keyboard::left); QVERIFY(keyboardLeftSpy.isValid()); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(keyboardEnteredSpy.wait()); KWin::Cursor::setPos(client->geometry().center()); QCOMPARE(input()->pointer()->window().data(), client); QVERIFY(pointerEnteredSpy.wait()); Toplevel *selectedWindow = nullptr; auto callback = [&selectedWindow] (Toplevel *t) { selectedWindow = t; }; // start the interaction QCOMPARE(input()->isSelectingWindow(), false); kwinApp()->platform()->startInteractiveWindowSelection(callback); QCOMPARE(input()->isSelectingWindow(), true); QVERIFY(!selectedWindow); QCOMPARE(keyboardLeftSpy.count(), 0); QVERIFY(pointerLeftSpy.wait()); if (keyboardLeftSpy.isEmpty()) { QVERIFY(keyboardLeftSpy.wait()); } QCOMPARE(pointerLeftSpy.count(), 1); QCOMPARE(keyboardLeftSpy.count(), 1); // simulate left button press quint32 timestamp = 0; kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); // should not have ended the mode QCOMPARE(input()->isSelectingWindow(), true); QVERIFY(!selectedWindow); QVERIFY(input()->pointer()->window().isNull()); // updating the pointer should not change anything input()->pointer()->update(); QVERIFY(input()->pointer()->window().isNull()); // updating keyboard should also not change input()->keyboard()->update(); // perform a right button click kwinApp()->platform()->pointerButtonPressed(BTN_RIGHT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, timestamp++); // should not have ended the mode QCOMPARE(input()->isSelectingWindow(), true); QVERIFY(!selectedWindow); // now release kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(selectedWindow, client); QCOMPARE(input()->pointer()->window().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { QVERIFY(keyboardEnteredSpy.wait()); } QCOMPARE(pointerLeftSpy.count(), 1); QCOMPARE(keyboardLeftSpy.count(), 1); QCOMPARE(pointerEnteredSpy.count(), 2); QCOMPARE(keyboardEnteredSpy.count(), 2); } void TestWindowSelection::testSelectOnWindowKeyboard_data() { QTest::addColumn("key"); QTest::newRow("enter") << KEY_ENTER; QTest::newRow("keypad enter") << KEY_KPENTER; QTest::newRow("space") << KEY_SPACE; } void TestWindowSelection::testSelectOnWindowKeyboard() { // this test verifies window selection through keyboard key QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QScopedPointer pointer(Test::waylandSeat()->createPointer()); QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); QSignalSpy pointerEnteredSpy(pointer.data(), &Pointer::entered); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy pointerLeftSpy(pointer.data(), &Pointer::left); QVERIFY(pointerLeftSpy.isValid()); QSignalSpy keyboardEnteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(keyboardEnteredSpy.isValid()); QSignalSpy keyboardLeftSpy(keyboard.data(), &Keyboard::left); QVERIFY(keyboardLeftSpy.isValid()); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(keyboardEnteredSpy.wait()); QVERIFY(!client->geometry().contains(KWin::Cursor::pos())); Toplevel *selectedWindow = nullptr; auto callback = [&selectedWindow] (Toplevel *t) { selectedWindow = t; }; // start the interaction QCOMPARE(input()->isSelectingWindow(), false); kwinApp()->platform()->startInteractiveWindowSelection(callback); QCOMPARE(input()->isSelectingWindow(), true); QVERIFY(!selectedWindow); QCOMPARE(keyboardLeftSpy.count(), 0); QVERIFY(keyboardLeftSpy.wait()); QCOMPARE(pointerLeftSpy.count(), 0); QCOMPARE(keyboardLeftSpy.count(), 1); // simulate key press quint32 timestamp = 0; // move cursor through keys auto keyPress = [×tamp] (qint32 key) { kwinApp()->platform()->keyboardKeyPressed(key, timestamp++); kwinApp()->platform()->keyboardKeyReleased(key, timestamp++); }; while (KWin::Cursor::pos().x() >= client->geometry().x() + client->geometry().width()) { keyPress(KEY_LEFT); } while (KWin::Cursor::pos().x() <= client->geometry().x()) { keyPress(KEY_RIGHT); } while (KWin::Cursor::pos().y() <= client->geometry().y()) { keyPress(KEY_DOWN); } while (KWin::Cursor::pos().y() >= client->geometry().y() + client->geometry().height()) { keyPress(KEY_UP); } QFETCH(qint32, key); kwinApp()->platform()->keyboardKeyPressed(key, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(selectedWindow, client); QCOMPARE(input()->pointer()->window().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { QVERIFY(keyboardEnteredSpy.wait()); } QCOMPARE(pointerLeftSpy.count(), 0); QCOMPARE(keyboardLeftSpy.count(), 1); QCOMPARE(pointerEnteredSpy.count(), 1); QCOMPARE(keyboardEnteredSpy.count(), 2); kwinApp()->platform()->keyboardKeyReleased(key, timestamp++); } void TestWindowSelection::testCancelOnWindowPointer() { // this test verifies that window selection cancels through right button click QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QScopedPointer pointer(Test::waylandSeat()->createPointer()); QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); QSignalSpy pointerEnteredSpy(pointer.data(), &Pointer::entered); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy pointerLeftSpy(pointer.data(), &Pointer::left); QVERIFY(pointerLeftSpy.isValid()); QSignalSpy keyboardEnteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(keyboardEnteredSpy.isValid()); QSignalSpy keyboardLeftSpy(keyboard.data(), &Keyboard::left); QVERIFY(keyboardLeftSpy.isValid()); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(keyboardEnteredSpy.wait()); KWin::Cursor::setPos(client->geometry().center()); QCOMPARE(input()->pointer()->window().data(), client); QVERIFY(pointerEnteredSpy.wait()); Toplevel *selectedWindow = nullptr; auto callback = [&selectedWindow] (Toplevel *t) { selectedWindow = t; }; // start the interaction QCOMPARE(input()->isSelectingWindow(), false); kwinApp()->platform()->startInteractiveWindowSelection(callback); QCOMPARE(input()->isSelectingWindow(), true); QVERIFY(!selectedWindow); QCOMPARE(keyboardLeftSpy.count(), 0); QVERIFY(pointerLeftSpy.wait()); if (keyboardLeftSpy.isEmpty()) { QVERIFY(keyboardLeftSpy.wait()); } QCOMPARE(pointerLeftSpy.count(), 1); QCOMPARE(keyboardLeftSpy.count(), 1); // simulate left button press quint32 timestamp = 0; kwinApp()->platform()->pointerButtonPressed(BTN_RIGHT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QVERIFY(!selectedWindow); QCOMPARE(input()->pointer()->window().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { QVERIFY(keyboardEnteredSpy.wait()); } QCOMPARE(pointerLeftSpy.count(), 1); QCOMPARE(keyboardLeftSpy.count(), 1); QCOMPARE(pointerEnteredSpy.count(), 2); QCOMPARE(keyboardEnteredSpy.count(), 2); } void TestWindowSelection::testCancelOnWindowKeyboard() { // this test verifies that cancel window selection through escape key works QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QScopedPointer pointer(Test::waylandSeat()->createPointer()); QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); QSignalSpy pointerEnteredSpy(pointer.data(), &Pointer::entered); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy pointerLeftSpy(pointer.data(), &Pointer::left); QVERIFY(pointerLeftSpy.isValid()); QSignalSpy keyboardEnteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(keyboardEnteredSpy.isValid()); QSignalSpy keyboardLeftSpy(keyboard.data(), &Keyboard::left); QVERIFY(keyboardLeftSpy.isValid()); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(keyboardEnteredSpy.wait()); KWin::Cursor::setPos(client->geometry().center()); QCOMPARE(input()->pointer()->window().data(), client); QVERIFY(pointerEnteredSpy.wait()); Toplevel *selectedWindow = nullptr; auto callback = [&selectedWindow] (Toplevel *t) { selectedWindow = t; }; // start the interaction QCOMPARE(input()->isSelectingWindow(), false); kwinApp()->platform()->startInteractiveWindowSelection(callback); QCOMPARE(input()->isSelectingWindow(), true); QVERIFY(!selectedWindow); QCOMPARE(keyboardLeftSpy.count(), 0); QVERIFY(pointerLeftSpy.wait()); if (keyboardLeftSpy.isEmpty()) { QVERIFY(keyboardLeftSpy.wait()); } QCOMPARE(pointerLeftSpy.count(), 1); QCOMPARE(keyboardLeftSpy.count(), 1); // simulate left button press quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QVERIFY(!selectedWindow); QCOMPARE(input()->pointer()->window().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { QVERIFY(keyboardEnteredSpy.wait()); } QCOMPARE(pointerLeftSpy.count(), 1); QCOMPARE(keyboardLeftSpy.count(), 1); QCOMPARE(pointerEnteredSpy.count(), 2); QCOMPARE(keyboardEnteredSpy.count(), 2); kwinApp()->platform()->keyboardKeyReleased(KEY_ESC, timestamp++); } void TestWindowSelection::testSelectPointPointer() { // this test verifies point selection through pointer works QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QScopedPointer pointer(Test::waylandSeat()->createPointer()); QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); QSignalSpy pointerEnteredSpy(pointer.data(), &Pointer::entered); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy pointerLeftSpy(pointer.data(), &Pointer::left); QVERIFY(pointerLeftSpy.isValid()); QSignalSpy keyboardEnteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(keyboardEnteredSpy.isValid()); QSignalSpy keyboardLeftSpy(keyboard.data(), &Keyboard::left); QVERIFY(keyboardLeftSpy.isValid()); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(keyboardEnteredSpy.wait()); KWin::Cursor::setPos(client->geometry().center()); QCOMPARE(input()->pointer()->window().data(), client); QVERIFY(pointerEnteredSpy.wait()); QPoint point; auto callback = [&point] (const QPoint &p) { point = p; }; // start the interaction QCOMPARE(input()->isSelectingWindow(), false); kwinApp()->platform()->startInteractivePositionSelection(callback); QCOMPARE(input()->isSelectingWindow(), true); QCOMPARE(point, QPoint()); QCOMPARE(keyboardLeftSpy.count(), 0); QVERIFY(pointerLeftSpy.wait()); if (keyboardLeftSpy.isEmpty()) { QVERIFY(keyboardLeftSpy.wait()); } QCOMPARE(pointerLeftSpy.count(), 1); QCOMPARE(keyboardLeftSpy.count(), 1); // trying again should not be allowed QPoint point2; kwinApp()->platform()->startInteractivePositionSelection([&point2] (const QPoint &p) { point2 = p; }); QCOMPARE(point2, QPoint(-1, -1)); // simulate left button press quint32 timestamp = 0; kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); // should not have ended the mode QCOMPARE(input()->isSelectingWindow(), true); QCOMPARE(point, QPoint()); QVERIFY(input()->pointer()->window().isNull()); // updating the pointer should not change anything input()->pointer()->update(); QVERIFY(input()->pointer()->window().isNull()); // updating keyboard should also not change input()->keyboard()->update(); // perform a right button click kwinApp()->platform()->pointerButtonPressed(BTN_RIGHT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, timestamp++); // should not have ended the mode QCOMPARE(input()->isSelectingWindow(), true); QCOMPARE(point, QPoint()); // now release kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(point, input()->globalPointer().toPoint()); QCOMPARE(input()->pointer()->window().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { QVERIFY(keyboardEnteredSpy.wait()); } QCOMPARE(pointerLeftSpy.count(), 1); QCOMPARE(keyboardLeftSpy.count(), 1); QCOMPARE(pointerEnteredSpy.count(), 2); QCOMPARE(keyboardEnteredSpy.count(), 2); } WAYLANDTEST_MAIN(TestWindowSelection) #include "window_selection_test.moc"